# Website Contact Scraper - Emails, Phones & Social Profiles (`pipiagent/website-contact-scraper`) Actor

Find emails, phone numbers and social media profiles (LinkedIn, Facebook, Instagram, X, YouTube, TikTok and more) on any list of company websites. One clean row per website. Pay only for websites with contacts.

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

## Pricing

$5.00 / 1,000 results

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

Find email addresses, phone numbers and social media profiles on any list of company websites. Paste the websites, press Start, and get one clean row per company with every contact the website shows publicly. Sales teams, recruiters, agencies and researchers use it to turn a list of domains into a contact list for outreach, CRM enrichment or market research.

- **One row per website.** All emails, phones and profiles from the homepage, contact, about, imprint and team pages are merged and deduplicated.
- **Pay only for websites with contacts.** Websites that cannot be opened or show no contact details are saved for free, with a status that says why.
- **Social profiles included.** LinkedIn, Facebook, Instagram, X (Twitter), YouTube, TikTok, Pinterest, GitHub, WhatsApp and Telegram, without share buttons or post links.
- **Works in many languages.** It finds contact pages called Kontakt, Contacto, Contatti, Impressum, À propos, お問い合わせ, 联系我们 and many more.
- **Clean data.** Phone numbers in international format (+4930...), lowercased emails, junk such as `logo@2x.png` or `user@example.com` removed.
- **Low price.** $5 per 1,000 websites with contacts. The number of pages read per website does not change the price.

### What data can you get?

| Field | Example |
|---|---|
| `companyName` | Tim Wendelboe Coffee |
| `primaryEmail` | hello@timwendelboe.no |
| `emails` | All email addresses found on the website |
| `ownDomainEmails` | Only the emails on the company's own domain |
| `primaryPhone`, `phones` | +4740004062 |
| `linkedIn`, `facebook`, `instagram`, `twitter`, `youtube`, `tiktok`, `pinterest`, `github`, `whatsapp`, `telegram` | Profile links |
| `contactPageUrl` | The website's contact page |
| `emailsFoundOn` | Which page each email was found on |
| `title`, `description`, `language` | Homepage title, meta description and language |
| `url`, `domain` | The final address after redirects |
| `pagesCrawled` | Every page the Actor read |
| `status` | `ok`, `noContacts`, `unreachable` or `blocked` |

### How to use it

1. Click **Try for free**.
2. Paste your websites into **Websites**. Domains like `example.com` and full URLs both work, and you can paste a whole column from a spreadsheet at once.
3. Click **Start**.
4. Download the results as CSV, Excel or JSON, or open the **Overview** and **Social profiles** tables in the Output tab.

### Input examples

Scan a list of company websites with the default settings:

```json
{
    "websites": ["timwendelboe.no", "https://www.sacher.com", "kofola.cz"]
}
```

Read more pages per website, set the country for local phone numbers, and keep only websites with contacts:

```json
{
    "websites": ["https://www.example-dental.com", "example-plumbing.co.uk"],
    "maxPagesPerWebsite": 20,
    "defaultCountry": "GB",
    "includeEmptyResults": false
}
```

Only check the homepage of each website, which is the fastest:

```json
{
    "websites": ["coffeecollective.dk", "the-barn.de"],
    "maxDepth": 0
}
```

### Output examples

A website with contacts (shortened):

```json
{
    "website": "https://timwendelboe.no/",
    "url": "https://timwendelboe.no/",
    "domain": "timwendelboe.no",
    "status": "ok",
    "companyName": "Tim Wendelboe Coffee",
    "primaryEmail": "hello@timwendelboe.no",
    "primaryPhone": "+4740004062",
    "emails": ["hello@timwendelboe.no", "press@timwendelboe.no"],
    "ownDomainEmails": ["hello@timwendelboe.no", "press@timwendelboe.no"],
    "emailCount": 2,
    "phones": ["+4740004062"],
    "phoneCount": 1,
    "linkedIn": [],
    "facebook": ["https://www.facebook.com/TimWendelboeCoffee"],
    "instagram": ["https://www.instagram.com/timwendelboe"],
    "youtube": ["https://www.youtube.com/@TimWendelboeCoffee"],
    "socialCount": 5,
    "title": "Tim Wendelboe | Tim Wendelboe Coffee",
    "description": "Tim Wendelboe is a coffee roastery, espresso bar and coffee training centre located in Oslo, Norway.",
    "language": "en",
    "contactPageUrl": "https://timwendelboe.no/pages/contact-us",
    "emailsFoundOn": [
        {"email": "hello@timwendelboe.no", "pageUrls": ["https://timwendelboe.no/pages/contact-us", "https://timwendelboe.no/pages/our-team"]},
        {"email": "press@timwendelboe.no", "pageUrls": ["https://timwendelboe.no/pages/press"]}
    ],
    "pagesCrawled": [
        "https://timwendelboe.no/",
        "https://timwendelboe.no/pages/contact-us",
        "https://timwendelboe.no/pages/about",
        "https://timwendelboe.no/pages/press",
        "https://timwendelboe.no/pages/our-team",
        "https://timwendelboe.no/pages/data-sharing-opt-out"
    ],
    "pagesCrawledCount": 6,
    "error": null,
    "scrapedAt": "2026-10-28T10:03:16Z"
}
```

A website that refused the request (free, shortened):

