Website Contact Scraper โ Bulk Email, Phone & Social Finder
Pricing
$1.00 / 1,000 per website with contacts founds
Website Contact Scraper โ Bulk Email, Phone & Social Finder
Turn a list of domains into one clean row per website: emails, phone numbers, 18 social profiles, postal address, contact-page URL and mail provider (MX/SPF/DMARC), read from the homepage plus its contact, about and imprint pages. You only pay for domains where a contact is actually found.
Pricing
$1.00 / 1,000 per website with contacts founds
Rating
0.0
(0)
Developer
Scrapers Delight
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
๐ Website Contact Scraper โ a domain list in, one clean contact row out
Give it a list of company websites. Get back one row per domain with the emails, phone numbers, social profiles, postal address and contact-page URL that site publishes โ plus who runs its mail (MX / SPF / DMARC).
No API key. No login. No cookies. No CAPTCHA solving. Plain HTTPS GETs against the homepage and its own contact / imprint / privacy / about pages.
You only pay for domains where a contact is actually found. Misses and blocked sites are still pushed to your dataset โ with a status telling you why โ and cost you nothing.
๐ฏ The three things that make this different
Every number below is measured, on a real 60-domain run on Apify (build 0.1.2, Apify datacenter proxy, 2026-09-03). Nothing here is estimated.
1. You do not pay for the misses
The honest reality of this job is that 58% of company websites publish an email and 42% route everything through a web form. Competitors bill per page scanned or per dataset item regardless of outcome โ so on a 1,000-domain list you pay for 1,000 and get contacts on ~600.
Here, the 6 of 60 domains that returned nothing were pushed free. Billed rows: 54.
2. "Blocked" is never reported as "no contacts found"
A zero row that was actually a wall is the single most expensive lie a contact scraper can tell you,
because you cross the domain off your list. Every row carries a status:
status | Meaning | Billed? | Seen in the 60-domain run |
|---|---|---|---|
ok | Pages read, at least one contact channel returned | โ | 54 |
no_contacts | Pages read fine, the site genuinely publishes nothing | โ free | 1 |
js_only | HTTP 200 but a client-rendered shell โ no server-side contact surface | โ free | 3 |
challenge_blocked | An anti-bot interstitial answered instead of the page | โ free | 2 |
dns_error | The domain does not resolve (dead / typo / parked) | โ free | 0 |
timeout | No response inside the timeout after 3 rotated attempts | โ free | 0 |
http_error | A definitive 4xx from every host variant tried | โ free | 0 |
invalid_input | The input string is not parseable as a domain | โ free | 0 |
blockReason spells it out in plain English, e.g.
anti-bot interstitial (HTTP 200, 3036 bytes) (hornbach.de) or
HTTP 200 but only 27 chars of readable text across 1 page(s) โ client-rendered shell (tartinebakery.com).
3. Deobfuscation + provenance on every address
Most contact scrapers run one email regex over the HTML. That misses every address a site deliberately hid โ and on this run, 5 of the 35 domains with an email (14%) were only found because of deobfuscation:
| Domain | Recovered | Mechanism |
|---|---|---|
viget.com | info@viget.com | JSON-LD published it as info(at)viget.com |
zingermans.com | service@zingermans.com | written as serv%69ce@zing%65%72%6d%61n%73.c%6fm |
1800plumber.com | legal@, marketing@ | Cloudflare data-cfemail XOR encoding |
notion.so | team@makenotion.com, privacy@makenotion.com | Cloudflare data-cfemail |
ikea.com | legal.affairs@, franchise@, infringement.report@inter.ikea.com | Cloudflare data-cfemail |
And every address ships with where it came from and how it was decoded:
"emailDetails": [{ "email": "hello@viget.com", "sources": ["mailto", "plaintext"],"foundOnUrls": ["https://www.viget.com/", "https://www.viget.com/contact"], "type": "role" },{ "email": "info@viget.com", "sources": ["at-dot-obfuscated"],"foundOnUrls": ["https://www.viget.com/"], "type": "role" },{ "email": "legal@viget.com", "sources": ["mailto", "plaintext"],"foundOnUrls": ["https://www.viget.com/privacy-policy"], "type": "role" }]
Discovery mechanisms across the run: plaintext 26 domains ยท mailto 22 ยท jsonld 6 ยท
cloudflare-cfemail 3 ยท percent-encoded 1 ยท at-dot-obfuscated 1.
๐ Measured results โ 60 domains, 2026-09-03
Run: 60 domains โ 60 rows, 205 s wall clock, 166 pages fetched (2.77 per domain), 26 MB received, $0.019 total Apify platform usage. Mixed sample: 15 US home-service SMBs, 10 design/dev agencies, 10 restaurants and food brands, 15 SaaS/enterprise, 10 European retail.
Contact channels
| Signal | Fill | Notes |
|---|---|---|
| At least one contact channel | 54 / 60 (90%) | this is what gets billed |
| Same-domain email | 35 / 60 (58%) | 78 addresses total |
| Phone number | 34 / 60 (57%) | primaryPhoneE164 is always strict E.164; 25 were toll-free |
| โฅ1 social profile | 50 / 60 (83%) | 3.6 platforms per domain on average |
| Contact page URL | 47 / 60 (78%) | 1 of them recovered via the sitemap.xml fallback |
| Postal address (any form) | 18 / 60 (30%) | |
| Structured street address | 16 / 60 (27%) | e.g. Cincinnati, OH 45202 ยท Falls Church, VA 22046 |
| MX resolved | 58 / 60 (97%) |
Per-field fill, all 103 fields
| Fill | Fields |
|---|---|
| 100% | input_raw ยท domain ยท effectiveDomain ยท finalUrl ยท httpStatus ยท responseMs ยท fetchedAt ยท pagesFetchedCount ยท proxyGroupUsed ยท status ยท faviconUrl ยท emailCount ยท phoneCount ยท socialPlatformCount ยท contactFormCount ยท mxCount ยท placeholderEmailsFiltered |
| 97โ98% | hasSpf ยท spfRecord ยท hasDmarc ยท dmarcRecord ยท dmarcPolicy ยท pagesFetched ยท mxRecords ยท mailProvider ยท domainAcceptsMail |
| 92โ93% | companyDescription ยท language ยท companyName |
| 77โ83% | socialHandles ยท aboutPageUrl ยท contactPageUrl ยท schemaTypes |
| 63โ70% | facebook ยท instagram ยท privacyPageUrl ยท twitter |
| 55โ58% | emails ยท primaryEmail ยท primaryEmailType ยท emailSources ยท emailTypes ยท emailFoundOnUrl ยท emailDetails ยท phones ยท primaryPhone ยท primaryPhoneE164 ยท phoneSources ยท phoneDetails ยท phoneCountry ยท logoUrl ยท roleEmails ยท youtube |
| 45โ52% | hasContactForm ยท contactFormUrl ยท contactFormFields ยท supportPageUrl ยท linkedinCompany ยท locationsPageUrl ยท cms |
| 23โ42% | tollFree ยท addressRaw ยท addressLocality ยท notes ยท streetAddress ยท postalCode ยท personalEmails ยท addressRegion ยท contactPoints |
| 7โ17% | legalName ยท addressCountry ยท offDomainEmails ยท tiktok ยท pinterest ยท vimeo ยท availableLanguage ยท blockReason ยท deobfuscatedEmails ยท github ยท imprintPageUrl ยท redirectedToDifferentDomain ยท alternateName ยท additionalAddresses ยท areaServed |
| 2โ5% | liveChatVendor ยท slogan ยท openingHours ยท reddit ยท trustpilot ยท faxNumber ยท googleMapsEmbedUrl ยท latitude ยท longitude ยท linkedinPeople ยท bookingUrl |
| 0% on this sample | freemailEmails ยท yelp ยท whatsapp ยท telegram ยท threads ยท discord ยท crunchbase ยท glassdoor |
The 0% fields are not broken โ they are long-tail signals that a sample of US/EU corporates and
franchises simply does not carry. yelp, whatsapp and freemailEmails are SMB/local signals;
crunchbase and glassdoor are startup/recruiting signals. They are extracted the moment a site
publishes them, and are listed here at their honest observed rate rather than hidden.
๐ฆ What you get on every row
Provenance and transport
input_raw ยท domain ยท effectiveDomain ยท finalUrl ยท httpStatus ยท
redirectedToDifferentDomain ยท responseMs ยท fetchedAt ยท pagesFetched[] ยท
pagesFetchedCount ยท proxyGroupUsed ยท status ยท blockReason ยท notes[]
Company identity
companyName ยท legalName ยท alternateName ยท slogan ยท companyDescription ยท logoUrl ยท
faviconUrl ยท language ยท schemaTypes[] ยท cms
Emails
emails[] (ranked best-first) ยท primaryEmail ยท primaryEmailType ยท emailSources[] ยท
emailTypes[] ยท emailFoundOnUrl ยท emailDetails[] ยท roleEmails[] ยท personalEmails[] ยท
freemailEmails[] ยท offDomainEmails[] ยท deobfuscatedEmails[] ยท placeholderEmailsFiltered ยท
emailCount
Phones
phones[] ยท phonesE164[] ยท primaryPhone ยท primaryPhoneE164 ยท phoneSources[] ยท phoneDetails[] ยท
phoneCountry ยท tollFree ยท faxNumber ยท phoneCount
Postal address and place
streetAddress ยท addressLocality ยท addressRegion ยท postalCode ยท addressCountry ยท
addressRaw ยท additionalAddresses[] ยท latitude ยท longitude ยท googleMapsEmbedUrl ยท
openingHours[] ยท areaServed[] ยท availableLanguage[] ยท contactPoints[]
Social profiles โ 18 platforms, one column each
linkedinCompany ยท facebook ยท instagram ยท twitter ยท youtube ยท tiktok ยท pinterest ยท
yelp ยท whatsapp ยท telegram ยท github ยท reddit ยท threads ยท discord ยท vimeo ยท
crunchbase ยท trustpilot ยท glassdoor โ plus linkedinPeople[], socialHandles,
socialPlatformCount
Pages and conversion surfaces
contactPageUrl ยท aboutPageUrl ยท imprintPageUrl ยท privacyPageUrl ยท supportPageUrl ยท
locationsPageUrl ยท hasContactForm ยท contactFormCount ยท contactFormUrl ยท
contactFormFields[] ยท bookingUrl ยท liveChatVendor
Domain mail signals
mxRecords[] ยท mxCount ยท mailProvider ยท hasSpf ยท spfRecord ยท hasDmarc ยท dmarcRecord ยท
dmarcPolicy ยท domainAcceptsMail
๐ How to use it
Paste your domains and run. Anything goes in the list โ acme.com, www.acme.com,
https://acme.com/some/page โ duplicates are collapsed by host.
{"domains": ["clearleft.com", "viget.com", "blueskypest.com"],"maxPagesPerDomain": 3}
A real row from the run above:
{"domain": "viget.com","status": "ok","companyName": "Viget","primaryEmail": "hello@viget.com","primaryEmailType": "role","emails": ["hello@viget.com", "info@viget.com", "careers@viget.com","legal@viget.com", "business@viget.com", "publicity@viget.com"],"roleEmails": ["hello@viget.com", "info@viget.com", "careers@viget.com", "legal@viget.com"],"deobfuscatedEmails": ["info@viget.com"],"primaryPhone": "+17038910670","phoneSources": ["tel-href", "jsonld-contactPoint", "plaintext"],"streetAddress": "4th Floor, 105 W Broad St","addressLocality": "Falls Church", "addressRegion": "VA", "postalCode": "22046","contactPageUrl": "https://www.viget.com/contact/","mailProvider": "Google Workspace", "hasSpf": true, "dmarcPolicy": "quarantine","pagesFetched": ["https://www.viget.com/", "https://www.viget.com/contact","https://www.viget.com/privacy-policy"]}
How the pages are chosen
GET https://<domain>/โ and if the bare host does not serve,www.andhttp://are tried too.- Every same-domain link is classified into contact / imprint / privacy / about / support /
locations. All six URLs are returned; the top
maxPagesPerDomain - 1are actually fetched, in that priority order (contact first, then imprint โ legally required to carry a mailbox in DE/AT/CH โ then privacy, which is whereprivacy@,legal@anddpo@usually live). - If the homepage exposes no contact-ish link at all,
sitemap.xmlis fetched as a fallback. On this run that is howmetalab.com's contact page was found.
maxPagesPerDomain counts the homepage. The price is per domain, not per page โ raising it
costs you nothing extra.
๐ฐ Pricing
$0.001 per website where a contact is found. That is $1 per 1,000 domains โ and only for the ones that produced something.
| Event | Price | |
|---|---|---|
| โ | contacts-scraped โ a domain that returned at least one contact channel | $0.001 |
| ๐ | a domain that returned nothing, or was blocked | free โ pushed to the dataset with a status |
Charging is budget-aware: rows are delivered and billed in the same atomic call, so a run that hits your charge cap stops delivering instead of handing you unpaid rows.
On the measured run, 54 of 60 domains billed โ $0.054 for 60 domains, or about $0.90 per 1,000 domains at the observed 90% hit rate.
โ๏ธ Input reference
| Field | Default | What it does |
|---|---|---|
domains | โ | Your list of websites. Empty runs a 3-domain demo so you can see the shape first. |
maxPagesPerDomain | 3 | Total pages per site including the homepage. 1โ10. |
useSitemapFallback | true | Fetch sitemap.xml when homepage nav exposes no contact link. |
includeDnsSignals | true | Resolve MX / SPF / DMARC and name the mail provider. |
respectRobotsTxt | false | Honour each site's User-agent: * rules; skipped pages appear in notes. |
requestConcurrency | 12 | Domains in flight. Higher causes footer misses through contention. |
requestTimeoutSecs | 15 | Per request; each page gets up to 3 attempts. |
maxDomains | 0 | Stop after N domains. 0 = no limit. |
proxyConfiguration | Apify auto (datacenter) | See the proxy note below before switching to residential. |
๐ต๏ธ Reliability โ what actually goes wrong, and what we do about it
Transport failure is the #1 risk on this lane, not the target sites. Three things are built in:
- Every retry rotates the proxy session. In pre-build testing, Apify RESIDENTIAL succeeded on
only 7 of 12 domains on the first attempt โ the other 5 returned a proxy-side
590 UPSTREAM504with 0 bytes, and all 5 returned HTTP 200 on a retry with a fresh session. Zero bytes is broken transport, never a verdict. Without this the actor would report a ~40% "no contacts" rate that is pure plumbing. - The HTTP version is part of the retry ladder. Measured 2026-09-03:
segment.comandfigma.com/contact/answer HTTP/1.1 with a 400 (524 B / 122 B) and the identical request over HTTP/2 with a 200 (610 KB / 1.55 MB). That 400 described the client, not the site. Attempts alternate protocols, which is why both domains return full contact rows. - Redirects to a different domain are followed properly.
notion.so โ notion.com,segment.com โ twilio.com,bmw.com โ bmwusa.comandprecisiondoor.com โ precisiondoor.netall resolved correctly on the run; link discovery re-anchors to where the site actually landed, andredirectedToDifferentDomain+effectiveDomaintell you it happened.
A per-domain failure is a row, never a run failure. Even a run where every domain is blocked exits SUCCEEDED with an explanatory status message, so your schedule never shows a red run for something a target did.
โ ๏ธ Proxy note โ do not flip this to residential by reflex
The default is Apify's automatic (datacenter) pool, which returned HTTP 200 on 20/20 pre-build test domains and on 58 of the 60 domains in the live run (the 2 misses were anti-bot walls that answer any client the same way, not datacenter blocks). Residential is available as an opt-in for targets that block datacenter IPs, but it is slower, less reliable on the first attempt, and moves ~433 KB of billable residential traffic per domain (measured: 26 MB across 60 domains). At typical residential rates that costs several times what the row itself costs you. Use it deliberately, on a short list of hard targets โ not as a default.
โ๏ธ Honest limits โ what this actor does NOT do
- No SMTP / mailbox verification, and we will not pretend otherwise. Outbound TCP port 25 is
blocked on the Apify platform (verified from a live run: MX hosts on :25 gave
ETIMEDOUTin ~260 ms whilesmtp.gmail.com:587returned a live 220 banner from the same run). Even where port 25 is reachable, Google Workspace and Microsoft 365 accept every RCPT โ a garbage mailbox gets the same250 2.1.5 OKas a real one, and those two providers were 48 of the 58 MX-resolved domains on this run. What ships instead is domain-level signal: MX, SPF, DMARC and the provider name. That is deliverability context, not proof a specific mailbox exists. - 42% of company websites publish no email. Large brands route everything through a web form. On
those rows you still get
hasContactForm,contactFormUrl,contactFormFields[], the phone, the socials and the address โ and you are not charged if there is nothing at all. - Client-rendered sites return
js_only, not contacts. This actor does not run a browser. 3 of 60 domains (tartinebakery.com,work-co.com,adidas.de) served an HTTP 200 shell with under 35 characters of readable text. They are honestly flagged, not silently zeroed. - Some sites are behind an anti-bot wall. 2 of 60 (
hornbach.deโ a Fastly interstitial served as HTTP 200 in 3,036 bytes;decathlon.frโ HTTP 403 in 5,919 bytes). No CAPTCHA solving is performed. Bring your own residential proxies viaproxyConfigurationif a specific target matters. - No person-level discovery. This returns the company's contact channels. It does not hunt for
a named decision-maker, a job title, or a personal address pattern, and it does not guess
first.last@.linkedinPeople[]only carries profile links the site itself published. - Postal address fill is 30%, not 90%. Most websites do not publish a street address at all.
- Phone numbers are validated, not scraped naively.
tel:links and JSON-LDtelephoneare trusted; a plaintext number is only kept if it survives strict E.164/NANP validation. A naive regex on real pages emits000000000,1788400694899and(615) 675-2312801โ those are dropped here, which is why the phone fill rate is 57% and not a fake 90%. phones[]is not all E.164 โ usephonesE164[]if you need strict format. A number the site itself published in atel:href or JSON-LD is kept even when it will not normalise: vanity numbers, national short codes and malformedtel:hrefs are real leads and dropping them would lose contacts. On the 60-domain reference run, 44 of 165 values inphones[](27%, all from one domain's 100-entry list) are non-E.164 in exactly this way.phonesE164[],primaryPhoneE164andphoneCountryare the strictly normalised fields;phoneE164Counttells you how many survived.
๐ค robots.txt
This actor visits arbitrary user-supplied domains, so there is no single robots.txt to quote โ
the rules differ for every target on your list and cannot be pre-cleared. A respectRobotsTxt
toggle ships in the input schema (default off, matching how most contact-enrichment tooling behaves);
with it on, each site's User-agent: * Disallow rules are parsed and honoured, and any page skipped
because of them is named in that row's notes. Everything collected is public company contact
information published on the site's own pages โ no login, no cookies, no CAPTCHA solving, no
paywalled or member-only content. You are responsible for how you use the output: check applicable
marketing and data-protection law (GDPR, CAN-SPAM, CASL) before contacting anyone.
โ FAQ
How many pages does it fetch per domain?
2.77 on average, measured (166 pages for 60 domains) with the default maxPagesPerDomain: 3.
Do I get charged for a domain that returns nothing?
No. Only contacts-scraped bills, and it only fires when at least one contact channel came back.
The row still lands in your dataset with a status and a blockReason.
Why is primaryEmail sometimes a privacy@ or legal@ address?
Because that is often the only real mailbox a large company publishes. Addresses are ranked
same-domain first, then role over personal over freemail, with noreply@ pushed to the bottom.
Every alternative is in emails[], and roleEmails[] / personalEmails[] split them for you.
What is offDomainEmails?
Third-party addresses found on the page โ a parent company's privacy inbox, a franchise operator, an
agency. On the run: privacy@authoritybrandsllc.com on a franchise site, privacy@37signals.com on
basecamp.com. They are separated so they never become your primaryEmail, but they are still
returned because they are often the correct escalation contact.
Can it find a specific person's email? No โ that is deliberately out of scope. This returns company-level contact channels only.
Does it work outside the US?
Yes. Imprint (/impressum) pages are a first-class hop and phone numbers are normalised to E.164
where they parse
from any country code. On the run, zalando.de returned datenschutz@, legalnotice@ and
authorities-dsa@zalando.de plus its imprint URL, and ikea.com returned three Cloudflare-encoded
inter.ikea.com addresses.
What happens with an empty input? It runs a small built-in 3-domain demo instead of failing, so a first click always shows you the output shape.
๐ฌ Validation record
| Check | Result |
|---|---|
| Real Apify run, 60 domains, build 0.1.2, 1024 MB, datacenter proxy | SUCCEEDED โ 60 rows, 205 s, $0.019 |
| Rows with a contact channel | 54 / 60 (90%) โ billed; 6 free |
| Distinct fields emitted | 103 |
| Prefill-only input ร 3 (Apify's daily health test) | SUCCEEDED, 3 rows each, 8โ11 s |
Empty {} input ร 2 | SUCCEEDED, 3 rows each, 9 s and 31 s, โค $0.0027 |
| Parser validated offline against captured bytes | 6 real homepages + unit cases for every obfuscation form |