Website Contact & Email Crawler avatar

Website Contact & Email Crawler

Pricing

$19.99/month + usage

Go to Apify Store
Website Contact & Email Crawler

Website Contact & Email Crawler

πŸ“žπŸ” Extract phone numbers, emails, contact details & social media links from any website in seconds. ⚑️ Scrapes tel, mailto, WhatsApp, LinkedIn, X/Twitter, Instagram & more; dedupes & validates. πŸ“Š Export CSV/JSON, API-ready. πŸš€ Ideal for lead gen, sales & CRM enrichment.

Pricing

$19.99/month + usage

Rating

0.0

(0)

Developer

Scrapio

Scrapio

Maintained by Community

Actor stats

0

Bookmarked

31

Total users

4

Monthly active users

2 days ago

Last modified

Share

Extract Phone Numbers, Contacts & Socials from Any Website πŸ“‡

Give this actor a list of website URLs. It visits each site, finds the pages that actually carry contact details (/contact, /contact-us, /about, /impressum, /team, /support, footer links, sitemap.xml), and returns every phone number, email address and social-media profile it can find β€” with the exact page each item was found on.

It is built for lead generation: one clean row per website, ready to export to CSV/Excel and drop straight into a CRM.

A second, legacy keyword-search mode is preserved for existing users: it searches public search-engine results across 80+ platforms (Instagram, LinkedIn, Yelp, TikTok, Zillow…) for profiles that publish a phone number.


Two modes 🧭

ModeInputOutput
🌐 Website crawl (default)startUrls β€” any website URLsPhones + emails + social profiles per website
πŸ” Keyword search (legacy)keywords + platform + countryPhone numbers found in public search results

Pick the mode with the mode input. If you leave mode out entirely, the actor infers it: URLs present β†’ website crawl, keywords present β†’ keyword search. Existing tasks keep working unchanged.


What the website crawler actually does πŸ”¬

  1. Fetches the homepage over plain HTTP (fast and cheap).
  2. Scores every internal link by how likely it is to hold contact data β€” contact, contact-us, kontakt, contacto, contatti, impressum, imprint, mentions-legales, about, team, support, locations, find-us, legal, … β€” and visits the best ones first, breadth-first, up to your crawl depth and max pages per site.
  3. Peeks at /sitemap.xml (one extra request, optional) to catch contact pages that aren't linked from the homepage.
  4. Extracts contacts from every page it visits.
  5. Falls back to a headless Chromium browser only when a site returned no phone and no email over plain HTTP and the HTML looks like an empty JavaScript shell (React/Next/Vue/Angular). Browsers are slow and expensive, so they are never used for pages that already produced data.

Phone numbers ☎️

  • tel: / callto: links, JSON-LD telephone fields, WhatsApp wa.me links, and visible page text.
  • Normalised to international E.164 (+441614960123). When the country can't be established the number is returned exactly as the site printed it, so nothing useful is lost.
  • Country is resolved from your phoneCountry setting, or auto-detected per site from the domain's country code (.co.uk β†’ +44, .de β†’ +49, .in β†’ +91 …).
  • Per-country length validation rejects garbage that merely looks like a phone number: NANP (+1) must be exactly 11 digits, India (+91) exactly 12, plus fixed-length checks for +7, +33, +34, +61, +65, +48, +351, +30, +420, +421, +386, +372.
  • Bare digit runs in body text must earn a confidence score before they are reported (explicit + prefix, an adjacent phone label like Tel: / Call us / (fax), human digit grouping, or being on a contact page). VAT numbers, company registration numbers, order/invoice numbers, prices, ZIP+4, dates and year ranges are filtered out.

