Website Contact Details Crawler
Pricing
from $0.005 / actor start
Website Contact Details Crawler
Crawl any list of websites and extract every email address, phone number (E.164) and social media profile, deduplicated into one clean record per domain. Fast HTTP-only crawler with pay-per-result pricing.
Pricing
from $0.005 / actor start
Rating
0.0
(0)
Developer
Eonix Pvt Ltd
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Website Contact Scraper — Extract Emails, Phone Numbers & Social Media Links from Any Website
Give this Actor a list of websites and get back one clean row per website with every email address, phone number and social media profile it could find. It's the cheapest contact scraper on Apify: you pay a tenth of a cent per page, and the per-website fee applies only when an email or phone number was actually found.
What you get
For every website you give it, the scraper visits the pages most likely to list contact details first: Contact, About, Team, Impressum. It then returns:
- 📧 Emails: including addresses hidden with Cloudflare email protection, HTML entities, or written as
name [at] domain [dot] com - 📞 Phone numbers: validated, in international E.164 format (
+12125550123) plus the original text as written on the site - 🔗 Social profiles: Facebook, Instagram, LinkedIn (company and personal), X/Twitter, YouTube, TikTok, Pinterest, GitHub, Threads, Bluesky
- 🏢 Business name and address: from the site's structured
schema.orgdata when available - 🔍 Sources: the exact page each email and phone number was found on, so you can verify any result
Use cases
- Lead generation: turn a list of company websites into emails and phone numbers for outreach or your CRM.
- Enrich Google Maps results: Google Maps listings have websites but rarely emails. Feed the websites in here (see below).
- Market research: map which competitors or suppliers are active on which social networks.
- AI agents and RAG pipelines: give your agent a reliable "find the contact details for this company" tool through the API or MCP.
- Data hygiene and monitoring: re-check the contact details you store against what's live on each company's website, on a schedule.
Sample output
These are the first 3 records from a real run with the default input on 26 Sep 2026. The phone lists and sources are shortened here: Levain Bakery has 21 phone numbers and Voodoo Doughnut 24, one per shop location.
[{"domain": "russanddaughters.com","startUrl": "https://russanddaughters.com/","pagesCrawled": 2,"hasContacts": true,"emails": ["info@russanddaughters.com"],"phones": [{ "e164": "+12124754880", "raw": "212-475-4880", "country": "US" }],"socials": { "instagram": ["https://instagram.com/russanddaughters"] },"organizationName": null,"address": null,"sources": {"emails": {"info@russanddaughters.com": ["https://russanddaughters.com/","https://www.russanddaughters.com/accessibility/"]},"phones": {"+12124754880": ["https://russanddaughters.com/", "https://www.russanddaughters.com/accessibility/"]}},"crawledAt": "2026-09-26T14:28:19.419Z"},{"domain": "levainbakery.com","startUrl": "https://levainbakery.com/","pagesCrawled": 16,"hasContacts": true,"emails": ["corporategifts@levainbakery.com","events@levainbakery.com","info@levainbakery.com","myorder@levainbakery.com"],"phones": [{ "e164": "+18004882085", "raw": "1-800-488-2085", "country": "US" },{ "e164": "+19174643769", "raw": "+1917.464.3769", "country": "US" },{ "e164": "+16469745901", "raw": "+1646-974-5901", "country": "US" }],"socials": {"facebook": ["https://facebook.com/LevainBakery"],"instagram": ["https://instagram.com/levainbakery"],"linkedinCompany": ["https://linkedin.com/company/levain-bakery"],"tiktok": ["https://tiktok.com/@levainbakery"]},"organizationName": "Levain Bakery","address": null,"sources": {"emails": {"events@levainbakery.com": ["https://levainbakery.com/pages/weddings","https://levainbakery.com/pages/events"]},"phones": { "+18004882085": ["https://levainbakery.com/pages/catering"] }},"crawledAt": "2026-09-26T14:28:34.727Z"},{"domain": "voodoodoughnut.com","startUrl": "https://voodoodoughnut.com/","pagesCrawled": 16,"hasContacts": true,"emails": ["legal@voodoodoughnut.com", "llegal@voodoodoughnut.com", "marketing@voodoodoughnut.com"],"phones": [{ "e164": "+15032352666", "raw": "5032352666", "country": "US" },{ "e164": "+17206495666", "raw": "+17206495666", "country": "US" },{ "e164": "+14072676897", "raw": "4072676897", "country": "US" }],"socials": {"facebook": ["https://facebook.com/Voodoo-Doughnut-119262244761342"],"instagram": ["https://instagram.com/voodoodoughnut"],"linkedinCompany": ["https://linkedin.com/company/voodoo-doughnut"],"tiktok": ["https://tiktok.com/@voodoodoughnut"]},"organizationName": "Voodoo Doughnut","address": "7101 Melrose Ave, Los Angeles, California (CA) 90046, US","sources": {"emails": {"legal@voodoodoughnut.com": ["https://voodoodoughnut.com/privacy-policy/","https://voodoodoughnut.com/terms-and-conditions/"]},"phones": {"+15032352666": ["https://voodoodoughnut.com/locations/", "https://voodoodoughnut.com/event-catering/"]}},"crawledAt": "2026-09-26T14:28:35.385Z"}]
Every row has the same fields. Empty lists stay [] and missing values are null (never ""). Every social network key is always present in socials; empty networks are omitted above to keep the sample short.
Pricing: pay only for what you get
This Actor uses pay-per-event pricing. There is no monthly fee, and platform usage is included.
| Event | Price | When it's charged |
|---|---|---|
| Actor start | $0.005 | Once per run |
| Page crawled | $0.001 | Each page successfully downloaded and scanned. Failed, blocked, 404 and off-site pages are free. |
| Website with contacts | $0.01 | Once per website where at least one email or phone number was found. Websites with nothing found don't incur this charge. |
Worked example: 1,000 company websites
- A typical small-business site needs 5–15 pages (the demo run above averaged 11). Say 8 pages: 1,000 × 8 × $0.001 = $8
- If all 1,000 websites have an email or phone: 1,000 × $0.01 = $10. If only 700 do, it's $7.
- Total: about $15–18 for 1,000 websites, or less than 2 cents per website.
You stay in control:
- Max pages per website caps the per-page cost of each site. The default is 20; contact pages are always visited first.
- Max websites caps how many results you receive.
- Maximum charge per run (set in the Apify Console run options) is a hard budget. The Actor stops cleanly before going over it, and websites already in progress are still delivered. The run statistics record
budgetReached: truewhen this happens.
Input parameters
| Parameter | Type | Default | What it does |
|---|---|---|---|
startUrls | list of URLs | 3 demo websites | Websites to scan. Full URLs (https://acme.com/contact) or bare domains (acme.com). Upload a file or link a Google Sheet for big lists. Duplicates of the same website are merged. |
maxPagesPerDomain | number | 20 | Most pages scanned per website. |
maxDepth | number | 2 | How many clicks away from the start page to go. 0 = start page only. |
maxResults | number | 50 | Stop after this many websites have been delivered. |
prioritizePaths | list | /contact, /contact-us, /about, /about-us, /team, /impressum, /kontakt | Pages scanned before anything else. |
sameDomainOnly | yes/no | yes | Turn off to also scan subdomains such as shop.acme.com. Other websites are never followed. |
extractPhones | yes/no | yes | Find and validate phone numbers. |
extractSocials | yes/no | yes | Find social media profile links. |
phoneCountryHint | text | US | Country used to read numbers written without +country (e.g. (212) 555-0123). Use GB, DE, AU… for sites in other countries. |
respectRobotsTxt | yes/no | yes | Skip pages a website asks crawlers not to visit. |
maxRequestRetries | number | 3 | Retries for failed or blocked pages, each with a fresh IP and a longer wait. |
proxyConfiguration | proxy | Apify Proxy | Datacenter proxy works for most sites. Switch to residential if many sites block you. |
How to use it
From the API (cURL)
curl -X POST "https://api.apify.com/v2/acts/yasaslive~contact-info-crawler/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \-H "Content-Type: application/json" \-d '{ "startUrls": [{ "url": "https://russanddaughters.com" }], "maxPagesPerDomain": 10 }'
Python
from apify_client import ApifyClientclient = ApifyClient("YOUR_API_TOKEN")run = client.actor("yasaslive/contact-info-crawler").call(run_input={"startUrls": [{"url": "https://levainbakery.com"}, {"url": "voodoodoughnut.com"}],"maxPagesPerDomain": 15,})for site in client.dataset(run["defaultDatasetId"]).iterate_items():print(site["domain"], site["emails"], [p["e164"] for p in site["phones"]])
Node.js
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });const run = await client.actor('yasaslive/contact-info-crawler').call({startUrls: [{ url: 'https://levainbakery.com' }],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Make (Integromat)
Add the Apify → Run an Actor module and choose contact-info-crawler. Turn on Run synchronously, then add Apify → Get Dataset Items with the run's defaultDatasetId. Map emails and phones[].e164 into your CRM or Google Sheets module.
n8n
Use the Apify node (community node @apify/n8n-nodes-apify), set Operation: Run Actor and get dataset, Actor: yasaslive/contact-info-crawler, and pass the input JSON. You can also use a plain HTTP Request node with the cURL URL above.
MCP (Claude, Cursor and other AI agents)
Add the Apify MCP server with this Actor as a tool:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com/?actors=yasaslive/contact-info-crawler","headers": { "Authorization": "Bearer YOUR_API_TOKEN" }}}}
Your agent can then call it with prompts like "find the contact email and phone for acme-bakery.com".
Pair with Google Maps Scraper output
Google Maps gives you business websites but almost never email addresses. Chain the two:
- Run a Google Maps scraper (e.g.
compass/crawler-google-places) for your search, like "bakeries in Brooklyn". - Take the
websitefield of each place and feed it into this Actor. - Join the results back on the domain.
from urllib.parse import urlparsefrom apify_client import ApifyClientclient = ApifyClient("YOUR_API_TOKEN")# 1. Places from Google Mapsmaps_run = client.actor("compass/crawler-google-places").call(run_input={"searchStringsArray": ["bakery"],"locationQuery": "Brooklyn, New York","maxCrawledPlacesPerSearch": 100,})places = list(client.dataset(maps_run["defaultDatasetId"]).iterate_items())# 2. Contact details for every place that has a websitewebsites = sorted({p["website"] for p in places if p.get("website")})contacts_run = client.actor("yasaslive/contact-info-crawler").call(run_input={"startUrls": [{"url": url} for url in websites],"maxPagesPerDomain": 10,"maxResults": len(websites),})contacts = {c["domain"]: c for c in client.dataset(contacts_run["defaultDatasetId"]).iterate_items()}# 3. Join on domaindef domain_of(url):return urlparse(url).hostname.removeprefix("www.")for place in places:site = contacts.get(domain_of(place["website"])) if place.get("website") else Noneprint(place["title"], "|", ", ".join(site["emails"]) if site else "—")
FAQ
Why didn't I get a row for some websites?
Websites where no page could be loaded (offline, blocked by a bot wall, or disallowed by robots.txt) are listed with the reason in the run's key-value store under FAILED_DOMAINS. You are not charged for them. Websites that loaded but had no contacts do appear in the results with "hasContacts": false; you pay only for the pages scanned there.
Does it work on JavaScript-heavy websites? It reads the HTML the server sends, which covers the large majority of small-business sites (WordPress, Wix, Squarespace, Shopify, Webflow). Websites that build their whole page in the browser can come back empty. That's the trade-off for being fast and cheap.
Does it find emails hidden from bots?
Yes, for the common techniques: Cloudflare email protection, HTML-entity encoding, URL-encoded mailto: links and name [at] domain [dot] com style text. It won't solve CAPTCHAs or run JavaScript to reveal an email.
Can it crawl a whole website?
It's built to find contact details, not to archive sites. Contact-style pages are scanned first and each website is capped at maxPagesPerDomain (up to 1,000).
Where are the run statistics?
In the key-value store record STATS. It holds pages crawled, websites finished, errors by category (blocked / rate-limited / proxy / network / parse / other), budgetReached, and how many events were charged. It is updated every 30 seconds while the run is going.
Limitations
- No JavaScript rendering: content injected by client-side scripts is invisible to this crawler.
- Phone numbers written without a country prefix are read using
phoneCountryHint, so a site with numbers from several countries written in national format may get some misread. Numbers written with+are always read correctly. - Unformatted digit runs (
2125550123in running text) are ignored on purpose, because they are usually order numbers or IDs. Numbers intel:links and structured data are always kept. - At most 10 source pages are listed per email or phone.
- Websites behind aggressive bot protection may fail even with retries. Try residential proxies.
Legal and responsible use
This Actor only reads publicly available web pages, the same ones any visitor can open without logging in. It respects robots.txt by default and paces requests to each website (at most about one per second).
You are responsible for how you use the data. Personal data, such as named employees' emails or LinkedIn profiles, is protected by laws including the GDPR (EU/UK) and CCPA (California). Make sure you have a legitimate basis for processing it, honour opt-outs, and follow anti-spam laws such as CAN-SPAM and PECR when contacting anyone. Check each website's Terms of Service. If you're unsure, ask a lawyer before using scraped personal data.