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

Extract emails, phone numbers and social profile links (Facebook, Instagram, LinkedIn, X, TikTok, YouTube, WhatsApp, Telegram) from any list of websites. Plain HTTP, no browser, respects robots.txt. $0.80 per 1,000 pages scanned, no per-lead fee.

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

## Pricing

from $0.80 / 1,000 page scanneds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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 & Social Extractor (Emails, Phones, Socials)

Extract contact details from a list of company websites or bare domains. For each site the actor fetches the start page and up to 5 same-domain pages (configurable), then returns emails, phone numbers and social profiles in one dataset row per site. It is plain HTTP: no headless browser, no Playwright, no Puppeteer.

### What it finds

- **Emails** from `mailto:` links and visible text, including simple obfuscations such as `name [at] domain [dot] com` and Cloudflare `data-cfemail` values. Image file names, version strings and placeholder addresses such as `email@domain.com`, `you@email.com`, `name@example.com`, `@sentry` and `@wixpress.com` are filtered out.
- **Phone numbers** from `tel:` links and common international or national text formats. Each phone keeps the raw string and a normalised value.
- **Social profiles** for Facebook, Instagram, LinkedIn (company and `in/` profiles), X/Twitter, TikTok, YouTube, Pinterest, Threads, WhatsApp and Telegram. Share, intent and tracking links are removed.
- **JSON-LD** `Organization` and `LocalBusiness` values for `email`, `telephone` and `sameAs`.
- **Contact pages first**: links whose URL or anchor text mentions contact, about, team, support, legal, impressum, kontakt or hubungi are fetched before other same-domain pages.

### Input example

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

Bare domains such as `hartamas.com` work too. An empty input (`{}`) runs the default example, `https://apify.com`, so the actor always has something to do.

| Field | Default | What it does |
|---|---|---|
| `startUrls` | `[{"url": "https://apify.com"}]` | Website URLs or bare domains. One row per domain; duplicate domains are collapsed. |
| `maxPagesPerSite` | `5` | Pages fetched and scanned per site, including the start page. Range 1 to 50. |
| `maxItems` | `10` | Global cap on website rows. The default keeps a default run under $0.05 in page charges at 5 pages per site. Range 1 to 1,000. |
| `concurrency` | `10` | Sites processed at the same time. Pages within one site are fetched one at a time. Range 1 to 50. |
| `outputPerPage` | `false` | Also push one row per fetched page. Page rows are not charged separately. |
| `respectRobotsTxt` | `true` | Check `robots.txt` before fetching a page and skip disallowed URLs. |
| `proxyConfiguration` | `{ "useApifyProxy": false }` | Direct requests by default. Enable Apify datacenter or residential proxy if target sites block Apify IP ranges. A proxy failure fails the request and never falls back to a direct request. |

### Output example

This is a real site row from the example input.

```json
{
  "rowType": "site",
  "inputUrl": "https://apify.com",
  "domain": "apify.com",
  "pageUrl": null,
  "pageTitle": null,
  "status": "success",
  "emails": ["hello@apify.com"],
  "phones": [],
  "socials": {
    "facebook": [],
    "instagram": [],
    "linkedin": ["https://linkedin.com/company/apify"],
    "x": ["https://x.com/apify"],
    "tiktok": ["https://tiktok.com/@apifytech", "https://tiktok.com/@apifyoffice"],
    "youtube": ["https://youtube.com/apify"],
    "pinterest": [],
    "threads": [],
    "whatsapp": [],
    "telegram": []
  },
  "pagesScanned": 5,
  "pagesWithContacts": [
    "https://apify.com/",
    "https://apify.com/about",
    "https://apify.com/contact",
    "https://apify.com/contact-sales",
    "https://apify.com/actors"
  ],
  "httpStatus": null,
  "error": null,
  "scrapedAt": "2026-10-24T04:53:14.503Z"
}
```