```json
{
    "website": "https://bookpeople.com/",
    "status": "blocked",
    "emails": [],
    "phones": [],
    "pagesCrawledCount": 0,
    "error": "the website refused automated requests (HTTP 403)",
    "scrapedAt": "2026-10-28T10:01:50Z"
}
```

Each run also saves a **Run summary** (in the Output tab) with the number of websites per status and the list of websites without contacts.

### How much does it cost?

You pay **$5.00 per 1,000 websites with contacts** ($0.005 per website). A website counts when at least one email, phone number or social profile was found. Websites that could not be opened, were blocked or had no contacts cost nothing. There is no start fee and no monthly rental, and reading more pages per website does not cost more.

| Run | Websites with contacts | Cost |
|---|---|---|
| 100 websites | about 85 | about $0.43 |
| 1,000 websites | about 850 | about $4.25 |
| 10,000 websites | about 8,500 | about $42.50 |

The share of websites with contacts depends on your list. In our tests with about 110 real company websites from 20 countries, 85% had contacts. Set a maximum charge per run in the run options and the Actor stops when it is reached.

### Limits you should know

- **Some websites block automated visitors.** In our tests about 8% of websites answered with a bot check or "access denied" (Cloudflare, Sucuri and similar). These come back with status `blocked` and are free. The Actor already retries them once through another server address, which rarely gets past strong bot protection.
- **JavaScript-only websites give fewer results.** The Actor reads the HTML that the server sends and does not run a browser. Websites built entirely in JavaScript show almost nothing to it. In our tests this was about 4% of websites. They come back as `noContacts` with a note in `error`.
- **It reads contact-like pages, not the whole website.** The homepage plus pages such as contact, about, imprint, team, legal and support, up to **Max pages per website** (default 10). Emails that appear only in blog posts or deep product pages can be missed.
- **Emails of other companies can appear.** Imprint pages often name the web agency or the parent company, and some pages show the email of a partner. Use `ownDomainEmails` when you only want the company's own addresses. Typing mistakes on the website are kept as they are.
- **Phone numbers are read carefully.** To avoid order numbers, tax IDs and dates, a number is kept only when it is a link, has a phone label nearby (Tel, Phone, Telefon...), starts with + or 00, or is written in phone-style groups. Local numbers are read with the country of the domain ending (.de, .fr, ...) or the page language, so a `.com` website with local numbers from another country may need **Country for phone numbers**. Fax numbers are skipped.
- **Emails are not verified.** The Actor reports addresses as the website shows them. It does not check whether a mailbox exists.
- **Speed.** About 10 to 15 websites per minute with the default settings. Each website is read politely, one page after another.

### Tips

- **Start with the default 10 pages.** Contact details are almost always on the homepage, contact page or imprint, and the Actor opens those first.
- **Use `primaryEmail` for mail merges.** It picks the company's own general address (info@, contact@, hello@ and similar) when there is one.
- **Use the free status rows.** Filter by `status` to find websites that are down (`unreachable`) or need another approach (`blocked`).
- **Combine it with a list source.** Feed it websites from Google Maps, directory or search results to build a full lead list.

### FAQ

**Is it legal?** The Actor only reads pages that every visitor can see without logging in. Business contact details can still be personal data, for example a named employee's email. You are responsible for using the results lawfully: have a legitimate reason to process the data under the GDPR and similar laws, and follow anti-spam rules such as CAN-SPAM and the ePrivacy rules before sending marketing emails.

**Does it find emails that are hidden?** It decodes common protections: Cloudflare email protection, HTML entities, addresses written as `name [at] company [dot] com`, and addresses inside page data.

**Can I get one row per email instead of per website?** Not in this version. Each row lists every email in `emails` and `emailsFoundOn`, and the CSV export puts them in separate columns.

**Something is broken or missing?** Open a ticket in the Issues tab. Issues are usually answered within a day.

# Actor input Schema

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

Company websites to scan. Full URLs (https://example.com) or bare domains (example.com) both work. You can paste a whole list at once, one website per line or separated by commas. You get one result per website.

## `maxPagesPerWebsite` (type: `integer`):

How many pages to open on each website, the homepage included. The Actor opens contact, about, imprint, team and similar pages first, so 5 to 10 is enough for most websites. The price per website does not change with this number.

## `maxDepth` (type: `integer`):

How many clicks away from the homepage the Actor may go. 0 = homepage only, 1 = pages linked from the homepage, 2 = one more step (for example Company > Team).

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

Optional two-letter country code, for example US, GB or DE. Used to read local phone numbers written without a country prefix. Leave empty to detect the country from the website's domain ending and language.

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

Also save a row for websites that could not be opened or showed no email, phone number or social profile, with a status that says why. These rows are free. Turn off to keep only websites with contacts.

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

How many websites to scan at the same time. Pages of one website are always opened one after another.

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

Optional. The Actor works without a proxy. Turn one on only if many websites come back as blocked.

## Actor input object example

```json
{
  "websites": [
    "https://apify.com",
    "https://www.python.org",
    "https://www.mozilla.org"
  ],
  "maxPagesPerWebsite": 10,
  "maxDepth": 2,
  "includeEmptyResults": true,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One item per website: emails, phone numbers, social profiles and company details.

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

Counts per status and the list of websites without contacts or that could not be opened.

# 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.python.org",
        "https://www.mozilla.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pipiagent/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.python.org",
        "https://www.mozilla.org",
    ] }

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

```

## MCP server setup

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