# Website Contact Scraper: Emails, Phones & Social Links (`leezx/website-contact-scraper`) Actor

Find emails, phone numbers and social profiles on company websites. Paste domains, get one clean row per site, read from the contact, about and team pages. Hidden emails decoded, phone numbers validated, dead mail domains removed. Pay only for sites that return a contact.

- **URL**: https://apify.com/leezx/website-contact-scraper.md
- **Developed by:** [Leo Lee](https://apify.com/leezx) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 leads

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 Contact Scraper: Emails, Phones & Social Links

Paste a list of company websites and get one clean row per site: email addresses, phone numbers in international format, social media links, company name and postal address when the site states one.

For each website the Actor opens the home page and the pages most likely to hold contact details (Contact, About, Team, Impressum, Support, Locations, and the same words in other languages). It reads plain HTML like a search engine does. No login, no browser.

### Use this Actor when you want to

- **Build a lead list** from a list of company domains you already have, for example from a directory, a trade show list or another scraper's output.
- **Fill in the contact columns** of a spreadsheet or CRM that only has website addresses.
- **Find the right contact page** and social profiles of many companies at once.
- **Feed contact data into your own tools** through the API, a schedule or an integration.

### How to use it

1. Paste the websites into **Websites**, one per line. `apify.com`, `www.apify.com` and `https://apify.com/contact` all work. A page deeper than the home page is read first, and the home page is read too.
2. Optionally set the country for phone numbers and how many pages to read per site.
3. Run. Download the results as JSON, CSV or Excel, or call the Actor from the API.

#### Example: contact details for three companies

```json
{
    "websites": ["apify.com", "www.sparkfun.com", "https://www.oxfam.org.uk"],
    "maxPagesPerSite": 5
}
```

#### Example: only websites with an email, German phone format for numbers without a country code

```json
{
    "websites": ["example-one.de", "example-two.de"],
    "requireEmail": true,
    "defaultCountry": "DE"
}
```

### Output

One website looks like this (made-up values, not a real company):

```json
{
    "url": "https://www.exampleco.com/",
    "domain": "exampleco.com",
    "status": "contacts_found",
    "companyName": "Exampleco",
    "email": "hello@exampleco.com",
    "emails": ["hello@exampleco.com", "jane.doe@exampleco.com", "press@exampleco.com"],
    "phone": "+442079460958",
    "phones": ["+442079460958", "+447911123456"],
    "phoneDetails": [
        { "number": "+442079460958", "international": "+44 20 7946 0958", "country": "GB", "type": "fixed_line" },
        { "number": "+447911123456", "international": "+44 7911 123456", "country": "GB", "type": "mobile" }
    ],
    "linkedin": "https://www.linkedin.com/company/exampleco",
    "facebook": "https://www.facebook.com/exampleco",
    "instagram": "https://www.instagram.com/exampleco",
    "twitter": "https://x.com/exampleco",
    "youtube": null,
    "tiktok": null,
    "pinterest": null,
    "github": null,
    "whatsapp": null,
    "telegram": null,
    "address": "1 Example Street, London, EC1A 1AA, United Kingdom",
    "contactPageUrl": "https://www.exampleco.com/contact",
    "pagesScanned": 4,
    "scannedAt": "2026-10-29T12:00:00.000Z"
}
```

- `email` and `phone` are the first (best) entry of `emails` and `phones`, handy for CSV exports. Addresses on the website's own domain come first, and general addresses (info@, contact@, hello@, sales@) come before people's addresses, which come before departments such as press@, legal@ or webmaster@. Addresses on other domains (sponsors, agencies, consultants listed on a page) are only returned when the website shows none of its own.
- `phones` holds numbers in international format without spaces. `phoneDetails` adds the country and the line type (`mobile`, `fixed_line`, `toll_free` and so on) when it can be told.
- Each social field holds the profile linked most often from the site, or `null`.
- `status` is `contacts_found` for every billed row. With "Also return websites where nothing was found" on, the other rows carry `no_contacts`, `http_error`, `unreachable`, `unreadable` or `blocked_by_robots`, plus an `error` text.
- At most 25 emails and 15 phone numbers are returned per website.

### What it finds

**Emails** from `mailto:` links, visible text, structured data on the page, addresses written as `name [at] company [dot] com`, addresses hidden with HTML entities and addresses protected by Cloudflare's email protection. It ignores placeholders (`you@example.com`), file names that look like emails (`logo@2x.png`), no-reply addresses and the noise inside scripts. With "Drop emails at domains that cannot receive mail" on (default), it also removes addresses whose domain does not exist or has no mail server. That check cannot tell whether the mailbox itself exists.

**Phone numbers** from `tel:` links, WhatsApp links, structured data and visible text. A number is kept only if it is a valid number for its country. Numbers labelled as fax are skipped, and so are dates, opening hours and other digits that merely look like a number. In visible text, a number written without a country code counts only when a word like Tel, Phone, Call or Mobile comes right before it. It is read with your default country, or with the country guessed from the website's address, or from the address on the page. If no country is known, only numbers that start with `+` or `00` are read.

**Social links** to LinkedIn, Facebook, Instagram, X (Twitter), YouTube, TikTok, Pinterest, GitHub, WhatsApp and Telegram. Share buttons and links to single posts are ignored.

### Good to know

- **No JavaScript:** pages that build their contact details with JavaScript after loading show nothing to a plain HTML read, so those sites return no result. Contact forms without an address are not leads.
- **Blocked sites:** some websites refuse requests from data centers. They show up as `http_error` (also for "Just a moment..." style bot-check pages) or `unreachable` in the results (with "Also return websites where nothing was found" on) and in the run summary (key `SUMMARY`), and cost nothing. An Apify Proxy in the Proxy setting can help; its traffic is billed to your account.
- **Pages that are gone:** if a web address deeper than the home page (an old link from a directory, say) returns an error, or robots.txt closes that one page, the Actor reads the home page of the same website instead.
- **Slow websites:** a website that does not answer within about 20 seconds is tried once more with a different kind of connection and then skipped. No website holds up the run for more than about 90 seconds, so one dead website never delays the others. If a website does not open as you typed it, the Actor also tries it with and without `www.` and over `http`.
- **robots.txt:** followed by default. A site that asks automated tools to stay away, or whose robots.txt answers with a server error, returns `blocked_by_robots`. A robots.txt that cannot be read at all (no answer within a few seconds, no connection) counts as no restrictions.
- **One row per website:** websites are matched by host name, so `acme.com` and `www.acme.com` are one site. Sub-domains such as `shop.acme.com` are separate sites.
- **Speed:** set mostly by how fast the websites answer. In one test, 44 company websites (up to 5 pages each) took 28 seconds with 20 read at a time. Raise "Websites read at the same time" (up to 50) to go faster, lower it if many websites time out.
- **Long lists:** the Actor stops starting new websites a little before the run's time limit, keeps everything found so far and says in the run summary (key `SUMMARY`) how many websites were left. Raise Timeout in the run options for very long lists.
- **Only the site's own pages:** the Actor follows links within the same website and nowhere else. It never searches the web for a company.

### Pricing

You pay per lead (see the pricing box on this page). A lead is a website where at least one email address or phone number was found. Websites where nothing was found, websites that could not be read and websites skipped by "Only keep websites with an email address" are not billed. If you set a maximum cost per run in the run options, the Actor stops when that limit is reached and says so in the run summary.

### Using the data responsibly

The Actor returns only what each website publishes publicly. Business contact details are still personal data when they name a person. Laws such as the GDPR and ePrivacy rules in Europe, CAN-SPAM in the US, the PDPA in Malaysia and Singapore and similar rules elsewhere decide who you may email or call, and what you must tell them. You are responsible for following them, and for the terms of the websites you scan. Do not use the results for spam.

This Actor is not affiliated with or endorsed by any website it reads or by any social network it recognises. Names are used only to say which links are recognised.

Found a bug or a website that does not work? Open an issue on this Actor's page and include the address.

### Changelog

- **1.0** First release: emails, phone numbers, social links, company name and address from company websites.

# Actor input Schema

## `websites` (type: `array`):

One website per line, for example <code>apify.com</code> or <code>https://www.example.com/contact</code>. Paste as many as you like. Each website gives one result, even if you list it twice.

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

The home page plus the pages most likely to hold contact details: Contact, About, Team, Impressum, Support, Locations and the same words in other languages. Higher finds more addresses and takes longer.

## `requireEmail` (type: `boolean`):

Skip websites where only a phone number was found. Skipped websites are not billed.

## `checkEmailDomains` (type: `boolean`):

Looks up each email's domain and removes addresses whose domain has no mail server or does not exist. It does not contact the mailbox itself.

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

Two-letter country code, for example <code>US</code>, <code>GB</code>, <code>DE</code>, <code>MY</code>. Used to read numbers written without a country code, like 020 7946 0958, when a word such as Tel or Phone comes before them. If empty, the country is guessed from the website's address (.de, .co.uk, .my) or from the address on the page. Numbers that start with + are always read.

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

Skips pages a website asks automated tools not to open. Recommended. Turn it off only if you have the right to read those pages.

## `returnSitesWithoutContacts` (type: `boolean`):

Adds a row with a status (no_contacts, http_error, unreachable, blocked_by_robots) for every website that gave no lead, so you can see why. These rows are free.

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

Higher is faster. Lower it if many websites time out.

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

Optional. Some websites refuse requests from data centers. An Apify Proxy can help, and proxy traffic is billed to your account on top of the lead price.

## Actor input object example

```json
{
  "websites": [
    "https://apify.com",
    "https://www.sparkfun.com",
    "https://www.oxfam.org.uk"
  ],
  "maxPagesPerSite": 5,
  "requireEmail": false,
  "checkEmailDomains": true,
  "respectRobotsTxt": true,
  "returnSitesWithoutContacts": false,
  "concurrency": 12,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (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 = {
    "websites": [
        "https://apify.com",
        "https://www.sparkfun.com",
        "https://www.oxfam.org.uk"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("leezx/website-contact-scraper").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 = {
    "websites": [
        "https://apify.com",
        "https://www.sparkfun.com",
        "https://www.oxfam.org.uk",
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("leezx/website-contact-scraper").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 '{
  "websites": [
    "https://apify.com",
    "https://www.sparkfun.com",
    "https://www.oxfam.org.uk"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call leezx/website-contact-scraper --silent --output-dataset

```

## MCP server setup

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

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/8eH70H851E4y6dtIJ/builds/GZ3a2ctL8TzHLfWCu/openapi.json