The same run returned a `hartamas.com` row with 9 emails (including `info@hartamas.com` and `asset@hartamas.com`), 12 phone entries and 7 social profiles. Phone entries have this shape:

```json
{ "raw": "+603 7839 5617", "normalised": "+60378395617" }
```

#### Row fields

| Field | Meaning |
|---|---|
| `rowType` | `site` for one website row, `page` for an optional per-page row. |
| `inputUrl` | The URL or domain exactly as supplied in `startUrls`. |
| `domain` | The website domain, with a leading `www.` removed. |
| `pageUrl`, `pageTitle` | The page URL and HTML title on a page row. `null` on a site row. |
| `status` | `success` when contacts were found, `no-contacts` when pages were scanned but nothing was found, `error` when the site could not be scanned. |
| `emails` | Deduplicated, lowercase email addresses. Junk and placeholder addresses are removed. |
| `phones` | Deduplicated `{ raw, normalised }` objects. `normalised` keeps a leading `+` when the source had an international prefix, otherwise it contains national digits. |
| `socials` | Profile URLs grouped by network. Every supported network is present, even when its array is empty. |
| `pagesScanned` | Pages successfully fetched as HTTP 200 HTML and scanned. `1` on a page row. |
| `pagesWithContacts` | URLs of the scanned pages that contained at least one email, phone or social profile. |
| `httpStatus` | `200` on a page row, `null` on a site row. |
| `error` | A readable failure reason on an error row, `null` otherwise. |
| `scrapedAt` | ISO timestamp for the row. |

With `outputPerPage: true`, the dataset also gets one row per successfully fetched page. Page rows have `rowType: "page"`, `pageUrl`, `pageTitle`, the page's own emails, phones and socials, and `httpStatus: 200`. They are not charged separately.

### Pricing

| Event | Price | Charged when |
|---|---|---|
| `page-scanned` | $0.0008 per page ($0.80 per 1,000 pages) | A page is successfully fetched as HTTP 200 HTML and scanned. Charged once per site row after that row is pushed, with `count` equal to the site's `pagesScanned`. |

- Error rows are never charged.
- A `no-contacts` row is a completed scan, so it is charged for the pages that were actually scanned. The row still tells you that the site published no emails, phones or supported social profiles.
- There is no per-lead, per-email, per-phone or per-social-profile fee.
- Platform usage (compute, proxy, storage) is paid by you, the buyer.
- The example input (2 sites, up to 5 pages each) scans at most 10 pages, which is at most $0.008 in page charges. A default `maxItems` run caps page charges at $0.04 (10 sites x 5 pages x $0.0008) before platform usage.

### How this compares

| Actor | Base price | Extra fees | Platform usage |
|---|---|---|---|
| `subimpact/website-contact-extractor` | $0.80 per 1,000 pages | None per lead or per profile | Paid by you |
| `vdrmota/contact-info-scraper` | $2.00 per 1,000 pages | $0.10 per lead and $0.10 per social profile on its enrichment events | see listing |
| `lukaskrivka/google-maps-with-contact-details` | $5.00 per 1,000 places | see listing | see listing |

`vdrmota/contact-info-scraper` had 3,694 users in the last 30 days at the time of checking. It is a general contact scraper. `lukaskrivka/google-maps-with-contact-details` starts from Google Maps search terms rather than website URLs, so its input and results are different from this actor.

Our base page price is $0.80 per 1,000 pages, below the $2.00 per 1,000 page base price of `vdrmota/contact-info-scraper`, and we do not add a per-lead or per-social-profile fee. Compare field coverage and result quality against those listings before choosing on price alone.

Prices are Apify Store list prices on the Free plan, checked 24 October 2026. Competitor prices can change. Check their listings.

### How it works

The actor sends plain HTTP GET requests with a browser-compatible `Accept` header and a clear bot user agent. It follows redirects, accepts only `text/html` responses, caps each response at 3 MB and times out a request after 20 seconds. Transient failures (timeouts, network errors, 429 and 5xx) are retried up to three times with a fresh proxy session. A 404 is not retried. Pages are parsed without a DOM engine: links, visible text, JSON-LD blocks and `data-cfemail` attributes are read directly from the HTML.

