Find emails from a website list in Python (CSV in, CSV out)
If your list lives in a CSV, a 25-line script is the shortest path. It sends all sites in one request, matches rows by domain and writes a new CSV. We ran it as written on the three-row file below.
Use your own token. Create an API token in Apify under Settings, Integrations, and put it in the tool's secret or credential store. Never paste it into a shared sheet or a public workflow. The examples say YOUR_APIFY_TOKEN; replace it. Sending the token in the Authorization: Bearer header is safer than putting it in the URL, because URLs end up in logs.
Input file
company,website
Basecamp,basecamp.com
Mozilla,https://mozilla.org
Sitemaps,sitemaps.org
The script
Install requests and set your token with export APIFY_TOKEN=... (Python 3.9 or later).
import csv, os, requests
TOKEN = os.environ["APIFY_TOKEN"] # your own Apify API token
URL = "https://api.apify.com/v2/acts/datagleaner~website-contact-details-scraper/run-sync-get-dataset-items"
with open("companies.csv", newline="") as f:
rows = list(csv.DictReader(f))
resp = requests.post(
URL,
params={"maxTotalChargeUsd": 1, "fields": "domain,bestEmail,status"},
headers={"Authorization": f"Bearer {TOKEN}"},
json={"websites": [r["website"] for r in rows], "maxPagesPerSite": 4},
timeout=300,
)
resp.raise_for_status()
by_domain = {item["domain"]: item for item in resp.json()}
for r in rows:
domain = r["website"].replace("https://", "").replace("http://", "").removeprefix("www.").split("/")[0]
item = by_domain.get(domain, {})
r["email"] = item.get("bestEmail") or ""
r["status"] = item.get("status", "missing")
with open("companies_with_emails.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
w.writeheader()
w.writerows(rows)
Output file
company,website,email,status
Basecamp,basecamp.com,jason@basecamp.com,ok
Mozilla,https://mozilla.org,trademark-permissions@mozilla.com,ok
Sitemaps,sitemaps.org,,noContacts
The call returns HTTP 201, which raise_for_status() treats as success. A bad token gives 401; we confirmed that case returns immediately without spending anything.
Bigger lists and the official client
For more than a few hundred sites, send chunks of 200 in a loop so each request finishes in minutes. Apify also publishes an official Python client (apify-client) whose client.actor("datagleaner/website-contact-details-scraper").call(run_input=...) starts a run and waits, after which you read the dataset; the sync endpoint above does the same in one request and is what we tested.
What comes back
A JSON array with one object per site. Output order is not guaranteed to match input order (in our test, sitemaps.org came back before mozilla.org), so always match rows on domain, never on position. A real response for three sites, with fields=domain,bestEmail,status,failureReason:
[
{"domain": "basecamp.com", "bestEmail": "jason@basecamp.com", "status": "ok", "failureReason": null},
{"domain": "sitemaps.org", "bestEmail": null, "status": "noContacts", "failureReason": "no_contact_found"},
{"domain": "mozilla.org", "bestEmail": "trademark-permissions@mozilla.com", "status": "ok", "failureReason": null}
]
Without fields each object is large (around 60 keys: every email with the page it was found on and a kind, phones, 14 social networks, contact-form URLs, pages crawled). Ask only for what you need. A site that publishes no email still returns a row, with bestEmail: null. Junk input such as "not a site" returns a row with status: "invalidUrl" instead of failing the call.
What it costs
The Actor charges $0.002 for each site where an email is found and nothing for sites without one. 1,000 sites with 600 hits is about $1.20, and a 4-site test is under a cent. Apify's free plan includes monthly credit. The maxTotalChargeUsd query parameter in the examples is a hard cap on what one call can spend: the run stops when it is reached, so a typo cannot cost more than you set. Expect an email on roughly half to two-thirds of small-business sites and less on large brands; in our five-site test three sites gave one.
Try it: Website Contact Details Scraper on Apify. $0.002 per site with an email, nothing for sites without. The Apify free plan includes monthly credit, so a test run costs nothing out of pocket.
Related guides
- Find emails from a website list in Clay with an HTTP API column
- Clay: find a contact email per row with the Run Apify Actor action
- Find emails from a list of websites in n8n with Apify
- Find emails from a website list in Make (formerly Integromat)
- Find emails from a company website in Zapier with Webhooks
- Find emails from a website list in Google Sheets (Apps Script)