Website Contact Extractor: Emails, Phones & Social Links
Pricing
from $1.00 / 1,000 page reads
Website Contact Extractor: Emails, Phones & Social Links
Get emails, phone numbers and social links (LinkedIn, Facebook, Instagram, X, YouTube, TikTok) from websites or a Google Maps dataset. Reads the home, contact and Impressum pages, normalizes phones to E.164, finds contact pages and forms. One row per site. Pay per page read.
Pricing
from $1.00 / 1,000 page reads
Rating
0.0
(0)
Developer
Michael Costa
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
What does Website Contact Extractor: Emails, Phones & Social Links do?
Website Contact Extractor is an email extractor for websites: give it a list of sites, or a dataset from a Google Maps or directory scraper, and get one row per website with its emails, phone numbers in E.164, social links and contact page, read live from the sites' public pages. No login.
Input fields · API · Use it from Claude, ChatGPT or Cursor (MCP)
Jump to: Fields · Price · How to use · Google Maps leads · Switching · Input · Output · AI agents (MCP) · Limits · FAQ
Try it in one click: the input comes pre-filled with one website. That's 5 pages, or about $0.01 (5 × $0.0019, plus $0.00005 for the run start). Then replace it with your own list.
What data does Website Contact Extractor return?
| Field | Example | Notes |
|---|---|---|
primaryEmail, emails | info@acme.com | Every address found, the site's own domain first. Placeholders, image names and tracking keys are dropped. |
primaryPhone, phones | +13035550142 | Valid numbers only, in E.164. tel: links come first. |
phoneDetails | {"national": "(303) 555-0142", "type": "mobile", "source": "tel link", ...} | Each number's international and national format, country, type and where it was found. |
linkedin, facebook, instagram, x, youtube, tiktok, pinterest, threads, telegram, whatsapp | https://www.linkedin.com/company/acme | The profile the site links to most; null when it links to none. Share buttons, single posts and embedded videos are left out. |
socialLinks | Every profile link found. | |
contactPageUrl, contactFormUrl | https://acme.com/contact | The contact or legal-notice page, and a page with a message form. |
address | 1 Main St, Denver, US | From the site's structured data; null when it doesn't publish one that way. |
pagesVisited, pagesScanned | 3 | Which pages were read, and so what the website cost. |
error, notes | Why a website couldn't be read (dead domain, robots.txt, bot protection), and pages that failed. | |
sourceTitle, sourcePlaceId, sourceIndex, source... | "Rosie's Bakery" | From the dataset item the website came from, plus any keepFields. |
The full list is under Output.
How much does it cost to extract contacts from websites?
You pay per page read, and you see exactly which pages in pagesVisited, plus $0.00005 each time a run starts. Rows
are free.
| Apify plan | Free | Starter | Scale | Business and above |
|---|---|---|---|---|
| Per 1,000 pages | $1.90 | $1.90 | $1.40 | $1.00 |
- What a website costs: at most Max pages per website × the page price, $0.0095 at the default 5 pages on the Starter plan. After the start page, only pages likely to hold contact details are read, so a small site often takes fewer.
- 1,000 Google Maps leads, for example: a measured run on 51 real business websites read 144 pages, 2.8 per website with the unreadable ones counted. At that rate 1,000 websites take about 2,800 pages, or about $5.40 on Starter. With Max pages per website at 1 (home pages only) the most it can be is $1.90.
- Caps: Max websites per run in the input, and Maximum cost per run in the run options. No page is fetched that the maximum can't pay for.
Never charged: websites that can't be read, pages that fail or don't exist, pages robots.txt disallows, bot-check pages, and the rows themselves.
How to extract emails and phone numbers from websites
- Open Website Contact Extractor and click Try for free (or Start if you're signed in).
- Paste your websites into Website URLs, one per line (
acme.comis enough), or pick a dataset. - Optional: set Max pages per website, and Country for local phone numbers if your sites' numbers are written without a country code and the sites have no country domain.
- Click Start, then open the Output tab and export as JSON, CSV or Excel.
Get emails for your Google Maps leads
Most Google Maps results give you a website but no email. Pick the Maps scraper's dataset in Or: websites from a
dataset: the website field is found by itself, and Maps links are never mistaken for the site. A chain's branches
sharing one website are read once. Each row carries the place's sourceTitle, sourcePlaceId and sourceIndex so you
can join it back, and each place's countryCode sets the country for its local phone numbers. Add address, phone
or categoryName under Fields to keep from the dataset to get them as sourceAddress, sourcePhone and
sourceCategoryName.
Run it automatically after your scraper finishes
- On the scraper's Integrations tab, add Website Contact Extractor and set it to start when the run succeeds.
- In Actor input, set Or: websites from a dataset to
{{resource.defaultDatasetId}}. Apify replaces it with the dataset of the run that just finished, and that is what gives this run read access to it. - Save, then test it on one of the scraper's past runs.
Fill in that field: a run that gets the dataset id only in the integration's payload isn't given access to the
dataset (this actor runs with Apify's limited permissions, tested 2026-11-04). It then fetches nothing, charges
nothing, and its status message says what to set.
Without the Integrations form, create a webhook on
ACTOR.RUN.SUCCEEDED with https://api.apify.com/v2/acts/humble-echidna~website-contact-extractor/runs as its URL
and this payload template:
{"datasetId": {{resource.defaultDatasetId}}, "keepFields": ["address", "phone"]}
Switching from another actor?
If you use another contact scraper, the settings map like this:
| Elsewhere | Here |
|---|---|
| Start URLs | Website URLs (urls), as plain lines rather than objects. Or datasetId, which most contact scrapers don't take. |
| Maximum pages per start URL | Max pages per website (maxPagesPerSite), 1 to 20, default 5. |
| Maximum link depth | Not needed: pages are picked by what their links say, not by depth. |
| Merge contacts | Always on: one row per website. For one row per address, turn on One row per email address. |
linkedIns, facebooks and the like (lists) | One best profile per network (linkedin, facebook...), with every link in socialLinks. |
phones, phonesUncertain | phones holds only numbers libphonenumber says are valid, in E.164. |
| Proxy and browser settings | None: pages are fetched directly, without a browser (what that misses). |
| Lead enrichment and email verification add-ons | Not included. There's nothing extra to switch off or pay for. |
The usual surprise with per-page pricing is one website costing hundreds of pages. Here the most a website can cost is
Max pages per website × the page price, and pagesVisited lists what you paid for.
Input
| Field | What it does |
|---|---|
Website URLs (urls) | The websites, one per line. A deeper address starts there. Ignored with a dataset. |
Or: websites from a dataset (datasetId) | One of your Apify datasets, or {{resource.defaultDatasetId}} in an integration. |
Field with the website (datasetUrlField) | The item field holding the website, e.g. contact.website. Empty finds it automatically. |
Fields to keep from the dataset (keepFields) | More item fields to copy, up to 20; address comes out as sourceAddress. |
Max pages per website (maxPagesPerSite) | 1 to 20, default 5, the start page included. |
Country for local phone numbers (defaultCountry) | Two-letter code. Empty uses the Maps item's countryCode, the site's country domain, the page's language tag, else the United States. |
One row per email address (onePerEmail) | One row per address instead of per website, adding email, emailPageUrl, emailOnSiteDomain, emailIsRole. Websites without an email give no row. |
Max websites per run (maxResults) | Stop after this many websites. |
{"urls": ["https://wikimediafoundation.org/", "acme.com"],"maxPagesPerSite": 5,"defaultCountry": "US"}
Output
The pre-filled input's row, from a real run on 2026-11-04 (lists and phoneDetails shortened):
{"url": "https://wikimediafoundation.org/","domain": "wikimediafoundation.org","finalUrl": "https://wikimediafoundation.org/","primaryEmail": "info@wikimedia.org","emails": ["info@wikimedia.org", "donate@wikimedia.org", "business@wikimedia.org", "answers@wikimedia.org", "legal@wikimedia.org"],"primaryPhone": "+14158396885","phones": ["+14158396885", "+14153478540"],"phoneDetails": [{"number": "+14158396885", "international": "+1 415-839-6885", "national": "(415) 839-6885", "country": "US", "type": "fixed line or mobile", "source": "page text"}],"linkedin": "https://www.linkedin.com/company/wikimedia-foundation","facebook": "https://www.facebook.com/wikipedia","instagram": "https://www.instagram.com/wikimediafoundation","x": "https://x.com/wikimedia","youtube": "https://www.youtube.com/@wikipedia","tiktok": "https://www.tiktok.com/@wikipedia","pinterest": null,"threads": null,"telegram": null,"whatsapp": null,"socialLinks": ["https://www.instagram.com/wikimediafoundation", "https://x.com/wikimedia"],"contactPageUrl": "https://wikimediafoundation.org/contact/","contactFormUrl": null,"address": null,"phoneCountry": "US","pagesVisited": ["https://wikimediafoundation.org/", "https://wikimediafoundation.org/contact/", "https://wikimediafoundation.org/who-we-are/", "https://wikimediafoundation.org/who-we-are/people/", "https://wikimediafoundation.org/role/executive/"],"pagesScanned": 5,"error": null,"notes": ["read the 5 pages allowed per website; 3 more candidate pages left"],"sourceTitle": null,"sourcePlaceId": null,"sourceIndex": null,"scrapedAt": "2026-11-04T22:28:59.598Z"}
A website that can't be read still gets a row, with error saying why, empty lists, and pagesScanned 0. The run's
RUN_STATS record lists every website, what it gave and anything that failed.
Run it on a schedule, or from your own code
- Save your input as a task (Save as a new task, top right of the actor page) and add it to a schedule (Console → Schedules → Create new).
- Collect results: download the dataset as JSON, CSV or Excel; fetch the latest run's results from the
API
(
GET https://api.apify.com/v2/actor-tasks/<task id>/runs/last/dataset/items?status=SUCCEEDED&format=csv, with your API token); let a webhook tell your system when a run succeeds; or connect it to Make, Zapier or n8n through Apify's integrations.
Can I use Website Contact Extractor from an AI agent (MCP)?
Yes, through Apify's MCP server, from Claude, ChatGPT, Cursor or any other MCP client. Add this to your client's MCP configuration (or let the agent find it with the server's actor search); your client signs you in to Apify:
{"mcpServers": {"apify": {"url": "https://mcp.apify.com?tools=humble-echidna/website-contact-extractor"}}}
To use an Apify API token instead of signing in, add
"headers": {"Authorization": "Bearer <APIFY_TOKEN>"} next to url.
An agent passes for example {"urls": ["acme.com"], "maxPagesPerSite": 3}. It runs under Apify's limited permissions,
so it needs no approval step before an agent can start it.
Who it's for
Anyone turning a list of businesses into a list of contacts: sales and agency teams enriching Google Maps or directory leads on each scrape, recruiters adding contacts to company lists, and people building local business directories.
Why this one?
- A bill you can work out first. The most a website can cost is Max pages per website × the page price, and each row lists the pages you paid for. There are no add-ons, and failed pages are free.
- Built to run after a scraper. It takes a dataset and a field name directly, so no export and re-import, and carries your source fields into each row.
- Phones you can dial. Every number is checked with libphonenumber and written in E.164, with its country, type and where it was found. Dates, opening hours and order numbers that merely look like numbers are filtered out.
- Measured on real leads. On 51 small and mid-size business websites from OpenStreetMap (Denver, Manchester, Hamburg), run on Apify on 2026-11-04: 37 could be read, and of those 32 gave an email, 33 a phone number and 26 a social profile. Of the other 14, 5 domains were dead, 5 sites' robots.txt asks crawlers not to read them, 3 refused the request and 1 didn't answer in time.
- Polite and safe. It identifies itself honestly (User-Agent
HumbleEchidnaApify), follows each site's robots.txt and Crawl-delay, and only requests public web addresses on the standard ports (80 and 443). - Reliable. One failing website never affects the others, and each failure is named in its row.
Limits
- No browser. Pages are fetched as HTML, so details that only appear after scripts run are missed unless they're in the page source (Wix and Squarespace sites usually are). There are no iframes and no image or PDF reading.
- No proxies, no bot-check workarounds. A site that refuses automated requests or shows a bot check is reported, not worked around, and its robots.txt is followed. You'll get fewer sites read than from a crawler that ignores both.
- Same site only: the site you gave and its subdomains, up to 20 pages. It finds contact pages; it isn't a whole-site crawl for emails buried in old blog posts.
- Emails aren't verified. They're what the site publishes; run them through a verifier before a large mailing.
- Local phone numbers depend on the country. A number without a country code is read in the country from the order under Input. On a generic domain with no other hint that's the United States; set Country for local phone numbers if that's wrong.
- At most 20,000 dataset items and 10,000 websites per run. Up to 50 emails and 20 phone numbers per website.
FAQ
Is it legal to extract contact details from websites?
It reads public pages you point it at, with no login, and follows each site's robots.txt. You choose the sites, and a
site's own terms of use may limit automated access, so check them for your use. Contact details can be personal data
(jane.smith@acme.com), even when published: how you store and use them falls under laws such as the GDPR, and cold
outreach under laws such as CAN-SPAM and the ePrivacy rules. You are responsible for having a lawful basis. If in doubt,
ask a lawyer.
Why did a website return nothing, or fewer contacts than I can see?
Check its row. error says why it couldn't be read at all, and notes lists pages that failed or a page limit that was
reached (raise Max pages per website). If the row has neither, the details probably appear only after scripts run,
or sit on a page the links didn't point to. Please open an issue with the website if it looks wrong.
Why is this phone number wrong?
Most likely the country: a local number was read in the wrong one. phoneCountry shows which was used, and
phoneDetails shows where each number came from. Set Country for local phone numbers, or use a dataset with
countryCode.
Can I get one row per email address?
Yes, turn on One row per email address. It costs the same, since pages are charged and rows are free.
Related actors
| Actor | Use it when |
|---|---|
| Dataset Transformer | You want the results cleaned up: only rows with an email, duplicates removed by email, as a CSV. |
| Google Sheets Import & Export | You want the contacts in a Google Sheet after each run. |
| Website Technology Detector | You also want to know what each lead's website runs on. It takes the same dataset. |
| Bulk URL & Broken Link Checker | You want to weed out dead websites first. |
Feedback and support
Found a bug, or a site whose contacts it misses? Open an issue on the Issues tab with the website.
Versions
Current version: 0.1. See the Changelog tab for what changed in each version.