# Website Contacts Extractor — Email, Phone & Social Scraper (`cleandata-tools/website-contacts-extractor`) Actor

Contact details scraper and email extractor. Find public emails, phone numbers, and social profiles (LinkedIn, X, Facebook, Instagram, YouTube, GitHub) from a list of websites. Static HTML, robots.txt respected.

- **URL**: https://apify.com/cleandata-tools/website-contacts-extractor.md
- **Developed by:** [CleanData Tools](https://apify.com/cleandata-tools) (community)
- **Categories:** Lead generation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.00 / 1,000 site 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?

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 Contacts Extractor — email, phone, and social scraper

**Contact scraper** and **email extractor** for public websites. Pass a list of URLs. For each site the Actor reads the page you give it, plus a few same-site contact, about, impressum, and team links, and returns the emails, phone numbers, and social profiles it found, with the source page for each one.

It is a **website contact finder** for lead lists: homepages in, a table of contacts out.

### What one site returns

| Field | Meaning |
| --- | --- |
| `status` | `ok`, `blocked` (partial results after a 403, 429, or bot wall), `error` (the site could not be fetched), or `blocked_by_robots` (robots.txt disallows the page; nothing fetched). |
| `emails` | Address plus the pages it was found on. `emails_text` is the same list for the table. |
| `phones` | Display text, E.164 when the number includes a country code or the site country is known, and source pages. |
| `socials` | `linkedin`, `x`, `facebook`, `instagram`, `youtube`, `tiktok`, or `github`, with the profile URL and source pages. |
| `pages` | Every fetched page: URL, HTTP status, outcome (`ok`, `blocked`, `timeout`, `skipped_robots`, `skipped_login`, and others). |
| `source_pages` | Just the page URLs, for the table. |
| `notices` | Notes such as a page that was cut off because it was too long. |

An invalid URL or a failed call to the extractor is saved with `worker_error` and is **not** charged. A site that returns `blocked_by_robots` is saved and **not** charged. A site that returns `blocked` or `error` from the extractor **is** charged: the site was processed, and any partial contacts are in the row.

### Input

| Field | Default | What to put |
| --- | --- | --- |
| Website URLs | Two public example pages | Up to 10,000 http(s) URLs. A bare host such as `gnu.org` is read as `https://gnu.org`. |
| Concurrent sites | 4 | 1–10 sites at once. |
| Retries per site | 3 | Extra attempts after a timeout, HTTP 429, or HTTP 5xx. HTTP 400 is not retried. |

Example:

```json
{
  "urls": ["https://www.gnu.org/home.html", "https://nodejs.org/en/about"],
  "concurrency": 4
}
```

### Output

The **Contacts** table is one row per site: status, counts, and the email, phone, and social lists. **Source pages** shows which URLs were fetched and how each one ended. Full objects are on the dataset item for JSON and CSV export. A run summary is stored as the `OUTPUT` record in the key-value store.

### Pricing

Pay per event. One charge per site the extractor finishes. You are not charged platform compute on top, and you are not charged when the extractor call itself fails.

| Event name | What it is | Price to set in Console |
| --- | --- | --- |
| `site-processed` | One website extracted | **$0.004** |
| `apify-actor-start` | Platform start event. Do not charge this from the Actor. | Leave the default **$0.00005** |

250 sites are **$1.00**. 1,000 sites are **$4.00**.

[vdrmota/contact-info-scraper](https://apify.com/vdrmota/contact-info-scraper) is the large contact scraper on the Store, priced from **$1.05 per 1,000 scraped pages** ($0.00105 per page). That Actor bills every page it crawls. This Actor bills **once per site**. The extractor fetches the URL you pass plus up to four contact, about, impressum, or team links, so a typical site is about two to five page fetches, roughly $0.002 to $0.005 on the per-page price. **$0.004 per site** sits in that band as a single line item. Copy the numbers from `.actor/pay_per_event.json` into the Monetization tab. The dollar amount is not hard-coded in the source.

Set the minimum run budget (`minimalMaxTotalChargeUsd`) to **$0.005** so a run can pay for the start event and one site. When the run hits its maximum charge, the Actor saves the last billed site and skips the rest.

### Honest limits

- **Static HTML only.** A React, Vue, or Angular page that injects the address after load returns an empty list. There is no headless browser in this Actor.
- The fetcher identifies itself with an honest User-Agent and **obeys robots.txt**. A page skipped for robots, a login wall, or a non-HTML type is reported on `pages` and is not silently retried with a different identity.
- When a page answers 403 or 429, or shows a bot challenge, `status` is `blocked` and any contacts already found are kept.
- Phone numbers are E.164 only when the number includes a country code or the site country is clear. Otherwise `e164` is null and `display` still has the text.
- Social profiles are the seven networks listed above, not every icon on the page.
- This Actor does not verify that a found address can receive mail. There is no SMTP mailbox probing.
- One run accepts at most 10,000 URLs.

### Operator setup

Set these Actor environment variables only after the extractor expects a shared secret. Leave both unset until then. Set both, or neither.

| Variable | Example |
| --- | --- |
| `WORKER_AUTH_HEADER` | `X-Origin-Secret` |
| `WORKER_AUTH_VALUE` | the shared secret |

The header is sent on every extractor call and is never written to the dataset or the log.

### Local run

```bash
npm install
npm test
mkdir -p storage/key_value_stores/default
printf '%s\n' '{"urls":["https://www.gnu.org/home.html","https://example.com"]}' > storage/key_value_stores/default/INPUT.json
npm start
```

`npm start` sets `ACTOR_TEST_PAY_PER_EVENT=1` and `ACTOR_USE_CHARGING_LOG_DATASET=1`. Locally, each event is priced at $1 and appended to `storage/datasets/charging_log`. That log is not created on the Apify platform. `OUTPUT.charged_events` is the number of events the run actually billed. Publish from this folder with `apify push`.

# Changelog

This Actor's version history is a separate document: https://apify.com/cleandata-tools/website-contacts-extractor/changelog.md

# Actor input Schema

## `urls` (type: `array`):

Pages to read, usually homepages. A bare host such as gnu.org is treated as https://gnu.org. Up to 10,000 URLs per run. One dataset item and one charge per site that the extractor finishes.

## `concurrency` (type: `integer`):

How many sites to extract at the same time.

## `maxRetries` (type: `integer`):

Extra attempts after a timeout, HTTP 429, or HTTP 5xx, with exponential backoff. HTTP 400 is not retried. A failed site is saved with worker_error and is not charged.

## Actor input object example

```json
{
  "urls": [
    "https://www.gnu.org/home.html",
    "https://nodejs.org/en/about"
  ],
  "concurrency": 4,
  "maxRetries": 3
}
```

# Actor output Schema

## `results` (type: `string`):

One dataset item per site, with emails, phones, social profiles, and source pages.

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

Counts of sites processed and contacts found.

# 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 = {
    "urls": [
        "https://www.gnu.org/home.html",
        "https://nodejs.org/en/about"
    ],
    "concurrency": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("cleandata-tools/website-contacts-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 = {
    "urls": [
        "https://www.gnu.org/home.html",
        "https://nodejs.org/en/about",
    ],
    "concurrency": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("cleandata-tools/website-contacts-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 '{
  "urls": [
    "https://www.gnu.org/home.html",
    "https://nodejs.org/en/about"
  ],
  "concurrency": 4
}' |
apify call cleandata-tools/website-contacts-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cleandata-tools/website-contacts-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/ssEPSpHfSaOhUgjKS/builds/ZMWQhCBlrTEptF37G/openapi.json
