# Website Contact Scraper (`calm_builder/website-contact-scraper`) Actor

Extract emails, phone numbers, social media profiles and addresses from any list of websites. Finds contact, about and imprint pages automatically, filters out fake and placeholder emails, and validates every phone number. Built for lead generation, sales prospecting and CRM enrichment.

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

## Pricing

from $2.00 / 1,000 website with contacts

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 Scraper

Extract emails, phone numbers, social media profiles and postal addresses from any list of websites, in a clean, structured format.

Paste a list of domains or links, choose how many pages to check on each site, and export the results as JSON, CSV or Excel, or pull them through the Apify API.

### What This Actor Does

- Collects contact details from any website: emails, phone numbers, social media profiles and postal addresses
- Finds the right pages automatically: contact, about, imprint, team, locations and more, picked from each site's own links
- Filters out fake and useless addresses: placeholders like `you@example.com`, image file names, tracking and vendor emails, and no-reply inboxes
- Validates every phone number for the website's country and returns it in international format (`+14155550100`)
- Picks a main email and phone for each website, preferring the business's own general inbox, and keeps every other contact alongside
- Finds profiles on Instagram, Facebook, LinkedIn, X (Twitter), TikTok, YouTube, Pinterest, Threads, WhatsApp and Yelp
- Handles modern websites built with JavaScript frameworks and site builders
- Returns one row per website, with a status that says whether contacts were found or why a website could not be checked

### Best For

- Lead generation and sales prospecting
- Enriching CRM, spreadsheet or Google Maps business lists with emails and phone numbers
- Finding social media profiles for a list of companies
- Building outreach lists for agencies, recruiters and B2B sales teams
- Cleaning and completing company databases

### Input

Add one or more websites and choose how many pages to check on each.

#### Main input fields

- `websites`
  One website per row. Accepts domains (`bluebottlecoffee.com`), links (`https://www.bluebottlecoffee.com/`) and any page on a site (`https://www.bluebottlecoffee.com/about`). Each website is checked once, even if it appears in several rows.
- `maxPagesPerSite`
  How many pages to check on each website, including its homepage. `1` checks only the homepage. `5` adds the most useful pages, such as contact, about and imprint pages, and suits most lists. Up to `20` goes deeper, into team, location and press pages.
- `includeEmptyResults`
  On by default: every website gets a row, with a status explaining empty ones, so the results line up with your list. Turn it off to get only websites where contact details were found.

#### Example input

```json
{
  "websites": [
    "bluebottlecoffee.com",
    "https://www.levainbakery.com/",
    "dishoom.com"
  ],
  "maxPagesPerSite": 5,
  "includeEmptyResults": true
}
```

### Output

Each dataset item is one website from your input. With `includeEmptyResults` turned off, only websites where contact details were found are included.

#### Fields

| Field                                                                                                           | Description                                                                                                               |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `domain`, `url`, `inputUrl`                                                                                     | The website, its homepage, and the row from your input                                                                    |
| `status`, `statusMessage`                                                                                       | `contacts_found`, `no_contacts_found`, `website_not_found`, `blocked` or `unreachable`, with a plain-language explanation |
| `email`, `phone`                                                                                                | The main email and phone number for the website                                                                           |
| `emails`, `phones`                                                                                              | Every email and phone number found, main ones first                                                                       |
| `instagram`, `facebook`, `linkedin`, `twitter`, `tiktok`, `youtube`, `pinterest`, `threads`, `whatsapp`, `yelp` | The website's main profile on each platform                                                                               |
| `socialLinks`                                                                                                   | Every social profile found, grouped by platform                                                                           |
| `addresses`                                                                                                     | Postal addresses the website publishes, split into street, city, region, postcode and country                             |
| `country`                                                                                                       | The country the website's phone numbers were validated for                                                                |
| `pagesScraped`, `scrapedUrls`                                                                                   | How many pages were checked, and which                                                                                    |

#### Example output

```json
{
  "inputUrl": "dishoom.com",
  "domain": "dishoom.com",
  "url": "https://www.dishoom.com/",
  "status": "contacts_found",
  "statusMessage": "Contact details found.",
  "email": "hello@dishoom.com",
  "phone": "+441135171712",
  "instagram": "https://www.instagram.com/dishoom",
  "facebook": null,
  "linkedin": "https://uk.linkedin.com/company/dishoom",
  "twitter": "https://x.com/dishoom",
  "tiktok": "https://www.tiktok.com/@dishoom_uk",
  "youtube": "https://www.youtube.com/user/DishoomLondon",
  "emails": [
    "hello@dishoom.com",
    "reservations@dishoom.com",
    "team@dishoom.com",
    "all-ears@dishoom.com",
    "press@dishoom.com"
  ],
  "phones": ["+441135171712", "+441218095986", "+441312026406"],
  "socialLinks": {
    "instagram": ["https://www.instagram.com/dishoom"],
    "linkedin": ["https://uk.linkedin.com/company/dishoom"],
    "twitter": ["https://x.com/dishoom"],
    "tiktok": ["https://www.tiktok.com/@dishoom_uk"],
    "youtube": ["https://www.youtube.com/user/DishoomLondon"]
  },
  "addresses": [
    {
      "streetAddress": "4 Derry Street",
      "addressLocality": "London",
      "addressCountry": "England"
    }
  ],
  "country": "GB",
  "pagesScraped": 5,
  "scrapedUrls": [
    "https://www.dishoom.com/",
    "https://www.dishoom.com/say-hello/",
    "https://www.dishoom.com/locations/"
  ]
}
```

