Website Email Scraper - Contact Details Extractor by Domain
Pricing
from $11.10 / 1,000 site with contacts founds
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
Maintained by CommunityActor 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 obfuscation — info [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.comand 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:
| Field | Example |
|---|---|
domain | www.debian.org |
ok | true — 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 / phoneLocalCount | 15 / 2 / 0 |
pagesScanned | 4 |
sourcePages | The exact URLs the contacts came from — so you can verify every one |
reason | Why 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 us →
ok: false,reason: "The site blocked us: blocked by the site (HTTP 403)", and you are not charged - robots.txt told us not to read it →
ok: 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 details →
ok: 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 raisemaxPagesPerSiteinstead 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 is | Seen on | Naive result |
|---|---|---|
A float in a code sample — 5.666666666666667 | python.org | +1 666 666 666 6667 |
The Fibonacci sequence — 55 89 144 233 | python.org | +1 558 914 4233 |
A copyright range — 2001-2026 | python.org | +1 200 120 26 |
A VAT number — DE811128135 | bosch.com, hetzner.com | +49 811 128 135 |
A ZIP+4 postal code — 10504-1722 | ibm.com | +1 105 041 722 |
A template placeholder — tel:111-111-1110 | docker.com | +1 111 111 1110 |
A fiction-reserved number — +1 415 555 0132 | anywhere | libphonenumber 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 page | What a default country code produces | Reality |
|---|---|---|
Brussels, Belgium Tel: 31-23-8080010 | +49 31 238 080 010 | The number is Dutch |
Budapest, Hungary Tel: 361-489-4600 | +49 361 489 4600 — and libphonenumber calls it a valid German landline | The number is Hungarian |
Auckland Tel: 0800 569 047 | +49 800 569 047 | New 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+65is lost.+49 (0)9831 505-0— the German(0)must be dropped when dialling internationally, and(+33) 01 45 66 22 22keeps its French trunk0. 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}
| Field | Default | Meaning |
|---|---|---|
websites | 3 sample sites | Domains or URLs. One row out per row in |
maxPagesPerSite | 5 | Pages read per site (1–20) |
respectRobots | true | Read and honour robots.txt |
timeoutSecs | 20 | Per-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.