Website Contact Scraper avatar

Website Contact Scraper

Pricing

from $72.00 / 1,000 website scanneds

Go to Apify Store
Website Contact Scraper

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 Community

Actor stats

0

Bookmarked

2

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
renderedWithBrowserWhether the browser fallback was needed
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
jsFallbackbooleantrueRender in a browser only when the HTML yielded nothing
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.

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

💰 How much does it cost?

EventPrice
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.

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