### How To Use

1. Add your websites to `websites`, one per row. Domains and links both work.
2. Set `maxPagesPerSite`: `5` suits most lists; use `1` for a quick check or up to `20` for a deeper crawl.
3. Run the actor and open the dataset, export it as JSON, CSV or Excel, or read it through the Apify API.
4. Filter by `status` to see which websites had contact details and which could not be checked, or turn off `includeEmptyResults` to receive only websites with contacts.

### Pricing

This actor uses pay-per-event pricing, so you only pay for results you receive. Websites that don't exist, block automated access, or show no contact details are never charged for contacts, and checking extra pages beyond the homepage is an optional add-on.

Current prices for each Apify plan are shown on the actor's Pricing tab.

#### Free plan

On the Apify free plan, each run checks a limited number of websites and pages. Upgrade to a paid Apify plan to remove these limits.

### Best Practices

- Start with a few websites to check the output quickly.
- Keep `maxPagesPerSite` at `5` for most lists; go higher only when you need staff, branch or press contacts.
- Use the main `email` and `phone` columns for outreach, and the full lists when you need every contact.
- Use JSON export when you need nested data such as social links and addresses.
- Remove duplicates and social media links from your list first; they are skipped anyway.

### FAQ

#### Why did a website return no contact details?

Many websites only offer a contact form and don't publish an email or phone number. The `status` column shows `no_contacts_found` for these. Checking more pages with a higher `maxPagesPerSite` sometimes helps.

#### What does "blocked" or "unreachable" mean?

`blocked` means the website uses bot protection that refuses automated visits. `unreachable` means the website did not respond, usually because it is down. Unreachable websites often work on a later run.

#### How are fake emails filtered out?

Every email is checked for a real domain, and placeholders, image file names, tracking and vendor addresses and no-reply inboxes are removed. Emails from structured data, email links and contact pages are trusted most, so the main email is almost always the business's real inbox.

#### Are phone numbers validated?

Yes. Each number is checked against the numbering rules of the website's country and returned in international format. Dates, prices and product codes are not mistaken for phone numbers.

#### Can I scrape social media profiles or marketplace pages directly?

No. This actor reads business websites. Social media, marketplace and link-in-bio pages in your input are skipped with a note in the log.

#### How long does a run take?

Websites are checked in parallel. Around 200 websites at 5 pages each typically finish in a few minutes.

### Responsible Use

Use this actor only for publicly available information, and make sure your use complies with applicable laws, including data protection and anti-spam rules such as GDPR and CAN-SPAM. Avoid collecting personal data you don't need.

### Troubleshooting

- Open the website in a browser to confirm the address is correct and the site is online.
- Check the `status` and `statusMessage` columns for the reason a website had no results.
- If a website did not respond, run it again a little later.
- Test with a few websites before large runs.

# Actor input Schema

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

Enter one website per row.

Supported formats:

- Domain — `bluebottlecoffee.com`
- Link — `https://www.bluebottlecoffee.com/`
- Any page on the site — `https://www.bluebottlecoffee.com/about`

Each website is checked once, even if it appears in several rows.

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

How many pages to check on **each website**, including its homepage.

- `1` checks the homepage only, the fastest option.
- `5` adds the most useful pages, such as contact, about and imprint pages. Recommended for most lists.
- Up to `20` goes deeper, into team, location and press pages, to find more staff and branch contacts.

Pages beyond the homepage are an optional add-on with their own price. Pages are picked from the site's own links, most useful first.

## `includeEmptyResults` (type: `boolean`):

Keep a row for every website in your input, including websites with no contact details and websites that could not be checked. The `status` column says why a row is empty, so your results line up with your list.

Turn this off to get only websites where contact details were found.

## Actor input object example

```json
{
  "websites": [
    "bluebottlecoffee.com",
    "https://www.levainbakery.com/"
  ],
  "maxPagesPerSite": 5,
  "includeEmptyResults": true
}
```

# 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": [
        "bluebottlecoffee.com",
        "https://www.levainbakery.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("calm_builder/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": [
        "bluebottlecoffee.com",
        "https://www.levainbakery.com/",
    ] }

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

```

## MCP server setup

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