Website Contact Scraper โ€” Bulk Email, Phone & Social Finder avatar

Website Contact Scraper โ€” Bulk Email, Phone & Social Finder

Pricing

$1.00 / 1,000 per website with contacts founds

Go to Apify Store
Website Contact Scraper โ€” Bulk Email, Phone & Social Finder

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

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

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:

statusMeaningBilled?Seen in the 60-domain run
okPages read, at least one contact channel returnedโœ…54
no_contactsPages read fine, the site genuinely publishes nothingโŒ free1
js_onlyHTTP 200 but a client-rendered shell โ€” no server-side contact surfaceโŒ free3
challenge_blockedAn anti-bot interstitial answered instead of the pageโŒ free2
dns_errorThe domain does not resolve (dead / typo / parked)โŒ free0
timeoutNo response inside the timeout after 3 rotated attemptsโŒ free0
http_errorA definitive 4xx from every host variant triedโŒ free0
invalid_inputThe input string is not parseable as a domainโŒ free0

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:

DomainRecoveredMechanism
viget.cominfo@viget.comJSON-LD published it as info(at)viget.com
zingermans.comservice@zingermans.comwritten as serv%69ce@zing%65%72%6d%61n%73.c%6fm
1800plumber.comlegal@, marketing@Cloudflare data-cfemail XOR encoding
notion.soteam@makenotion.com, privacy@makenotion.comCloudflare data-cfemail
ikea.comlegal.affairs@, franchise@, infringement.report@inter.ikea.comCloudflare 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

SignalFillNotes
At least one contact channel54 / 60 (90%)this is what gets billed
Same-domain email35 / 60 (58%)78 addresses total
Phone number34 / 60 (57%)primaryPhoneE164 is always strict E.164; 25 were toll-free
โ‰ฅ1 social profile50 / 60 (83%)3.6 platforms per domain on average
Contact page URL47 / 60 (78%)1 of them recovered via the sitemap.xml fallback
Postal address (any form)18 / 60 (30%)
Structured street address16 / 60 (27%)e.g. Cincinnati, OH 45202 ยท Falls Church, VA 22046
MX resolved58 / 60 (97%)

Per-field fill, all 103 fields

FillFields
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 samplefreemailEmails ยท 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

  1. GET https://<domain>/ โ€” and if the bare host does not serve, www. and http:// are tried too.
  2. Every same-domain link is classified into contact / imprint / privacy / about / support / locations. All six URLs are returned; the top maxPagesPerDomain - 1 are actually fetched, in that priority order (contact first, then imprint โ€” legally required to carry a mailbox in DE/AT/CH โ€” then privacy, which is where privacy@, legal@ and dpo@ usually live).
  3. If the homepage exposes no contact-ish link at all, sitemap.xml is fetched as a fallback. On this run that is how metalab.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.

EventPrice
โœ…contacts-scraped โ€” a domain that returned at least one contact channel$0.001
๐Ÿ†“a domain that returned nothing, or was blockedfree โ€” 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

FieldDefaultWhat it does
domainsโ€”Your list of websites. Empty runs a 3-domain demo so you can see the shape first.
maxPagesPerDomain3Total pages per site including the homepage. 1โ€“10.
useSitemapFallbacktrueFetch sitemap.xml when homepage nav exposes no contact link.
includeDnsSignalstrueResolve MX / SPF / DMARC and name the mail provider.
respectRobotsTxtfalseHonour each site's User-agent: * rules; skipped pages appear in notes.
requestConcurrency12Domains in flight. Higher causes footer misses through contention.
requestTimeoutSecs15Per request; each page gets up to 3 attempts.
maxDomains0Stop after N domains. 0 = no limit.
proxyConfigurationApify 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 UPSTREAM504 with 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.com and figma.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.com and precisiondoor.com โ†’ precisiondoor.net all resolved correctly on the run; link discovery re-anchors to where the site actually landed, and redirectedToDifferentDomain + effectiveDomain tell 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 ETIMEDOUT in ~260 ms while smtp.gmail.com:587 returned 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 same 250 2.1.5 OK as 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 via proxyConfiguration if 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-LD telephone are trusted; a plaintext number is only kept if it survives strict E.164/NANP validation. A naive regex on real pages emits 000000000, 1788400694899 and (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 โ€” use phonesE164[] if you need strict format. A number the site itself published in a tel: href or JSON-LD is kept even when it will not normalise: vanity numbers, national short codes and malformed tel: hrefs are real leads and dropping them would lose contacts. On the 60-domain reference run, 44 of 165 values in phones[] (27%, all from one domain's 100-entry list) are non-E.164 in exactly this way. phonesE164[], primaryPhoneE164 and phoneCountry are the strictly normalised fields; phoneE164Count tells 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

CheckResult
Real Apify run, 60 domains, build 0.1.2, 1024 MB, datacenter proxySUCCEEDED โ€” 60 rows, 205 s, $0.019
Rows with a contact channel54 / 60 (90%) โ€” billed; 6 free
Distinct fields emitted103
Prefill-only input ร— 3 (Apify's daily health test)SUCCEEDED, 3 rows each, 8โ€“11 s
Empty {} input ร— 2SUCCEEDED, 3 rows each, 9 s and 31 s, โ‰ค $0.0027
Parser validated offline against captured bytes6 real homepages + unit cases for every obfuscation form