Website Contact Details Scraper - Emails, Phones & Socials avatar

Website Contact Details Scraper - Emails, Phones & Socials

Pricing

from $2.00 / 1,000 domain inspecteds

Go to Apify Store
Website Contact Details Scraper - Emails, Phones & Socials

Website Contact Details Scraper - Emails, Phones & Socials

Paste company domains or pick a Google Maps scraper's Dataset. Get one contact record per company with public emails, phones, social profiles, MX status and the page where each email was found.

Pricing

from $2.00 / 1,000 domain inspecteds

Rating

0.0

(0)

Developer

Vadim Bezrukov

Vadim Bezrukov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Share

Paste company domains, or pick the Dataset of a Google Maps, directory or lead scraper run. You get one contact record per company: public emails, phone numbers and social profiles, with an MX check and the page where each email was found.

Use it when the next step is a spreadsheet, a CRM or an outreach workflow. You do not need a second job to merge the homepage, contact page and about page into one row, and you do not need a separate MX lookup for the addresses the site actually published.

This Actor reads public HTML over HTTP. It does not open a browser, log in, guess addresses, or build emails from people's names. HAS_MX means the email domain publishes mail servers. It does not mean the mailbox exists, and a role label such as sales does not prove who reads that inbox.

Run it

{
"websites": ["python.org", "https://www.iana.org/"],
"maxPagesPerSite": 5,
"includePhones": true,
"includeSocials": true,
"checkMx": true
}

The default input is python.org. The run returns one Dataset row for that domain with each public email it found, the page the email came from and its MX status. On another domain, NO_CONTACTS is a finished inspection with nothing public to keep. It is not a failed download.

For an agent or API run, set maxTotalChargeUsd to at least $0.01 plus $0.002 for each domain you expect to inspect. At the default 512 MB the start event is $0.01. That cap is the event bill. Platform usage is included.

Run it after a Google Maps scraper

You already have places with a website column. Pick that run's Dataset in Source Dataset and start. Most Google Maps scrapers call the column website, which is the default. Our Google Maps B2B Leads Scraper calls it website_url, so set Website field in Dataset to that. A selected Dataset takes priority over the pasted websites.

To run it every time the scraper finishes, open the scraper or its Task, go to Integrations, connect this Actor and use this input:

{
"datasetId": "{{resource.defaultDatasetId}}",
"websiteField": "website"
}

The integration fills the placeholder when the scraper's run succeeds. It also adds a payload object, which this Actor accepts and does not read. If the integration input has no datasetId, the run fails and says what to set, instead of enriching the sample websites saved in the input.

  • Places without a website are skipped and not charged. RUN_SUMMARY.datasetRowsWithoutWebsite counts them.
  • Several places on one domain, such as branches of a chain, become one row.
  • input keeps the website string from the scraper row, so you can join contacts back to the place by that value.
  • One run reads up to 2,000 rows. When more remain, RUN_SUMMARY.nextDatasetOffset is the datasetOffset for the next run.
  • A Dataset that cannot be read, or that has no column with that name, fails the run before any site is visited.

To hand a clean list to a CRM, run Dataset Cleaner on this run's Dataset. This input keeps companies with an email and writes each email once:

{
"datasetId": "{{resource.defaultDatasetId}}",
"transforms": [
{
"op": "keep",
"fields": ["input", "domain", "companyName", "primaryEmail", "homepageUrl", "status"]
}
],
"filters": [{"op": "notEmpty", "field": "primaryEmail"}],
"dedupeBy": ["primaryEmail"]
}

What you get

Each row is one registrable domain. example.com, www.example.com and https://example.com/?utm_source=x are the same target. The original string stays in input. domain is the public-suffix domain. homepageUrl is the final homepage after ordinary redirects.

Emails include sourceUrl, sourceType, role and mxStatus. mxStatus is HAS_MX, NO_MX, DNS_FAILED or NOT_CHECKED. Phones are parsed numbers, not raw digit strings. Social profiles are organization links the site published. Share, intent and login URLs are left out. address is the postal address from the site's Organization or LocalBusiness structured data, or its first HTML address block, and is null when the site publishes none.

Status

