Website Contact Scraper
Pricing
from $4.00 / 1,000 contact details founds
Website Contact Scraper
Extract e-mail addresses, phone numbers and social profiles from any list of websites. Every address is MX-verified at no extra charge, and every value reports where on the site it was found.
Pricing
from $4.00 / 1,000 contact details founds
Rating
0.0
(0)
Developer
Mehmet
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
8 days ago
Last modified
Categories
Share
B2B lead generation from a list of websites. Give it the domains, get back one record per site with e-mail addresses, phone numbers and social profiles — and, for every address, where on the site it was found and whether the domain accepts mail.
Built for lead generation and CRM enrichment: feed it a prospect list, an export from your CRM, or the output of another scraper, and it returns outreach-ready contact data with the evidence attached.
No browser. No residential proxy. No login.
What makes this one different
1. E-mail verification is included, not an add-on. Every address found is checked against the domain's MX records, with the mail provider identified (Google Workspace, Microsoft 365, …), disposable domains flagged, and likely typos suggested. Other Actors on this shelf charge separately for verification. Here it is part of the base price.
2. Every value says where it came from.
Each address carries method and sourcePath:
mailto— taken from ahref="mailto:"link. The site declares this address.jsonld— taken from structured data (schema.org).cfemail— amailto:link that Cloudflare obfuscated, decoded back.text— found in the page body. Useful, but weaker evidence.guessed— not found anywhere on the site. See below.
In our measurement 87% of addresses and 79% of phone numbers came from structural markup, not body text. You can filter on that.
3. A 404 does not cost you a page.
The crawl budget counts pages that actually loaded, not requests attempted.
Trying /contact, /kontakt and /impressum on a site that has none is
nearly free — an empty 404 has no body — so a long path list is no longer a
penalty. In our measurement this alone recovered 5 more websites.
4. It finds the contact page instead of guessing its address.
Most scrapers try a fixed list of paths (/contact, /about, …). This one
reads the site's own navigation and follows the contact link it finds. In
our measurement 19 of 67 sites published their address on a path no
fixed list would have tried — /pages/contact, /outreach,
/company/contact, /en/contacts, /information/contact-us/, and plain
/contact/ with a trailing slash.
5. It does not invent precision.
A phone number written in national format ((555) 123-4567) is returned
exactly as written, with e164: null — because converting it without
knowing the country would be a guess. International numbers get a proper
E.164 value.
6. Share buttons are not social profiles.
When a website links to its own social accounts, those links are collected
and the noise around them is not: facebook.com/sharer,
twitter.com/intent/tweet, pinterest.com/pin/…, t.me/s/… and platform
navigation links are all excluded, so you get the company's actual profile
URL rather than the share widget in the page footer.
To be clear about what this is: the Actor reads your list of company websites. It recognises links pointing to twelve social networks and returns those URLs — it does not log into, crawl or scrape any social platform itself.
Measured coverage — real numbers, not claims
We measured on two samples, because the answer depends heavily on what kind of list you feed it.
Sample A — real businesses (100 sites). Restaurants, law firms, dental practices, accountants, estate agents, ad agencies and architects, taken from OpenStreetMap entries that publish a website (London and Berlin). This is what a lead list actually looks like.
Sample B — a random slice of the web (200 sites). Drawn systematically from the Tranco top-1M. Deliberately hard: it contains parked domains, adult and gambling sites, CDN hosts and things that serve no HTML at all.
| A: businesses | B: random web | |
|---|---|---|
| Sites that responded | 84% | 64% |
| Contact details found (of responding) | 82% | 64% |
| E-mail address found (of responding) | 71% | 53% |
| Phone number found (of responding) | 64% | 35% |
| With a pattern-guessed address included | 95% | 91% |
Blocked by robots.txt | 0 | 5 |
| Contact details found (of the whole sample) | 69% | 41% |
Both were scraped from Apify's datacenter IP with no proxy, 29–30 August 2026, at a platform cost of about $0.0006 per website.
We ran sample B four times while developing this. Three consecutive builds scored 76, 77 and 79 sites — that spread is run-to-run noise, so treat these numbers as ±2 rather than as decimals. The table reports the last run, not the best one.
One caveat on sample A, stated plainly: it is London- and Berlin-weighted and restaurant-heavy, so it represents Western-European small business rather than the whole world.
Why two denominators, stated plainly. We checked every unreachable domain against a neutral DNS resolver:
| Domain resolves, host is alive | 66% |
| Registered, but no web server at all | 29% |
| Genuinely gone | 5% |
So the sites we miss are mostly alive but not serving us — they block datacenter traffic or refuse the request — not dead. Measured against every domain that actually resolves, coverage is 46% of the whole sample.
A list you supply is normally made of sites you already care about and can reach, so the "responded" column is the one that matches real use.
One more honest limit: about 26% of the sites we could not reach from Apify were reachable from an ordinary connection — those sites block datacenter IP ranges. We measured whether Apify's datacenter proxy helps: across the same 200 sites it recovered 2. It does not help, because that pool is datacenter IP space too. Only residential proxies would, and they cost enough to defeat the point of an HTTP-only scraper. The proxy input stays available, but it is off by default and we are not going to tell you it fixes this.
Input
{"websites": ["example-company.com", "https://another-company.co.uk"],"maxPagesPerSite": 6,"verifyEmails": true,"includePhones": true,"includeSocials": true,"includeGuessedEmails": true,"onlyWithContact": false}
| Field | Default | What it does |
|---|---|---|
websites | — | URLs or bare domains. Each is reduced to its root. |
maxPagesPerSite | 6 | Contact-intent pages to try: /, /contact, /contact-us, /about, /about-us, /impressum, then /kontakt, /imprint, /legal, … |
verifyEmails | true | MX lookup for every address. No extra charge. |
includePhones | true | From tel: links, JSON-LD, and international-format text. |
includeSocials | true | Collect links the site publishes to its own social profiles (12 networks recognised). Share buttons excluded. |
includeGuessedEmails | true | When a site publishes nothing, add info@<domain> if the domain has mail servers. Marked method: guessed. |
onlyWithContact | false | Skip records for sites with no e-mail and no phone. |
maxSites | 0 | 0 means no limit. |
proxyConfiguration | none | Optional. |
maxPagesPerSite is a budget, not a list: the homepage is fetched
first, its contact links are followed next, and the fixed paths fill any
remaining budget. The default of 6 is measured, not guessed.
Output
One record per website — including sites where nothing was found, so your input list and output list line up.
{"input": "https://eff.org","url": "https://eff.org","domain": "eff.org","scrapedAt": "2026-08-28T18:33:23.916Z","emails": [{"email": "press@eff.org","method": "mailto","sourcePath": "/contact","isRole": false,"isFreemail": false,"verification": {"status": "deliverable_domain","mxFound": true,"provider": "Microsoft 365","isDisposable": false,"didYouMean": null}}],"phones": [{"e164": "+15102438020","raw": "+1-510-243-8020","method": "text","sourcePath": "/contact"}],"socials": [{ "network": "facebook", "url": "https://www.facebook.com/eff", "sourcePath": "/" }],"pagesScanned": 3,"requests": 4,"skipped": null,"hasContact": true,"hasGuessedEmail": false,"contactFormUrl": null}
contactFormUrl — when a site publishes no address at all
Some websites deliberately publish no address and only offer a form. For those, the Actor reports the page where the form lives:
{ "emails": [], "phones": [], "contactFormUrl": "https://example.com/contact" }
This is not a contact detail and is not billed as one. It does not set
hasContact. It exists because "nothing found" is an incomplete answer
when there is a way to reach the company — we just cannot turn it into an
address, because the site never published one.
Search boxes, newsletter sign-ups and login forms are excluded.
Guessed addresses — what they are, and what they are not
If a website publishes no address anywhere we looked, and its domain has
mail servers, the Actor adds info@<domain> with method: "guessed" and
sets hasGuessedEmail: true.
This address was not observed on the site. It is a naming-convention
guess that the domain can at least receive mail. It never sets
hasContact, it is billed under its own cheaper event, and you can turn it
off completely with includeGuessedEmails: false.
In our measurement it raised coverage from 64% to 91% of responding sites — but those extra 27 points are guesses, and the output says so on every single record.
verification.status
| Value | Meaning |
|---|---|
deliverable_domain | The domain accepts mail. The mailbox itself was not verified. |
no_mx | The domain has no mail server. Definitively undeliverable. |
disposable | Temporary / throwaway mail provider. |
invalid_syntax | Not a valid address. |
unknown | The MX lookup failed or was not run. |
What this Actor will never claim: that a specific mailbox exists. SMTP handshakes are not possible from this infrastructure (port 25 is closed), so mailbox-level verification cannot be performed — and we will not report a number we cannot measure. Verification here stops at the domain's mail server, and the field names say so.
skipped
| Value | Meaning |
|---|---|
null | The site was scraped normally. |
robots | robots.txt disallows crawling. Nothing was requested and nothing was charged. |
budget | The page or request budget ran out before a contact was found. |
unreachable | The homepage could not be fetched. |
Pricing
Pay per event, with volume tiers.
| Event | When it is charged |
|---|---|
site-scraped | Once per website, if at least one request was made. |
contact-found | Once per website, if at least one e-mail or phone number was found on the site. |
contact-guessed | Once per website, only when nothing was found and a pattern-guessed address was added instead. Cheaper than contact-found. |
A site that disallows crawling in robots.txt is never charged — no
request was made, so no work was done. A site that responds but has no
contact details is charged only the cheaper site-scraped event. A guess
is never billed as a find.
Frequently asked questions
How many of my websites will return contact details? On real business websites we measured 82% of the sites that responded (69% of the whole list). On a random slice of the web it drops to 64%, because a random slice contains parked domains, CDN hosts and sites that publish nothing. Your own list will behave like the first number, not the second.
A site I know publishes an e-mail returned nothing. Why? Three usual causes. The page renders its contact details with JavaScript, and this Actor is HTTP-only. The site blocks datacenter traffic — about a quarter of the sites we could not reach were reachable from an ordinary connection. Or the address is inside an image, which no text scraper can read.
Do you verify that the mailbox exists?
No, and no Actor running on this infrastructure can. Port 25 is closed here, so an SMTP
handshake is impossible. Verification stops at the domain's mail servers, and the status value
says exactly that: deliverable_domain, never deliverable.
What is a guessed address and can I switch it off?
When a site publishes nothing anywhere we looked, and its domain has mail servers, the Actor
adds info@<domain> marked method: "guessed". It was never observed on the site, it does not
set hasContact, and it is billed under a separate cheaper event. Set
includeGuessedEmails: false to turn it off entirely.
Is this Actor affiliated with the websites it scrapes?
No. It reads public pages only, respects robots.txt per path, and never signs in. A site that
disallows crawling is reported with skipped: robots and is not charged.
Can I use it to enrich a CRM export?
Yes — that is the main use. Feed the domain column, and match the output back on input, which
echoes exactly what you passed in. Sites with no contact details still return a row, so the two
lists line up.
Limits, stated plainly
- HTTP only. There is no headless browser, so a contact page that draws itself with JavaScript comes back empty. This is also exactly why the Actor is cheap and fast — it is a deliberate trade, not an oversight.
- Datacenter IPs are blocked by some sites. About a quarter of the sites we could not reach were reachable from an ordinary connection. We measured whether Apify's datacenter proxy fixes this: across 200 sites it recovered 2. It does not, because that pool is datacenter space too.
- The mailbox is never verified. Port 25 is closed on this
infrastructure, so verification stops at the domain's mail servers. The
status value says so:
deliverable_domain, neverdeliverable. - An address inside an image cannot be read. No text scraper can.
robots.txtis respected per path, not just per site — and a site that disallows crawling is reported withskipped: robotsand never charged.- Third-party addresses are filtered out (comment authors, review authors, affiliate links). An address is returned only when it belongs to the site, or when the site itself declares it in structural markup.
Notes
- Both
www.and the bare domain are tried, andhttpas a last resort. In our measurement this alone recovered 1 site in 10 that would otherwise have been reported unreachable.