Website Contact Scraper avatar

Website Contact Scraper

Pricing

from $0.60 / 1,000 websites

Go to Apify Store
Website Contact Scraper

Website Contact Scraper

🏷️ From $0.60 / 1K | 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 $0.60 / 1,000 websites

Rating

0.0

(0)

Developer

丂卩ㄖㄖҜㄚ

丂卩ㄖㄖҜㄚ

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

2 days ago

Last modified

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

FromNotes
Email addressesmailto: links and page textSplit three ways, see below
Phone numberstel: links and page textEach says which it came from
Social profilesLinks anywhere on the page17 platforms, listed below
Postal addressschema.org markup onlyStreet, city, region, postcode, country
Opening hoursschema.org markup onlyAs published
Named peopleschema.org Person markup onlyName, 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:

FieldWhat it is
domainRegistrable domain, so www and subdomains collapse to one record
hasContactDataWhether anything was found. False is never charged
emailsEach with type, isFreeProvider, onSiteDomain, foundOn
emailCountWith personalEmailCount, roleEmailCount, unclassifiedEmailCount
phonesEach with source, tel-link or text, and foundOn
socialsProfile URLs keyed by platform
peopleName, job title, email where published
address, hoursFrom schema.org, with the page each came from
pagesCrawled, pagesVisitedExactly what was read to produce the record
queryUrl, startUrlThe URL asked for, and the one the scan began from after redirects
phoneCountHow many phone numbers were found
checkedAtWhen the scan ran, ISO 8601
errorSet only when no page could be read at all

A domain that returns nothing still produces a record with

hasContactData: false
, unless you switch that off. That is a real finding, not a failure: it tells you the company publishes no contact details.

💡 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?

  1. Click Try for free.
  2. Put a website into url, or up to 500 into urls.
  3. Set maxPagesPerDomain, and turn on deepScan if the sites have sparse menus.
  4. Click Start and wait for the run to finish.
  5. Download the results as JSON, CSV or Excel, or pull them from the API.

⬇️ Input

{
"url": "monzo.com",
"maxPagesPerDomain": 5
}
FieldTypeDefaultWhat it does
urlstringmonzo.comA single website, protocol optional
urlsarrayUp to 500 websites, charged per domain
maxPagesPerDomaininteger5Pages to crawl per domain, 1 to 20
deepScanbooleanfalseAlso probe standard contact paths
includeFreeProvidersbooleantrueKeep gmail.com and similar addresses
onlyWithContactsbooleanfalseDrop empty domains from the output
timeoutSecsinteger20Per 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.

TypeMeaning
roleA known department word: info, sales, support, careers
personalName shaped, two parts around a separator: jane.smith, j.smith
unclassifiedA 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.

What it does not do

It does not run a browser. Every page is read over plain HTTP, which is what keeps a run fast and cheap.

A browser fallback used to render pages whose HTML carried no contacts. It was removed because it was not earning its place: it fired on none of the last ten runs, and on the client rendered sites it existed for, the contact details are absent from the HTML and from the page's own JSON payloads alike, because those sites publish a contact form rather than an address. A browser cannot conjure an address that is not there.

So a site that publishes contact details only after its scripts run will come back empty here. It also comes back free, because a domain that produced nothing is never charged.

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 Person markup 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.

Run timeout

A run stops starting new work shortly before its own time limit and finishes cleanly with whatever it has, naming the sites it did not reach. The Actor's default is 3600 seconds, which is enough for the largest input it accepts. If you set a shorter limit in your own run settings, integration or API call, expect fewer results and a note in the run's status message saying so. You pay per delivered result rather than per minute, so a generous timeout costs you nothing.

⏱️ How long does a run take?

Measured on real runs, so you can tell a normal run from one that has stalled.

Input sizeTypical run time
1 site3 to 8 seconds
50 sitesabout 5 minutes

The first few seconds of any run are the container starting rather than the work. A run is never silently stuck: progress is logged as it goes, and if it runs out of time it stops early, keeps everything collected so far and says in the status message what was left.

💰 How much does it cost?

EventPrice
Website scanned$0.001
Actor start$0.00005 per run

Charged once per domain that produced contact details. Not per page and not per address found. A run over 100 sites with a 60% hit rate costs 60 at $0.001 plus one run start, $0.06005, not $0.10005.

Paid Apify plans pay less per website: $0.00085 on Bronze, $0.0007 on Silver, $0.0006 on Gold, $0.0005 on Platinum and $0.0004 on Diamond, which is 40 percent of the list price. The Apify listing always shows the current rates.

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.

Does it render JavaScript?

No. Every page is read over plain HTTP. A browser fallback was removed on 31 August 2026 after measurement showed it firing on none of the last ten runs and recovering nothing on the sites it was written for, which publish a contact form rather than an address. A site that only reveals contact details after its scripts run returns an empty record here, and an empty record is never charged for.

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 $0.06 rather than $0.10. 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.

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

ActorWhat it does
Company Email FinderPublished company addresses, the naming pattern behind them and an MX check
Wayback Machine ScraperEvery archived capture of a URL, what changed between them, and the original bytes
UK Food Hygiene RatingsFood hygiene ratings for every UK food business, straight from the FSA