StatusMeaningCharged
SUCCESSPages loaded and at least one email, phone or social profile was foundYes
NO_CONTACTSPages loaded and none of those were foundYes
PARTIALAt least one page loaded, and a later page failedYes
BLOCKEDHTTP 401, 403, 429, a challenge page, or robots.txt disallows the homepage or the page a redirect landed on. A disallowed subdomain page is skipped and does not block the domainNo
DOWNLOAD_FAILEDDNS, timeout, or HTTP 5xx after retriesNo
INVALID_DOMAINNot a public http(s) website, including private and metadata addressesNo
NOT_INSPECTEDThe spending limit or the run's time limit was reached before this domain was saved. error says whichNo
INTERNAL_ERRORThe Actor itself failed on this domain. This is not a problem with the siteNo

One bad domain does not fail the batch. The run fails only when the input envelope is invalid, the selected source Dataset cannot be read or has no column named by websiteField, the output Dataset cannot be written, billing cannot be reconciled, or every domain the run reached ended INTERNAL_ERROR. In that last case, open an Issue with the run URL.

Run it again

Run the same saved list next week when you want a fresh observation, or paste the next batch of domains from your CRM. fingerprint changes when the published contacts change. An MX result that flips between HAS_MX and DNS_FAILED does not change it. It is set only for SUCCESS and NO_CONTACTS, where the selected pages were read. PARTIAL, BLOCKED, DOWNLOAD_FAILED, INVALID_DOMAIN, NOT_INSPECTED and INTERNAL_ERROR rows have fingerprint: null, so a week the site could not be read never looks like a contact change. Keep the last complete row in that case. A weekly schedule of a domain list is the usual second run. There is no separate monitor mode in this version.

{
"websites": ["python.org", "https://www.iana.org/"]
}

In Apify, open Schedules and run that input once a week. On the run's webhook, choose a successful finish and post the Dataset to your CRM or sheet. Compare fingerprint with the previous row for the same domain.

What changed is in the CHANGELOG.md.

Limits

Up to 2,000 domains, or 2,000 source Dataset rows, per run. The default spending limit of $1 covers about 495 inspected domains. For a full 2,000-domain list, raise maxTotalChargeUsd to $4.01. Domains past the limit are NOT_INSPECTED and not charged. A run also stops starting new domains two minutes before its timeout, so a long list ends with the inspected rows saved and the rest NOT_INSPECTED, and timeLimitReached is true in RUN_SUMMARY. On staging, 100 large organization sites took about 2.5 minutes at the default 512 MB. For lists of more than 1,000 large sites, give the run more memory or time, or split the list. Each site is the homepage plus a few same-site contact, about, impressum or support links, at most 8 pages and 5 by default. The Actor does not crawl the whole site.

It follows redirects, including http to https. If the bare domain does not serve a page, the www host is tried, and a www host that redirects to the apex is followed. Redirects into private, loopback or link-local addresses are refused. If the homepage lands on a different registrable domain, only that landing page is read. A later link that leaves the domain is skipped. Residential proxy is not part of the product. Leave proxy disabled unless a datacenter Apify Proxy is specifically required. When that proxy is on, the connection uses the address from the DNS check, not a later answer for the same name. If the proxy cannot be created, the run continues over a direct connection and says so.

If robots.txt disallows a URL for a normal browser user agent, that URL is not read. This is checked for the homepage, for a host reached by a redirect, and separately for a subdomain. A disallow of the homepage or of the landing page is BLOCKED, and that row is not charged. A disallow of a later subdomain page skips that page only. The skipped URL is listed in robotsSkipped, and the row stays SUCCESS or NO_CONTACTS.

Pricing

EventPriceWhen
Actor start$0.01Once per run at 512 MB
Domain inspected (domain-processed)$0.002Once per unique domain that was actually inspected

NO_CONTACTS is charged because the pages were fetched and read. Duplicates, invalid inputs, blocked sites, download failures, retries and spending-limit leftovers are not charged. The event price includes platform usage.

Privacy and lawful use

The Actor collects contact details that the submitted website displays in public HTML, mailto: and tel: links, or structured data. It does not enter accounts, bypass access controls, or enrich people from a third-party database.

You are responsible for the lawful basis of any later outreach and for anti-spam and privacy rules that apply to you. A public page does not, by itself, make every later use lawful. Do not submit websites you are not allowed to request.

MCP

Agents can call this Actor directly:

https://mcp.apify.com?tools=automa-flow/website-contact-enricher

Ask it to turn a list of company domains into one contact record per domain, including the source page for each email and the MX status. Read RUN_SUMMARY before treating a partial batch as complete. Direct execution uses the caller's Apify account. Anonymous search is a separate discovery check and does not run the Actor.

Fields

examples/sample_output.json shows the row shape. It is an illustration of the fields, not a captured live run. schemaVersion is 1. Added fields will not rename these.