Direct requests were measured to work for the example sites on Apify on 24 October 2026, so direct is the default. Enable a proxy only if a target site blocks the Apify IP ranges.

### Limits and things to know

- At most 50 pages per site and 1,000 site rows per run. The defaults are 5 pages and 10 sites.
- Only HTTP 200 HTML pages are scanned. PDFs, images, JavaScript bundles and other file types are skipped.
- There is no JavaScript rendering. Contact details injected by client-side JavaScript into the page after load may be missed.
- Text phone numbers without a `+`, spaces, dashes, dots or brackets are only taken from `tel:` links and JSON-LD. This avoids treating order numbers, dates and coordinates as phones.
- Emails and phones are capped at 100 per site. Social profiles are capped at 25 per network.
- The social extractor covers Facebook, Instagram, LinkedIn, X/Twitter, TikTok, YouTube, Pinterest, Threads, WhatsApp and Telegram. Other networks that appear in JSON-LD `sameAs` are ignored.
- There is no form submission, login, CAPTCHA solving or email deliverability check. An address may be stale or undeliverable.
- Only public `http` and `https` hosts are accepted. Local and private network addresses are rejected.
- `robots.txt` is respected by default. A site that disallows its start page produces an error row and is not charged.

### Responsible use

Use this actor only on websites you are allowed to access. Keep `respectRobotsTxt` on unless you own the site or have permission to crawl it. Follow each site's terms and applicable privacy law. Contact details can be personal data, so make sure you have a lawful basis before outreach and do not use the output for spam. The actor does not verify that an email address is deliverable or that a phone number is still in service.

# Actor input Schema

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

One entry per website. Full URLs and bare domains are accepted, for example https://apify.com or hartamas.com. The first URL per domain is used as the start page. Duplicate domains are collapsed.

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

Pages fetched and scanned per website, including the start page. Links whose URL or anchor text mentions contact, about, team, support, legal, impressum, kontakt or hubungi are fetched first. Higher values find more contacts and cost more page-scanned events.

## `maxItems` (type: `integer`):

Global cap on website rows delivered in one run. The default of 10 keeps a default run under $0.05 at 5 pages per site. Raise it for larger lists. Optional per-page rows are not counted against this cap and are not charged separately.

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

How many websites are processed at the same time. Pages within one site are fetched one at a time. Lower this if target sites rate-limit you.

## `outputPerPage` (type: `boolean`):

Adds one dataset row per successfully fetched page with that page's own contacts. These page rows are not charged separately. Billing is still per page scanned, charged once after each site row.

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

Check each site's robots.txt before fetching pages and skip disallowed URLs. Keep this on unless you own the sites or have permission to crawl them.

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

Direct requests are the default and were measured to work for the example sites. Enable Apify datacenter or residential proxy only if target sites block the Apify IP ranges. A proxy failure fails the request and never falls back to a direct request.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://www.hartamas.com"
    }
  ],
  "maxPagesPerSite": 5,
  "maxItems": 10,
  "concurrency": 10,
  "outputPerPage": false,
  "respectRobotsTxt": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Website rows with emails, phones, social profiles, pages scanned, pages with contacts and an error field. Enable outputPerPage to add per-page rows.

# 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://apify.com"
        },
        {
            "url": "https://www.hartamas.com"
        }
    ],
    "maxPagesPerSite": 5,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("subimpact/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://apify.com" },
        { "url": "https://www.hartamas.com" },
    ],
    "maxPagesPerSite": 5,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("subimpact/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://apify.com"
    },
    {
      "url": "https://www.hartamas.com"
    }
  ],
  "maxPagesPerSite": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call subimpact/website-contact-extractor --silent --output-dataset

```

## MCP server setup

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