Website Contact Scraper in Python: extract emails, phone numbers and social profiles from any list of domains
A website email extractor, phone number finder and social link scraper for B2B lead generation, called from Python (or Node) with the official apify-client. Give it company domains; get one clean row per website with the best business email, every email found, phone numbers in international format, LinkedIn / Facebook / Instagram / X / YouTube / TikTok profiles, the company name, postal address and contact page URL.
Disclosure: I built this actor; it's a paid tool on Apify (Website Contact Scraper on the Apify Store). This repo is a free tutorial and example code (MIT).
- Priced per website, not per page: $3.00 per 1,000 websites, however many pages it reads (default 6, up to 30).
- Reads the right pages: homepage plus the most contact-like links (contact, imprint / Impressum, about, support, locations, in nine languages).
- No headless browser: plain HTTP + Cheerio, so 20 websites finish in about 6 seconds.
- Clean data: Cloudflare-protected and
info [at] acme [dot] comstyle emails decoded, phones validated with libphonenumber, share buttons and personal LinkedIn profiles ignored. - Failed sites are free: a domain that does not resolve or blocks bots comes back as
status: "failed"and is not charged.
pip install apify-clientfrom apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("rel8ble/website-contact-extractor").call(run_input={
"websites": ["apify.com", "hetzner.com", "thomann.de", "https://www.joesstonecrab.com"],
"maxPagesPerWebsite": 6,
})
for site in client.dataset(run["defaultDatasetId"]).iterate_items():
print(site["domain"], site.get("email"), site.get("phone"), site.get("linkedin"))Get your token in the Apify Console under Settings > Integrations. The free plan includes $5 of monthly credit (about 1,600 websites).
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('rel8ble/website-contact-extractor').call({
websites: ['kinsta.com', 'allbirds.com'],
onlySameDomainEmails: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((s) => console.log(s.domain, s.email, s.phonesE164));example.py reads domains.txt (one domain per line, e.g. the website column from a Google Maps or CRM export), runs the actor, and writes contacts.csv with one row per site. It also prints fill rates and the sites that publish no email, so you know which ones need the contact form or a phone call.
pip install -r requirements.txt
set APIFY_TOKEN=<YOUR_APIFY_TOKEN> # macOS/Linux: export APIFY_TOKEN=...
python example.py| Field | Type | Default | What it does |
|---|---|---|---|
websites |
array of strings | required | Bare domains, hosts or full URLs. One result row per domain (deduplicated). |
maxPagesPerWebsite |
integer 1-30 | 6 | Pages read per site incl. homepage. Price stays per website. |
defaultCountry |
string | guessed from TLD | Country for phone numbers written without a prefix (US, GB, DE). |
scanTextForPhones |
boolean | true | Also read validated phone numbers from contact/imprint page text. |
onlySameDomainEmails |
boolean | false | Keep only @yourdomain emails, drop Gmail/agency addresses. |
maxConcurrency |
integer 1-50 | 20 | Parallel requests. |
From a cloud run on 9 Oct 2026:
{
"website": "hetzner.com",
"domain": "hetzner.com",
"finalUrl": "https://www.hetzner.com/",
"status": "ok",
"companyName": "Hetzner",
"language": "en-US",
"email": "info@hetzner.com",
"emails": ["info@hetzner.com", "dsa-authorities@hetzner.com", "tco@hetzner.com"],
"emailSources": { "info@hetzner.com": "mailto @ /legal/legal-notice/" },
"phone": "+49 9831 5050",
"phones": ["+49 9831 5050", "+49 9831 5053"],
"phonesE164": ["+4998315050", "+4998315053"],
"linkedin": "https://www.linkedin.com/company/hetzner-online",
"facebook": "https://www.facebook.com/hetzner.de",
"instagram": "https://www.instagram.com/hetzner.online",
"twitter": "https://x.com/Hetzner_Online",
"youtube": "https://www.youtube.com/user/HetznerOnline",
"contactPageUrl": "https://www.hetzner.com/legal/legal-notice/",
"pagesCrawled": 6,
"scrapedAt": "2026-10-09T17:27:53.266Z"
}A few rows from a 20-domain test run on the same day (19 loaded, 1 deliberately fake domain failed and was not charged; 6.4 s total):
| Domain | Best email | Phone | Contact page | |
|---|---|---|---|---|
| thomann.de | info@thomann.de | +49 9546 922366 | - | yes |
| kinsta.com | contact@kinsta.com | +1 888 610 2915 | yes | yes |
| mcmaster.com | eprocurement@mcmaster.com | +1 630 833 0300 | - | yes |
| sennheiser.com | contact@sennheiser.com | +49 5130 6000 | yes | yes |
| joesstonecrab.com | (none published) | +1 305 673 0365 | - | yes |
| zapier.com | (none published) | - | yes | yes |
Across that run: 12 of 19 sites had an email, 11 a phone, 16 at least one social profile. On a larger 67-site list, 60% had an email, 33% a phone and 87% a social profile. The actor never guesses addresses; many sites only offer a contact form.
- $3.00 per 1,000 websites (pay per result,
status: "ok"rows only). 100 sites = $0.30, 10,000 = $30. - Reads the HTML the server sends; single-page apps that render everything in the browser may return fewer contacts.
- About 5% of sites use strict bot protection and come back
failed(free). - Only public, business-published contact details; personal LinkedIn profiles and team pages are skipped. Emails can still be personal data under GDPR and anti-spam laws, so use the results responsibly.
- Apify Store: https://apify.com/rel8ble/website-contact-extractor
- Python client docs: https://docs.apify.com/api/client/python
- Pairs well with: yellowpages-scraper-python, yc-companies-api-python, website-tech-stack-detector-python
MIT