# Website Contact Extractor - Emails, Phones & Socials (`theprojectdesk/website-contact-extractor`) Actor

Give it a list of websites, get back the emails, phone numbers and social profiles published on each one. Built to run straight after a Google Maps or directory scrape.

- **URL**: https://apify.com/theprojectdesk/website-contact-extractor.md
- **Developed by:** [Project Desk](https://apify.com/theprojectdesk) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$20.00 / 1,000 website processeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Contact Extractor — emails, phone numbers and social profiles

Give it a list of websites. Get back the **email addresses, phone numbers and social
profiles** each one publishes — as a clean table you can export to CSV, Excel, JSON, or
push straight into your CRM.

It is built to run **immediately after a Google Maps, directory or search scrape**. Those
give you a company name and a website. They do not give you a way to reach anyone. This
closes that gap.

***

### What it does

For every website you give it, this Actor:

1. Fetches the homepage.
2. Finds the pages most likely to carry contact details — *contact*, *about*, *impressum*,
   *team*, *support* — and fetches those too.
3. Pulls out every email address, phone number and social profile it can find.
4. Cleans the result: no image filenames mistaken for emails, no Sentry or Wix tracker
   addresses, no `noreply@`, no share-button links pretending to be the company's Facebook page.

#### Things most extractors miss, and this one handles

- **Cloudflare-obfuscated emails** (`data-cfemail`) — decoded, not skipped. These are addresses
  a human sees in the browser and a naive scraper never returns.
- **Written-out addresses** — `sid [at] example [dot] com`, `sid(at)example(dot)com`.
- **Phone numbers in E.164** — `+442079460958`, not `020 7946 0958 (opt 2)`. Validated with
  Google's `libphonenumber`, so years, VAT numbers and prices do not come back as phones.
- **Role vs personal emails** — `info@` and `sales@` are separated from `firstname.lastname@`,
  in their own output columns, so you can route them differently.
- **Expired SSL certificates** — small-business sites are full of them. Handled by default
  instead of failing the row.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | — | The websites. Paste them, upload a CSV, or link the dataset from another Actor. Bare domains (`example.com`) are fine. |
| `followContactPages` | boolean | `true` | Also check the contact/about pages. Leaving this on roughly triples the hit rate. |
| `maxPagesPerSite` | integer | `3` | Homepage plus this many contact-ish pages. |
| `defaultPhoneRegion` | string | — | Two-letter code (`US`, `GB`, `IN`, `DE`) for reading local numbers written without a country code. |
| `maxConcurrency` | integer | `10` | Websites handled at once. |
| `respectRobotsTxt` | boolean | `true` | Skip pages the site asks crawlers not to fetch. |
| `ignoreSslErrors` | boolean | `true` | Keep going on expired or mismatched certificates. |
| `proxyConfiguration` | object | off | Optional. Most sites do not need one; switch it on if you hit blocks at volume. |

**Minimal input:**

```json
{
  "startUrls": [
    { "url": "https://www.apify.com" },
    { "url": "hubspot.com" }
  ]
}
```

***

### Output

One row per website.

```json
{
  "website": "https://decathlon.in/",
  "domain": "decathlon.in",
  "status": "ok",
  "totalContacts": 5,
  "title": "Buy Sporting Goods, Sportswear and Equipments | Download App",
  "emails": [
    "care.india@decathlon.com"
  ],
  "emailsRole": [
    "care.india@decathlon.com"
  ],
  "emailsPersonal": [],
  "phones": [],
  "socials": {
    "twitter": [
      "https://x.com/Decathlon_ind"
    ],
    "facebook": [
      "https://www.facebook.com/decathlonindia"
    ],
    "instagram": [
      "https://www.instagram.com/decathlonsportsindia"
    ],
    "youtube": [
      "https://www.youtube.com/@decathlon_india"
    ]
  },
  "pagesCrawled": [
    "https://decathlon.in/",
    "https://decathlon.in/support/hc"
  ],
  "error": null
}
```

`status` is one of:

| Status | Meaning |
|---|---|
| `ok` | At least one contact was found. |
| `no-contacts-found` | The site was read fine and publishes nothing reachable. |
| `failed` | The site could not be read — see `error` (`http-403`, `timeout`, ...). |
| `skipped-by-robots` | The site's robots.txt asks crawlers not to fetch that page. |

***

### Pricing

**Pay per website processed.** One charge per website you hand in, whatever comes back — the
fetching happens either way, and a site that publishes nothing costs the same work as one that
publishes ten addresses. No subscription, no minimum, no charge for a run you cancel.

If a website you care about returns `failed`, re-run just that one with the proxy switched on.

***

### Honest limits

- **Only what is published.** This reads the public website. It does not guess addresses, buy
  them from a database, or verify that an inbox accepts mail.
- **No JavaScript rendering.** Contact details behind a JS-only widget or a "reveal email"
  button will not be found. The vast majority of business sites put them in the HTML.
- **Hard-blocked sites.** A few sites return 403 to anything that is not a browser. Turn the
  proxy on for those.
- **Social links are read off the site's own pages.** No request is ever made to LinkedIn,
  Instagram or any other platform, and nothing behind a login is touched.

***

### Common pairings

- **Google Maps Scraper → this** — turn a list of local businesses into a contactable list.
- **Any directory or search scrape → this** — the same last mile.
- **Your own customer list → this** — refresh contact details that have gone stale.

***

### Compliance note

Scraping publicly published business contact details is legal in most jurisdictions, but
**what you do next is regulated** — GDPR, CAN-SPAM, CASL and India's DPDP all govern
unsolicited outreach. Personal addresses (`firstname.lastname@`) carry more obligations than
role addresses (`info@`), which is exactly why this Actor separates them for you. Check your
own obligations before you send.

# Actor input Schema

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

The websites to check. Paste them, upload a CSV, or link the dataset from a Google Maps / directory scrape. Bare domains like `example.com` are fine.

## `followContactPages` (type: `boolean`):

Most sites keep the address one click away from the homepage. Leaving this on roughly triples how many contacts are found.

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

The homepage plus this many contact-ish pages. 3 is the sweet spot; higher costs more compute for little extra.

## `defaultPhoneRegion` (type: `string`):

Two-letter country code (US, GB, IN, DE...) used to read local numbers written without a country code. Leave empty to guess.

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

How many websites to work on at once. Lower it if you are being rate-limited.

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

Skip pages the site asks crawlers not to fetch. On by default, and worth leaving on.

## `ignoreSslErrors` (type: `boolean`):

Small-business sites often have an expired or mismatched certificate. On by default so those sites still return contacts.

## `proxyConfiguration` (type: `object`):

Optional. Most sites do not need one; turn it on if you hit blocks at high volume.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.apify.com"
    }
  ],
  "followContactPages": true,
  "maxPagesPerSite": 3,
  "defaultPhoneRegion": "",
  "maxConcurrency": 10,
  "respectRobotsTxt": true,
  "ignoreSslErrors": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `contacts` (type: `string`):

Every row as JSON - emails, phones and social profiles, one record per website.

## `contactsCsv` (type: `string`):

The same rows as a spreadsheet, ready for a CRM import.

# 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": [
        {
            "url": "https://www.apify.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("theprojectdesk/website-contact-extractor").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": [{ "url": "https://www.apify.com" }] }

# Run the Actor and wait for it to finish
run = client.actor("theprojectdesk/website-contact-extractor").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": [
    {
      "url": "https://www.apify.com"
    }
  ]
}' |
apify call theprojectdesk/website-contact-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,theprojectdesk/website-contact-extractor"
        }
    }
}

```

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/2j4yo9PaL44sYHCXM/builds/o6exhBepN7SlDbZHU/openapi.json
