Apify Actors / Emails, phones and social links from company websites

Apify Actor

Emails, phones and social links from company websites

Collecting contacts from company websites by hand means opening each home page, finding the contact or impressum page and copying what is there. This Actor does that for a list of domains. It reads the home page first, then the contact, about, team, impressum, legal and support pages, and matching sitemap.xml entries.

You get one row per website with each email and its type (role, personal or generic), the page it came from and an MX check, phone numbers in E.164 format when the country is clear, social profile links and the schema.org postal address. It uses plain HTTP requests with no browser, so a run of 1,000 websites finishes in minutes. Websites with nothing found and websites that fail are free.

Run it on ApifySee input and output

$0.005 per website with contacts ($5 per 1,000) · pay per result, no subscription

Price

EventPriceCharged when
Website with contacts (domain-with-contacts)$0.005Once per website that returned at least one email or phone number, whatever the number of pages read
No contacts or failed$0Nothing found, blocked websites, DNS errors, timeouts and bad input lines
Example runUnitsCost
The input below (2 websites)up to 2 × $0.005up to $0.01
1,000 websites, 650 with contacts650 × $0.005$3.25
1,000 websites, all with contacts1,000 × $0.005$5.00

What you get

Input

Set the run options in the Apify console, or send the same object through the API.

{
  "domains": [
    "apify.com",
    "python.org"
  ],
  "maxPagesPerDomain": 8
}

Two domains, up to 8 pages each. One domain, host or full URL per line, up to 1,000 per run; duplicates are crawled once. maxPagesPerDomain goes from 1 to 30, home page included, and does not change the price.

Output

{
  "domain": "litware-example.de",
  "finalUrl": "https://www.litware-example.de/",
  "status": "ok",
  "companyName": "Litware GmbH",
  "emails": [
    { "email": "", "type": "role", "source": "https://www.litware-example.de/kontakt", "mx": true }
  ],
  "phones": [
    { "phone": "+49301234567", "format": "e164", "source": "https://www.litware-example.de/impressum" }
  ],
  "socials": { "linkedin": ["https://www.linkedin.com/company/litware-example"], "x": [], "facebook": [], "instagram": [], "youtube": [], "tiktok": [], "github": [], "threads": [], "telegram": [], "whatsapp": [] },
  "address": { "street": "Beispielstr. 1", "locality": "Berlin", "postalCode": "10115", "country": "DE", "formatted": "Beispielstr. 1, 10115 Berlin, DE" },
  "pagesFetched": 6,
  "errorCode": null
}

The example row from the README, for a made-up company. status is ok only when at least one email or phone was found. The other values are no-contacts, blocked, dns-error, timeout, error and skipped, and those rows are free.

Run it from your own code

The Actor takes the same input as a JSON body. This call starts a run, waits for it and returns the dataset items.

curl -X POST "https://api.apify.com/v2/acts/zovo-one~contact-info-email-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["apify.com", "python.org"], "maxPagesPerDomain": 8}'

A sync call waits at most 300 seconds. For longer runs, start a run with POST /v2/acts/zovo-one~contact-info-email-extractor/runs, wait for it, then read /v2/datasets/<defaultDatasetId>/items.

Limits

Up to 1,000 websites per run, 1 to 30 pages per website, 5 to 60 seconds per page and 1 to 50 websites in parallel. robots.txt is read for each website and disallowed paths are skipped; a website that disallows its home page comes back as blocked with ROBOTS_DISALLOWED. It uses no browser, so websites that need JavaScript to show their contacts, or that block plain HTTP clients with a bot check, come back as no-contacts or blocked and are free. No login pages, no scraping of LinkedIn or other social networks, no guessing of email patterns and no email sending. No proxies in this version; requests come from Apify's servers.

Questions

How do I find the email address on a company website?

Put the domain on one line and run the Actor. It reads emails from mailto: links, the visible text and JSON-LD, and decodes common hiding tricks such as name [at] site [dot] com, (at), HTML entities and Cloudflare email protection. Image names such as and placeholders such as are dropped.

Does it verify the emails?

Only with an MX lookup. It checks once per email domain per run that the domain has mail servers, and mx is true, false or null when unknown. No email is sent, and it doesn't guess patterns such as first.last@.

Do I pay more for websites with many pages?

No. There is one price per website, whatever the number of pages read (1 to 30). A website where nothing was found costs nothing. For example, 1,000 websites where 650 have contacts cost $3.25.

Why does a big website come back as blocked?

Some large websites block any client that is not a full browser. The row says BLOCKED_BY_SITE and is free. Most small and mid-size company websites answer plain HTTP.

Why are the rows in a different order than my list?

Websites are crawled in parallel and rows land as they finish. Match them on input or domain.

Can I use the contacts for outreach?

You are responsible for how you use them. Data protection laws such as the GDPR, and email rules such as CAN-SPAM, may apply to storing the data and to contacting people. Personal addresses of named employees can be personal data, so keep only what you need and honour opt-outs.

Support this work

If this Actor saves you time, you can support the work on GitHub Sponsors. Monthly tiers start at $5, and one-time tiers start at $15. Sponsorship is optional, and the tool works the same without it.

Built and maintained by Zovo. Actor on Apify · All Actors.