Website Email Scraper - Contact Details Extractor by Domain avatar

Website Email Scraper - Contact Details Extractor by Domain

Pricing

from $11.10 / 1,000 site with contacts founds

Go to Apify Store
Website Email Scraper - Contact Details Extractor by Domain

Website Email Scraper - Contact Details Extractor by Domain

Finds emails, phone numbers and social profiles on any website. Decodes obfuscated addresses, refuses to invent ones that are not there, and tells you when a site blocked it instead of returning a blank row. Charges only when contacts are actually found.

Pricing

from $11.10 / 1,000 site with contacts founds

Rating

0.0

(0)

Developer

NeverEmpty

NeverEmpty

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Share

Give it a list of websites. Get back the emails, phone numbers and social profiles that are actually published on them — one row per site, in the order you gave them.

The problem with contact scrapers

Most of them will hand you an address that does not exist.

Here is a real example, produced by this Actor's own first version and then fixed. A page said:

Contact the team at dealer.com for details

A naive "decode obfuscated emails" step turns at into @ and produces team@dealer.com — an address nobody wrote, on a domain that has nothing to do with the site. Send an outreach campaign to a list built that way and you are mailing strangers.

This Actor only decodes what is unambiguously obfuscationinfo [at] acme [dot] com, @ entities, Cloudflare's data-cfemail protection — and leaves ordinary English prose alone.

It also drops, before you ever see them:

  • no-reply@, donotreply@, postmaster@, abuse@, bounce@
  • @example.com, @test.com, @yourdomain.com and other placeholders
  • image filenames that look like addresses (logo@2x.png)
  • Sentry keys and other long hex strings that happen to contain @

What you get

One row per website:

FieldExample
domainwww.debian.org
oktrue — false only when nothing could be read at all
emails["press@debian.org", "events@debian.org", ...]
phones["+4998315050"] — E.164, international form
phonesLocal["8887334281"] — numbers the site declared in a tel: link or schema.org without a country code. Kept exactly as written rather than guessed at, or dropped
socials{"linkedin": [...], "twitter": [...], "github": [...]}
emailCount / phoneCount / phoneLocalCount15 / 2 / 0
pagesScanned4
sourcePagesThe exact URLs the contacts came from — so you can verify every one
reasonWhy a row is empty, when it is

sourcePages matters. Every contact can be traced back to the page it came from. If you doubt a result, you can open the page and look.

It tells you which kind of empty it is

A blank row can mean three completely different things, and most scrapers give you the same blank for all of them:

  • The site blocked usok: false, reason: "The site blocked us: blocked by the site (HTTP 403)", and you are not charged
  • robots.txt told us not to read itok: false, and the reason names the paths its robots.txt disallows for every crawler. That is the site’s own rule, not a failure on our side - and you are not charged
  • The pages we opened had no contact detailsok: true, and you are not charged. The reason says how many pages were scanned and how many contact-looking pages were left unopened, so you can raise maxPagesPerSite instead of believing the site publishes nothing. It never claims "this site publishes no contact details" on the strength of five pages
  • We found contacts → the arrays are filled, and only then does it cost anything

It does not invent phone numbers

Phone validation is not something to hand-write. This Actor uses Google's libphonenumber — the same library your phone uses — to decide what is a phone number and what is not.

A digit sequence on a page is usually not a phone number. Measured against live pages, a naive regex turns all of these into "phone numbers":

What it really isSeen onNaive result
A float in a code sample — 5.666666666666667python.org+1 666 666 666 6667
The Fibonacci sequence — 55 89 144 233python.org+1 558 914 4233
A copyright range — 2001-2026python.org+1 200 120 26
A VAT number — DE811128135bosch.com, hetzner.com+49 811 128 135
A ZIP+4 postal code — 10504-1722ibm.com+1 105 041 722
A template placeholder — tel:111-111-1110docker.com+1 111 111 1110
A fiction-reserved number — +1 415 555 0132anywherelibphonenumber calls this valid; we still drop it

This Actor returns none of them.

It never guesses which country a number belongs to

There is no "default country code" option, and that is deliberate.

suse.com lists its offices with local numbers and no country codes. Stamp one country onto all of them and you get numbers that reach nobody:

On the pageWhat a default country code producesReality
Brussels, Belgium Tel: 31-23-8080010+49 31 238 080 010The number is Dutch
Budapest, Hungary Tel: 361-489-4600+49 361 489 4600and libphonenumber calls it a valid German landlineThe number is Hungarian
Auckland Tel: 0800 569 047+49 800 569 047New Zealand freephone

That last one is the dangerous kind: a fabricated number that passes validation, so the buyer cannot tell it is wrong.

So numbers are split into two columns:

  • phones — the number carried its own country code. Returned in E.164, ready to dial.
  • phonesLocal — the site published it without one. Returned exactly as written, never with a country guessed onto it.

Nothing is thrown away, and nothing is made up. Measured on 2026-08-27 across suse.com, bbva.com, huawei.com, ionos.de, chiyoda.lg.jp and others: 27 numbers in phones, every one of which libphonenumber confirms is valid, and 0 invented.

Two more things it gets right, each found on a real page:

  • + is a plus sign. ibm.com writes +65-6418-1000. Decode it and you have Singapore. Don't, and the +65 is lost.
  • +49 (0)9831 505-0 — the German (0) must be dropped when dialling internationally, and (+33) 01 45 66 22 22 keeps its French trunk 0. Both are handled.

It respects robots.txt

On by default. It reads each site's robots.txt, applies only the rules addressed to * or to this bot (not rules meant for someone else's crawler), honours * and $ patterns, and lets a more specific Allow override a broader Disallow — the standard interpretation.

Where it looks

The page you give it, plus the contact pages it finds from there: contact, about, company, support, and — importantly — impressum, kontakt, imprint, mentions-légales. In German-speaking countries an Impressum is legally required to carry a real address, phone number and email, which makes it the single richest page on a European site. Scrapers that only look for the English word "contact" walk straight past it.

If no such link exists, it tries the common paths directly instead of giving up.

The order matters more than the count. Contact pages are opened first, then Impressum and legal notices, then support and help, then about pages - not in whatever order they happen to appear in the HTML. Measured on python.org: the two pages that actually carry addresses (/about/help, /about/legal) are 5th and 7th in the page's own link order, so reading the first four in document order finds nothing and would report the site as publishing no contacts. With the ordering applied, both are reached within the default 5 pages. Links that differ only by a trailing slash (/about/apps and /about/apps/) are treated as one page, so they no longer take two slots.

Input

{
"websites": ["acme.com", "https://example-corp.de", "https://another.co.uk"],
"maxPagesPerSite": 5,
"respectRobots": true
}
FieldDefaultMeaning
websites3 sample sitesDomains or URLs. One row out per row in
maxPagesPerSite5Pages read per site (1–20)
respectRobotstrueRead and honour robots.txt
timeoutSecs20Per-page timeout

Pricing

You are charged per site where at least one contact was actually found (the current rate is shown on this page).

Sites that block you, and sites that simply publish no contact details, cost nothing. Compare that with per-row pricing that charges for blanks: the number that matters is the price per usable lead, not the price per row.

Typical uses

  • Building outreach lists from a set of company domains
  • Enriching a CRM with published contact details
  • Finding the right inbox for partnership, press or support enquiries
  • Compliance and due-diligence checks that need a company's published contact information

FAQ

Does it need a proxy? No. It reads ordinary company websites over plain HTTP.

Will it get blocked? Some sites block automated requests. When that happens you are told so explicitly — and not charged.

Can it find personal emails of employees? It reports what a site publishes. It does not guess address patterns, and it does not invent firstname.lastname@ combinations.

How fast is it? Roughly 5 pages per site. Three sites with five pages each took well under a minute when measured.

Other tools by NeverEmpty

Every NeverEmpty Actor follows the same rule: it never returns an empty result to mean two different things, and it only charges for rows that actually carry an answer.

  • domain-lookup - WHOIS, DNS, MX, SPF, DMARC and SSL expiry for a list of domains
  • ats-board-finder - a company domain to the ATS and board name it uses
  • seo-site-audit - technical SEO audit: meta, canonical, robots, headings

Support

Found a wrong address, or a site where a contact exists but was missed? Open an issue on the Issues tab with the domain. sourcePages in the output tells me where to look.