Website Contact Scraper
Pricing
from $72.00 / 1,000 website scanneds
Website Contact Scraper
Turn company websites into contact records. Finds emails split into personal and role, phone numbers, social profiles across 17 platforms, postal address and named people. Renders JavaScript sites at no extra cost. One charge per domain, and domains with nothing found are free.
Pricing
from $72.00 / 1,000 website scanneds
Rating
0.0
(0)
Developer
丂卩ㄖㄖҜㄚ
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Company websites in, one contact record per domain out: emails, phone numbers, social profiles, postal address, opening hours and named people.
🔍 What does Website Contact Scraper do?
It reads a company's own website and pulls out the contact details published on it, then returns one record per registrable domain so www and subdomains collapse into a single row.
One charge per domain that produced contact details, whatever the size of the site. Domains where nothing is found are not charged.
Exactly what it extracts
| From | Notes | |
|---|---|---|
| Email addresses | mailto: links and page text | Split three ways, see below |
| Phone numbers | tel: links and page text | Each says which it came from |
| Social profiles | Links anywhere on the page | 17 platforms, listed below |
| Postal address | schema.org markup only | Street, city, region, postcode, country |
| Opening hours | schema.org markup only | As published |
| Named people | schema.org Person markup only | Name, job title, email where given |
The 17 platforms: LinkedIn, X, Facebook, Instagram, YouTube, GitHub, TikTok, Pinterest, Threads, Bluesky, Mastodon, Crunchbase, Reddit, Discord, Telegram, WhatsApp and Snapchat.
📊 What data can I extract from a website?
One record per domain:
| Field | What it is |
|---|---|
domain | Registrable domain, so www and subdomains collapse to one record |
hasContactData | Whether anything was found. False is never charged |
emails | Each with type, isFreeProvider, onSiteDomain, foundOn |
emailCount | With personalEmailCount, roleEmailCount, unclassifiedEmailCount |
phones | Each with source, tel-link or text, and foundOn |
socials | Profile URLs keyed by platform |
people | Name, job title, email where published |
address, hours | From schema.org, with the page each came from |
pagesCrawled, pagesVisited | Exactly what was read to produce the record |
renderedWithBrowser | Whether the browser fallback was needed |
error | Set only when no page could be read at all |
A domain that returns nothing still produces a record with
hasContactData: false💡 Why scrape website contact details?
B2B outreach. Build a contact list from what businesses publish themselves, with role and personal addresses kept apart so your first line lands right.
Lead enrichment. Take a list of domains you already have and attach phones, socials and addresses to each one.
Local business research. Collect opening hours and postal addresses from schema.org markup across a sector or region.
List hygiene. See which domains publish nothing at all before you spend anything on them.
🚀 How do I use Website Contact Scraper?
- Click Try for free.
- Put a website into
url, or up to 500 intourls. - Set
maxPagesPerDomain, and turn ondeepScanif the sites have sparse menus. - Click Start and wait for the run to finish.
- Download the results as JSON, CSV or Excel, or pull them from the API.
⬇️ Input
{"url": "monzo.com","maxPagesPerDomain": 5}
| Field | Type | Default | What it does |
|---|---|---|---|
url | string | monzo.com | A single website, protocol optional |
urls | array | Up to 500 websites, charged per domain | |
maxPagesPerDomain | integer | 5 | Pages to crawl per domain, 1 to 20 |
deepScan | boolean | false | Also probe standard contact paths |
jsFallback | boolean | true | Render in a browser only when the HTML yielded nothing |
includeFreeProviders | boolean | true | Keep gmail.com and similar addresses |
onlyWithContacts | boolean | false | Drop empty domains from the output |
timeoutSecs | integer | 20 | Per page timeout |
⬆️ Output
Table view
Results arrive as a Sites table you can sort and filter in the Console, with the domain, whether contact data was found, the addresses and phones, how many social platforms were seen, how many people were named and the page count lined up per domain.
JSON
A typical row:
{"domain": "monzo.com","hasContactData": true,"emails": [{ "email": "help@monzo.com", "local": "help", "domain": "monzo.com", "type": "role" }],"phones": [{ "phone": "+44 20 7946 0000", "source": "tel-link", "foundOn": "https://monzo.com/help" }],"socialPlatformCount": 5,"peopleCount": 0,"pagesCrawled": 5,"emailCount": 1}
Download it from the run as JSON, CSV or Excel, or read it straight from the API.
Three address types, not two
Most tools split addresses into personal and role. That forces a guess on every single word local part, and the guess is wrong often enough to matter.
| Type | Meaning |
|---|---|
role | A known department word: info, sales, support, careers |
personal | Name shaped, two parts around a separator: jane.smith, j.smith |
unclassified | A single word that could be either: greg, solar, heat |
solar@, heat@ and greg@ are the same shape. Two are departments and one is
a person, and nothing in the address says which. Calling a department mailbox a
named human is the expensive direction of that mistake, because the first line
of your outreach lands wrong. So those come back unclassified rather than
assigned.
Every address also carries isFreeProvider, and onSiteDomain, which is false
when the address sits on a different domain to the site. That is how you tell a
company's own address from its agency's.
How it reads a site
The homepage first, then the pages most likely to carry contact details, ranked:
contact, imprint, about, team, then support and legal. A shallow /contact
beats a deep /blog/2019/contact-us-update. Same registrable domain only, so a
link to a partner's site is never followed.
deepScan also tries the standard contact paths directly, including the imprint
and legal notice pages European sites are required to publish. Useful when a
site's menu is sparse or built in JavaScript.
JavaScript rendering, included
If a site's HTML carries no contact details at all, the page is loaded in a browser and read after its scripts have run. On a client rendered site that is the difference between a record and an empty row.
It runs only where the plain fetch found nothing, so ordinary sites never pay the time for it, and there is no separate charge when it does run.
What it deliberately does not do
Each of these is a refusal to guess, because in an outreach list a wrong answer costs more than a missing one.
- Names are never inferred from headings. Only schema.org
Personmarkup is read. Guessing from<h2>returns product names and page furniture presented as staff. - Bare digit runs are never read as phone numbers. A price, an order number and a company registration number are indistinguishable from a phone number to a loose pattern.
- Addresses are never inferred from text. Without schema.org markup, the address is reported absent.
It also does not: verify mailboxes by SMTP, use a paid enrichment database, read login gated pages, or follow links off the domain. It finds what a company chose to publish on its own website, and nothing beyond that.
💰 How much does it cost?
| Event | Price |
|---|---|
| Website scanned | $0.12 |
Charged once per domain that produced contact details. Not per page, not per address found, and not extra when a browser was needed. A run over 100 sites with a 60% hit rate costs $7.20, not $12.
No API keys, no accounts, no proxies.
🔌 Integrations
Send results straight to Google Sheets, Slack, Airtable, Zapier, Make or your own webhook using Apify integrations. You can also trigger a run whenever something happens in another tool.
🔗 Using Website Contact Scraper with the Apify API
curl -X POST "https://api.apify.com/v2/acts/spookyweb~website-contact-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"url": "monzo.com", "maxPagesPerDomain": 5}'
Or with the Apify client:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('spookyweb/website-contact-scraper').call({url: 'monzo.com',maxPagesPerDomain: 5,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Full detail is in the Apify API reference, and every run is also callable from the Python and JavaScript clients.
❓ FAQ
Which pages does it read?
The homepage first, then the pages most likely to carry contact details, ranked: contact, imprint, about, team, then support and legal. A shallow /contact beats a deep /blog/2019/contact-us-update. It stays on the same registrable domain, so a link to a partner's site is never followed. maxPagesPerDomain caps how many are read, and deepScan also probes the standard contact paths directly.
What does the JavaScript fallback do, and when does it trigger?
It loads the page in a browser and reads it after the scripts have run. It triggers only where the plain HTML fetch found no contact details at all, so ordinary sites never wait for it, and there is no separate charge when it does run. renderedWithBrowser on the record tells you whether it was needed.
Are free provider addresses like gmail included?
Yes by default, and every address carries isFreeProvider so you can filter them out yourself. Set includeFreeProviders to false to drop them during the run instead. Small businesses often publish a Gmail address as their only contact, so throwing them away by default would lose real data.
What are the three address types?
role is a known department word such as info, sales, support or careers. personal is name shaped, two parts around a separator such as jane.smith. unclassified is a single word that could be either, such as greg, solar or heat. Those stay unclassified rather than being guessed at, because calling a department mailbox a named human is the expensive direction of the mistake.
Are domains with nothing found still charged?
No. The charge only happens when a domain produced contact details. A run over 100 sites with a 60% hit rate costs $7.20 rather than $12. The empty domains still appear in the dataset with hasContactData: false, unless you set onlyWithContacts to drop them.
Can it find named people?
Only where the site publishes schema.org Person markup, in which case you get the name, job title and email where given. Names are never inferred from headings, because guessing from an <h2> returns product names and page furniture presented as staff.
⚖️ Is it legal to scrape website contact details?
This reads contact details a business has published on its own website so customers can reach it, which is business contact information. It reads only public pages and never anything behind a login.
Where a detail identifies a named person, UK GDPR applies and you are the data controller for what you do next. You need a lawful basis, typically legitimate interest for B2B contact, and you must honour opt outs. Apify's ethical scraping guide covers the wider picture.
👍 Your feedback
Found a bug, or want a field that is not here yet? Open an issue on the Actor's Issues tab. Requests that make the data more useful get built, and problems get fixed quickly.
🔎 You might also like
| Actor | What it does |
|---|---|
| Company Email Finder | Published company addresses, the naming pattern behind them and an MX check |
| Wayback Machine Scraper | Every archived capture of a URL, what changed between them, and the original bytes |
| UK Food Hygiene Ratings | Food hygiene ratings for every UK food business, straight from the FSA |