# Website Contact Info Scraper: Emails, Phones & Social Links (`charlesmarkette/website-contact-info-scraper-emails-phones-socials`) Actor

Get the emails, phone numbers (E.164), social profiles, address and company name that company websites publish. Reads the home page and contact pages, decodes Cloudflare-protected emails, ranks on-site contacts first. One row per website; enrich any lead dataset.

- **URL**: https://apify.com/charlesmarkette/website-contact-info-scraper-emails-phones-socials.md
- **Developed by:** [Charles Markette](https://apify.com/charlesmarkette) (community)
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 scanned websites

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Website Contact Info Scraper** visits company websites and returns the contact details each one publishes: **email addresses**, **phone numbers** (formatted as E.164), **social profiles** (LinkedIn, X/Twitter, Facebook, Instagram, YouTube, TikTok, GitHub, Pinterest, Threads), the **postal address** and the **company name**. It reads the home page first, then the pages most likely to hold contact details (Contact, Impressum, About, Team, Support, Legal, Locations, in several languages), up to the page budget you set. You get **one row per website**, with the page each email and phone was found on. Paste a list of websites, or point it at any Apify dataset of leads or companies and the contact columns are added next to each item.

### Why this Actor

- **Reads more than plain text.** `mailto:` links, plain-text addresses, **Cloudflare-protected emails** (`[email protected]` placeholders are decoded), simple obfuscations such as `name [at] company [dot] com` or `name(at)company.com`, `tel:` links, and the company's own **schema.org JSON-LD** (Organization / LocalBusiness: name, phone, email, address, social profiles).
- **Filters the junk.** Image file names that look like emails (`logo@2x.png`), placeholders (`you@example.com`), error-tracking addresses (Sentry, Wix), no-reply addresses and other non-contacts are dropped. Phone numbers in page text are only taken from the home page and contact-like pages, and only when a label such as *Phone*, *Tel*, *Call*, *Fax*, *Mobile*, *Telefon* or *Tél* is right in front of them, so order numbers, prices and IDs aren't reported as phones. Every phone is format-checked with Google's libphonenumber (a valid number format, not proof the line works) and returned in E.164 (`+14155550132`).
- **Profile links, not share buttons.** Share buttons, tweet intents, Facebook sharer and plugin links, posts and videos, and the platforms' bare home pages are left out. Profile URLs are cleaned (tracking parameters and trailing slashes removed) and grouped by platform.
- **Polite.** It reads `robots.txt` and skips disallowed pages (you can turn that off), sends at most 2 requests at a time to a website, retries busy servers with backoff, and names itself in its User-Agent (`ContactInfoScraper/1.0; +https://apify.com/charlesmarkette`).
- **Best contacts first.** Emails on the website's own domain come first, then emails seen on contact-like pages, then the rest; phones from `tel:` links and the site's JSON-LD come first. `primaryEmail` and `primaryPhone` give you the single best pick for each website.
- **One clear row per website.** Websites that can't be read get a row too, with `status: "failed"` and a plain-English `error`, and they are free.

### Use cases

- **Lead enrichment:** add emails, phones and LinkedIn pages to a list of company websites from Google Maps, a directory or a CRM export.
- **Sales and partnership outreach:** find the published sales, support or press address of each prospect.
- **Data cleaning:** check which companies in your list publish a phone number or address, and where.
- **Market research:** see which social platforms the companies in a niche are on.

### How to use it

1. Add **Websites** (home page URLs or bare domains such as `example.com`), and/or pick a **Dataset to enrich** and set **URL field** to the field that holds the website (dotted paths such as `company.website` work).
2. Optionally set **Max pages per website** (default 6) and **Default country for phone numbers** (default `US`; used for numbers written without a `+` country code).
3. Run it. Export the results as CSV, Excel or JSON, or chain the run into an integration.

**Input example**

```json
{
  "startUrls": ["https://www.python.org", "apify.com"],
  "maxPagesPerSite": 6,
  "defaultCountry": "US",
  "respectRobotsTxt": true
}
```

**Output example** (illustrative)

```json
{
  "url": "acme-widgets.com",
  "domain": "acme-widgets.com",
  "companyName": "Acme Widgets LLC",
  "primaryEmail": "sales@acme-widgets.com",
  "primaryPhone": "+14155550132",
  "emails": ["info@acme-widgets.com", "sales@acme-widgets.com"],
  "emailsOnSiteDomain": ["info@acme-widgets.com", "sales@acme-widgets.com"],
  "phones": ["+14155550132", "+442079460958"],
  "socials": {
    "linkedin": ["https://www.linkedin.com/company/acme-widgets"],
    "x": ["https://x.com/AcmeHQ"],
    "facebook": ["https://www.facebook.com/acmewidgets"],
    "instagram": [],
    "youtube": ["https://www.youtube.com/@acmevideos"],
    "tiktok": [],
    "github": [],
    "pinterest": [],
    "threads": []
  },
  "address": "1 Main St, Springfield, IL 62701, US",
  "addressParts": {"streetAddress": "1 Main St", "addressLocality": "Springfield", "addressRegion": "IL", "postalCode": "62701", "addressCountry": "US"},
  "contactPageUrls": ["https://acme-widgets.com/contact-us", "https://acme-widgets.com/about"],
  "pagesScanned": 6,
  "sources": {
    "info@acme-widgets.com": "https://acme-widgets.com/",
    "sales@acme-widgets.com": "https://acme-widgets.com/contact-us",
    "+14155550132": "https://acme-widgets.com/contact-us",
    "+442079460958": "https://acme-widgets.com/about"
  },
  "status": "ok",
  "error": null,
  "scannedAt": "2026-10-16T12:00:00Z"
}
```

- `emails` are ranked: addresses on the website's own registered domain first (subdomains count), then addresses seen on contact-like pages, then the rest. `emailsOnSiteDomain` is the on-domain subset. `phones` are ranked the same way: `tel:` links and JSON-LD first, then labelled numbers on contact-like pages, then labelled numbers on the home page.
- `primaryEmail` is the first on-domain email seen on a contact-like page, else the first on-domain email, else `null`. `primaryPhone` is the first `tel:`/JSON-LD phone, else the first labelled phone on a contact-like page, else `null`.
- `companyName` comes from the site's JSON-LD organisation name, then its `og:site_name`, then the cleaned home page title.
- `address` and `addressParts` come from the site's JSON-LD (schema.org PostalAddress); they are empty when the site doesn't publish one there.
- `contactPageUrls` lists the scanned pages that looked like contact, imprint, about, team, support, legal or locations pages.
- `sources` maps each email and phone to the first page it was found on.
- A run summary (websites read and failed, emails and phones found, pages scanned, why the run stopped) is saved as `OUTPUT` in the key-value store.

### How much does it cost?

Pay per event: **$0.005 per website scanned** (one output row for a website where at least one page was read), **Apify platform usage included**, plus $0.005 per GB of Actor memory per run start (minimum one; $0.005 at the default 256 MB). Websites that couldn't be read at all are **free**: DNS errors, connection failures, bot-protection or error answers (403, 429, 503…), robots.txt disallowing the site, private or internal addresses, and input that isn't a website. A website that was read but publishes no contact details is still a scanned website and is charged.

**Apify Store discounts:** the price above is for Apify's Free plan. Paid plans pay less per 1,000 scanned websites: $4.50 on Starter, $4.00 on Scale, $3.50 on Business. The "from" price at the top of the Store page is the Business price.

**Worked examples:**

*Examples use the Free-plan rate. Where an example doesn't add it, each run also has its run-start charge, counted per GB of Actor memory (minimum one; one at the default memory); Apify Proxy (where used) is billed separately.*

- 1,000 company websites, all reachable: 1,000 × $0.005 = **$5.00**.
- 200 leads from a Google Maps run, 20 of which have a dead website: 180 × $0.005 = **$0.90**.

If you set a maximum cost per run, the Actor stops cleanly at that limit, and a run that is resumed continues after the rows already stored.

### FAQ and limits

#### What does it find, exactly?

Only what the website itself publishes on the pages it reads. It does **not** guess emails from name patterns, verify that inboxes exist, look anything up in third-party databases, log in, fill in forms, or bypass CAPTCHAs or bot protection.

#### Why did a website return few or no results?

- **JavaScript-only sites:** the Actor reads pages over plain HTTP, without a browser. Sites that build their content in the browser (some single-page apps and site builders) may show little or nothing to it.
- **Contact forms only:** many companies publish a form instead of an email address.
- **Page budget:** with the default of 6 pages, contact-like pages are read first; raise **Max pages per website** (up to 30) for large sites.
- **Images:** emails or phones shown as pictures can't be read.

#### Why is a website "failed"?

The `error` column says why, in plain English: for example the domain doesn't resolve, the site answered 403 (usually bot protection), or robots.txt disallows crawlers. Failed websites don't incur the website-scanned charge; a new run still has its start fee.

#### The results include other companies' emails or phones. Why?

Some websites show sample or customer data on their pages: demo tables, example scrapes, case studies, partner lists. The Actor reports what the pages publish, but ranks such addresses after the site's own and never uses them for `primaryEmail`. Turn on **Only emails on the website's own domain** (`onlySiteDomainEmails`) to drop every email that isn't on the website's registered domain (subdomains count, and so do domains of the email addresses in the site's own JSON-LD organisation data). Labelled phone numbers in page text are only taken from the home page and contact-like pages for the same reason.

#### Which phone numbers are kept?

Numbers in `tel:` links and in the site's JSON-LD `telephone` (from any page read), and numbers in the text of the home page and contact-like pages that follow a phone label. Each must be a valid number for its country; numbers without a `+` country code are read with your **Default country**.

#### Does it leave the website?

No. It reads pages of the website's own host only (`www.example.com` and `example.com` count as one). Turn off **Stay on the website's own host** to also read subdomains such as `help.example.com`. Links to other sites are not followed, except that social profile links are collected.

### Use responsibly

This Actor collects **business contact details that websites publish publicly**. How you use them is up to you and the law where you and your contacts are: privacy and anti-spam rules such as the **GDPR** (EU/UK) and **CAN-SPAM** (US) apply to storing and emailing personal data, including work addresses of named people. Respect opt-outs, keep only what you need, and don't use the results for unsolicited bulk messaging where that's not allowed.

### Other tools by the same author

| Actor | Use it to |
|---|---|
| [Phone Number Validator & Formatter](https://apify.com/charlesmarkette/phone-number-validator-formatter-bulk-e164) | Get the type (mobile, landline), carrier, region and national format of the phones found. |
| [Domain WHOIS & DNS Lookup](https://apify.com/charlesmarkette/domain-whois-dns-lookup-bulk-rdap) | Add domain age, registrar and email provider (Google Workspace, Microsoft 365…) to the same list. |
| [Bulk URL Status Checker](https://apify.com/charlesmarkette/bulk-url-status-checker-redirects-ssl) | Check first which lead websites load, where they redirect and whether their SSL certificate is valid. |
| [Merge & Deduplicate Datasets](https://apify.com/charlesmarkette/merge-deduplicate-datasets-only-new-items) | Keep only new companies between runs before scanning them. |
| [Dataset Join & VLOOKUP](https://apify.com/charlesmarkette/dataset-join-vlookup-merge-by-key) | Join the contact rows back onto your original lead list by website or domain. |
| [Google Sheets Sync](https://apify.com/charlesmarkette/google-sheets-sync) | Send the contact rows to a Google Sheet. |
| [Send Email Report & Alerts](https://apify.com/charlesmarkette/send-email-report-slack-discord-teams-telegram-alerts) | Get the results by email, Slack, Discord, Teams or Telegram when the run finishes. |
| [Broken Link Checker](https://apify.com/charlesmarkette/broken-link-checker-crawl-website-404) | Crawl a company's whole site for broken links and redirects. |
| [Website Tech Stack Detector](https://apify.com/charlesmarkette/website-tech-stack-detector-shopify-cms-analytics) | See which store platform, CMS and marketing tools each company website uses. |
| [Career Page Jobs Scraper](https://apify.com/charlesmarkette/career-page-jobs-scraper-greenhouse-lever-ashby) | See which companies on your list are hiring, and for what. |

Phone parsing: [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) (Google's libphonenumber). Domain parsing: [tldextract](https://github.com/john-kurkowski/tldextract) with the Public Suffix List.

# Actor input Schema

## `startUrls` (type: `array`):

One per line. Home page URLs or bare domains (example.com becomes https://example.com/). Each website becomes one output row.

## `datasetId` (type: `string`):

Optional. A dataset of companies or leads (from Google Maps, a directory scraper, a CSV import…). Each item's website (see URL field) is scanned and the contact columns are added next to the item's fields. Use {{resource.defaultDatasetId}} in an integration.

## `urlField` (type: `string`):

The item field that holds the website. Dotted paths work (company.website). If it holds a list, the first URL is used.

## `keepItemFields` (type: `boolean`):

On: each output row is the original item plus the contact columns. Off: only the contact columns.

## `maxPagesPerSite` (type: `integer`):

Pages read per website, home page included (1–30). Contact, imprint, about, team, support, legal and locations pages are read first; the rest of the budget goes to other pages of the site.

## `defaultCountry` (type: `string`):

Two-letter ISO country code (US, GB, DE…) used to read phone numbers written without a + country code. Numbers with a + code are read as written.

## `onlySiteDomainEmails` (type: `boolean`):

Drop email addresses that aren't on the website's registered domain (subdomains and www count as the same domain; domains of the email addresses in the site's own JSON-LD organisation data are kept too). Turn this on for sites that show sample or customer data, such as demo tables or example scrapes.

## `respectRobotsTxt` (type: `boolean`):

Don't read pages that the website's robots.txt disallows for crawlers.

## `sameDomainOnly` (type: `boolean`):

On: only pages on the website's host are read (www.example.com and example.com count as one). Off: subdomains of the same domain too (help.example.com, shop.example.com).

## `maxConcurrency` (type: `integer`):

How many websites to scan at the same time (1–20). Each website still gets at most 2 requests at a time.

## `maxResults` (type: `integer`):

Optional cap on output rows (each row is one website).

## Actor input object example

```json
{
  "startUrls": [
    "https://www.python.org",
    "https://apify.com"
  ],
  "urlField": "url",
  "keepItemFields": true,
  "maxPagesPerSite": 6,
  "defaultCountry": "US",
  "onlySiteDomainEmails": false,
  "respectRobotsTxt": true,
  "sameDomainOnly": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `items` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.python.org",
        "https://apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("charlesmarkette/website-contact-info-scraper-emails-phones-socials").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "startUrls": [
        "https://www.python.org",
        "https://apify.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("charlesmarkette/website-contact-info-scraper-emails-phones-socials").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.python.org",
    "https://apify.com"
  ]
}' |
apify call charlesmarkette/website-contact-info-scraper-emails-phones-socials --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,charlesmarkette/website-contact-info-scraper-emails-phones-socials"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Cpk45DfIYShta9yE8/builds/W0GOv6h8GvL2elTdV/openapi.json
