DEV Community

TORIX
TORIX

Posted on

Extract Emails, Phones & Socials From 1,000 Company Websites (Python + No-Code)

You have a list of domains: a CRM export, a conference exhibitor list, results from Google Maps, or companies hiring
for a role you sell into. You need a contact row for each company: a general email, a phone number, LinkedIn and
other social profiles.

Doing it by hand takes about two minutes per site. Writing your own crawler is easy for 10 sites and painful for 1,000.
You end up dealing with contact pages hidden in footers, [email protected] Cloudflare obfuscation,
info [at] acme [dot] com, junk like logo@2x.png, five spellings of the same phone number, and sites that only answer
from residential IPs.

This guide shows a hosted approach that returns one row per domain and only charges for sites that worked.

The tool

Website Contact Details Extractor on Apify:

  • crawls the homepage plus likely contact pages (/contact, /about, /impressum, footer links), 6 pages per site by default;
  • decodes Cloudflare email protection and [at]/[dot] obfuscation, and reads schema.org / JSON-LD business data;
  • filters junk (image filenames, noreply@, Sentry DSNs, placeholder numbers) and deduplicates phones;
  • ranks a primaryEmail (on-domain role inboxes like info@, sales@, hello@ first) and records which page each email came from.

Benchmark on the platform: 300 company websites (~1,330 pages) in 77 seconds. Price: $3 per 1,000 websites, with
failed sites free.

Python

pip install apify-client
export APIFY_TOKEN=<your token>
Enter fullscreen mode Exit fullscreen mode
import csv, os
from apify_client import ApifyClient

domains = [line.strip() for line in open("domains.txt") if line.strip()]

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("murch1k/website-contact-details-extractor").call(run_input={
    "websites": domains,
    "maxPagesPerSite": 6,
    "sameDomainEmailsOnly": True,   # drop gmail.com / agency emails that aren't the company's own
    "maxConcurrency": 50,
})

with open("contacts.csv", "w", newline="") as f:
    w = csv.writer(f)
    w.writerow(["domain", "primaryEmail", "allEmails", "phone", "linkedin"])
    for r in client.dataset(run["defaultDatasetId"]).iterate_items():
        socials = r.get("socials") or {}
        w.writerow([r["domain"], r.get("primaryEmail"), " ".join(r.get("emails") or []),
                    r.get("primaryPhone"), " ".join(socials.get("linkedin") or [])])
Enter fullscreen mode Exit fullscreen mode

Each dataset row looks like this:

{
  "domain": "joesstonecrab.com",
  "status": "ok",
  "primaryEmail": "info@joesstonecrab.com",
  "emails": ["info@joesstonecrab.com"],
  "primaryPhone": "3056734611",
  "socials": {
    "facebook": ["https://facebook.com/JoesStoneCrab"],
    "instagram": ["https://instagram.com/joesstonecrab"]
  },
  "pagesScraped": 5
}
Enter fullscreen mode Exit fullscreen mode

Node.js

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('murch1k/website-contact-details-extractor').call({
    websites: ['https://apify.com', 'https://crawlee.dev'],
    maxPagesPerSite: 6,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.table(items.map(({ domain, primaryEmail, primaryPhone }) => ({ domain, primaryEmail, primaryPhone })));
Enter fullscreen mode Exit fullscreen mode

No-code: Google Sheets / Make / n8n / Zapier

On Apify, open the Actor, paste domains into Websites, and click Start. Export as CSV or Excel, or connect the
Google Sheets integration so results land in a sheet. In Make, n8n or Zapier, use the Apify module "Run an Actor" and
map the dataset items to your CRM.

Pipeline ideas

  1. Google Maps → contacts: search "dentist in Austin, TX" with a Maps scraper, then feed the website column here.
  2. Hiring signal → contacts: companies posting "Head of Growth" roles are buying marketing tools. Pull open jobs from their career sites (see the next article in this series), then enrich the domains.
  3. AI agent enrichment: expose the Actor through the Apify MCP server, and your agent can enrich a lead on demand: "find the contact email and LinkedIn page for acme.com".

Tips

  • 403s: a few sites block all cloud traffic. Enable Apify Proxy and re-run only the failed rows (they weren't charged).
  • JS-only menus: if no contact links are found in the HTML, the actor still tries the standard contact paths.
  • Phones are returned the way the site prints them, normalised to digits with an optional +. Numbers without a country code are local to the site's country.

Use it responsibly

The actor only collects what websites publish about themselves, mostly role inboxes. Named personal emails are still
personal data under GDPR, CAN-SPAM and similar laws. Have a legitimate interest, keep outreach relevant, honour
opt-outs, and don't send bulk email from a domain you care about without warming it up.

Code for this and other scrapers is in the Apify Scrapers Pack repo.

The toolkit used in this series

All five are pay-per-result Actors on Apify (no subscription; the Apify free plan's monthly credits cover a test run):

  1. YouTube Transcript Scraper: transcripts with timestamps from videos, channels and playlists.
  2. Career Site Jobs Scraper: open roles from Greenhouse, Lever, Ashby, Workday and 4 more ATSs.
  3. Google Trends Scraper: interest over time, related queries and topics.
  4. Substack Scraper: posts and newsletter metadata as clean text for LLMs.
  5. Website Contact Details Extractor: emails, phones and socials, one row per domain.

Python, Node.js, LangChain and MCP examples for each one are in apify-scrapers-pack on GitHub.
A star there helps other people find it.

Top comments (0)