Email addresses βœ‰οΈ

  • mailto: links and plain page text.
  • Cloudflare email protection (data-cfemail="…" / /cdn-cgi/l/email-protection#…) is decoded properly β€” the hex payload's first byte is the XOR key for every following byte.
  • Text obfuscation β€” name [at] domain [dot] com, (at), {at}, [.], @, ., and bare info at example dot com.
  • Filtered: asset filenames (logo@2x.png), placeholder addresses (you@example.com), Sentry/Wix telemetry keys, and addresses that ran into the following word (info@acme.comwho we are).

Social media profiles πŸ”—

Facebook, Instagram, X and Twitter, LinkedIn (company and personal /in/), YouTube (@handle, /channel/, /c/, /user/), TikTok, Threads (threads.net + threads.com), Pinterest (incl. country domains), GitHub, Telegram (t.me), WhatsApp (wa.me, api.whatsapp.com, chat.whatsapp.com), Discord, Snapchat, Reddit, Twitch, Vimeo, SoundCloud, Spotify, Bluesky, Medium, Behance, Dribbble, Xing, VK, Yelp, Trustpilot.

Found in anchors, raw markup, page text, og:/twitter: meta tags and JSON-LD sameAs. Each is normalised to handle + canonical URL.

Share/intent links are filtered out. twitter.com/intent/tweet?url=…, facebook.com/sharer/sharer.php?u=…, linkedin.com/sharing/share-offsite/?url=…, pinterest.com/pin/create/button/?url=… and friends are "share this page" widgets, not the site's own profile β€” a classic false positive that this actor does not make. Platform infrastructure hosts (connect.facebook.net, platform.twitter.com, …) and reserved paths (/login, /help, /hashtag, /explore, /p/, /watch) are rejected too.

Bonus 🎁

JSON-LD Organization / LocalBusiness data also yields the company name and postal address when the site publishes it.


Input Parameters 🧩

Website-crawl mode

FieldTypeDefaultDescription
modestringwebsitewebsite or search
startUrlsarrayβ€”The websites to crawl. Standard Apify URL list β€” in the Console just paste one URL per row (bulk paste, link lists and Google Sheets all work); over the API it takes {"url": "…"} objects
websiteUrlsarrayβ€”Hidden API-only alias that takes plain URL strings (["https://example.com"]) for callers who prefer them. Merged with startUrls. Not shown in the Console form
maxPagesPerSiteinteger12Page budget per website (1–200)
maxCrawlDepthinteger2Link hops from the start URL (0–5)
phoneCountrystringAuto-detectPin a country for phone normalisation, or auto-detect per site
renderJavaScriptstringautoauto / always / never headless-browser rendering
useSitemapbooleantrueProbe /sitemap.xml for contact pages
includeSubdomainsbooleantrueFollow links to other subdomains of the same site
concurrencyinteger5Websites crawled in parallel
requestDelaySecsinteger0Politeness delay between page requests
maxBrowserPagesPerSiteinteger2Cap on headless-browser renders per site
proxyConfigurationobjectApify ProxyNetwork routing (see below)

Keyword-search mode (legacy)

FieldTypeDefaultDescription
keywordsarrayβ€”Keywords / usernames to search for
platformstringInstagramOne of 80+ supported platforms
countrystringUnited Kingdom (+44)Dial code used to build and filter the search
maxPhoneNumbersinteger10Phone numbers to collect per keyword
enginestringlegacyProcessing engine

πŸ“¦ Example input β€” website mode

{
"mode": "website",
"startUrls": [
{ "url": "https://www.zingermans.com" },
{ "url": "https://www.pizzapilgrims.co.uk" }
],
"maxPagesPerSite": 12,
"maxCrawlDepth": 2,
"phoneCountry": "Auto-detect (recommended)",
"renderJavaScript": "auto",
"useSitemap": true
}

πŸ“¦ Example input β€” legacy keyword-search mode

{
"mode": "search",
"keywords": ["marketing"],
"platform": "Instagram",
"country": "United Kingdom (+44)",
"maxPhoneNumbers": 10,
"engine": "legacy"
}

Output Format πŸ“€

Website mode produces one row per website (not per page). Every extracted item records the page it came from in foundOn.

{
"mode": "website",
"inputUrl": "https://www.zingermans.com",
"finalUrl": "https://www.zingermans.com",
"domain": "zingermans.com",
"companyName": "Zingerman's",
"siteTitle": "Zingerman's: Online Shopping for Food and Gifts",
"phones": ["+18662606169", "+18886368162", "+17344362006"],
"emails": ["service@zingermans.com", "bizgifts@zingermans.com"],
"socialLinks": [
"https://facebook.com/Zingermans",
"https://instagram.com/zingermansmailorder",
"https://twitter.com/zingermans"
],
"phoneDetails": [
{
"phone": "+18662606169",
"e164": "+18662606169",
"raw": "+18662606169",
"source": "tel_link",
"foundOn": "https://www.zingermans.com"
}
],
"emailDetails": [
{
"email": "service@zingermans.com",
"source": "mailto",
"foundOn": "https://www.zingermans.com"
}
],
"socialDetails": [
{
"platform": "facebook",
"handle": "Zingermans",
"url": "https://facebook.com/Zingermans",
"foundOn": "https://www.zingermans.com"
}
],
"socialsByPlatform": {
"facebook": "https://facebook.com/Zingermans",
"instagram": "https://instagram.com/zingermansmailorder"
},
"addresses": ["3711 Plaza Drive, Ann Arbor, MI, 48108"],
"phoneCount": 3,
"emailCount": 2,
"socialCount": 3,
"hasContacts": true,
"pagesCrawled": 8,
"pages": [
{ "url": "https://www.zingermans.com", "status": 200, "depth": 0, "phones": 2, "emails": 2, "socials": 4, "jsShell": false }
],
"dialCodeUsed": "",
"browserRenderUsed": false,
"errors": [],
"status": "ok"
}

Output fields

FieldDescription
inputUrl / finalUrlThe URL you supplied / the URL after redirects
domainRegistrable domain of the site
companyNameOrganisation name from JSON-LD, else the page title
phones / emails / socialLinksFlat de-duplicated lists β€” the columns you export
phoneDetailsPer phone: phone, e164, raw (as printed), source (tel_link / json_ld / whatsapp / text), foundOn
emailDetailsPer email: email, source (mailto / cloudflare / obfuscated / text / json_ld), foundOn
socialDetailsPer profile: platform, handle, url, foundOn
socialsByPlatformQuick map, one best URL per platform
addressesPostal addresses from JSON-LD
pagesEvery page visited, with per-page hit counts and whether it was a JS shell
browserRenderUsedWhether the headless-browser fallback was needed
hasContacts / status / errorsRow-level diagnostics β€” failed sites still produce a row

Legacy keyword-search mode keeps its original row shape unchanged: platform, keyword, title, description, url, phone_number, country, dial_code.


Proxy & reliability πŸ›‘οΈ

  • Website mode routes through Apify Proxy (datacenter) by default and automatically retries a failed or blocked request directly, which recovers a large share of sites that block datacenter IPs. Choose the RESIDENTIAL group in proxyConfiguration for stubborn WAF-protected sites.
  • Keyword-search mode always forces Apify's GOOGLE_SERP group β€” search requests fail without it.
  • Bad TLS certificates, exotic character encodings, http:// fallbacks and www. variants are all handled; a single broken page never kills the run.

Use Cases 🎯

  • Lead generation β€” turn a list of company domains into a contact sheet with phone, email and social handles.
  • Sales enrichment β€” fill in missing contact fields for accounts already in your CRM.
  • Agency prospecting β€” find which businesses have (or lack) an Instagram/TikTok presence, plus how to reach them.
  • Market research β€” map contactability and social presence across a whole industry vertical.
  • Data hygiene β€” re-verify that the phone numbers and emails you hold are still published on the site.

Limitations & honest notes ⚠️

  • The actor only reads publicly published contact data. It does not solve CAPTCHAs, log in, or read anything behind a form.
  • Sites protected by an aggressive WAF may return 403 to both the proxy and the direct retry. Those rows come back with status: "ok"/"failed" and the error recorded in errors β€” the run does not silently pretend it found nothing.
  • Phone numbers written locally on a .com domain with no country signal anywhere on the page are returned in the site's own national format rather than E.164 (except unmistakable US/Canada (NNN) NNN-NNNN shapes, which are converted to +1).
  • Contact details rendered only inside a <canvas>, an image, or behind an interactive widget click are not extracted.
  • The headless-browser fallback deliberately runs on at most a couple of pages per site; set renderJavaScript: "always" if you need every page rendered.
  • robots.txt is not consulted. Use the actor responsibly and in line with the target site's terms.

Extracting publicly available business contact information is generally lawful, but you remain responsible for how you use it:

  • Comply with GDPR, CAN-SPAM, PECR and any local marketing/privacy rules that apply to you.
  • Prefer role addresses (info@, sales@) over personal ones for cold outreach, and honour opt-outs.
  • Do not use the output for spam, harassment, or any unsolicited contact that breaks local law.
  • Respect each website's terms of service.

FAQ ❓

Does it really visit the website, or just search engines? Website mode genuinely fetches the site: the homepage plus the contact-bearing pages it discovers. Search-engine results are only used by the optional legacy keyword mode.

Do I get emails and social links, or just phone numbers? All three. Phones, emails and social profiles, each with the page it was found on.

Can it read emails hidden by Cloudflare? Yes β€” data-cfemail / /cdn-cgi/l/email-protection payloads are decoded, as are [at] / [dot] text obfuscations.

Does it handle JavaScript-only sites? Yes, via an automatic headless-Chromium fallback, used only when plain HTTP found nothing and the page looks like an empty JS shell.

Will it report a "share on Twitter" button as the company's Twitter? No. Share/intent/sharer links are explicitly filtered out.

How many pages does it crawl per site? maxPagesPerSite (default 12), visited in contact-likelihood order, limited by maxCrawlDepth (default 2).

Is my existing task/integration broken by this update? No. The legacy keyword-search mode is unchanged and is selected automatically when the input has keywords and no URLs.


Support and feedback πŸ’¬

  • Bug reports / feature requests: dev.scraperengine@gmail.com
  • Please include the run ID and the input you used β€” it makes diagnosis much faster.