# Website Contact Details Scraper (`automation-lab/website-contact-details-extractor`) Actor

Extract emails, phone numbers, addresses, contact pages, and social links from public business websites.

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

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Contact Details Scraper

Turn a list of public business websites into structured **website contact details** for lead enrichment, CRM cleanup, research, and scheduled data pipelines.

The Actor visits each supplied site, follows a bounded set of likely contact and company pages, and returns one aggregated record per website. Records can include public emails, phone numbers, address text, contact and about page URLs, social-profile links, crawl evidence, and failures.

### What does Website Contact Details Scraper do?

The Actor:

- accepts one or many public website URLs;
- normalizes domain-only inputs to HTTPS;
- follows redirects;
- discovers likely contact, support, about, company, team, and legal pages;
- extracts emails from visible text and `mailto:` links;
- extracts phone numbers from labeled text and `tel:` links;
- reads postal-address text from semantic HTML and JSON-LD;
- groups social links by network;
- aggregates every result into one website-level record;
- records both successful and failed page URLs.

It uses direct HTTP by default. Optional Apify Proxy settings are available for websites that reject requests from the default route.

### Who is this website contact extractor for?

Use it when you are:

- enriching company records before sales outreach;
- cleaning missing or stale CRM contact fields;
- researching vendors, members, exhibitors, or partners;
- building a public-business contact spreadsheet;
- checking where a company publishes its support channels;
- preparing website-level records for another automation;
- scheduling repeat collection to compare public details over time.

The Actor extracts what a website publicly publishes. It does not guess private email addresses or validate mailbox deliverability.

### Why use this Actor?

Many contact scrapers emit a separate row for every page or link. This Actor instead returns one predictable record for each supplied website, which makes joins against a CRM or company list straightforward.

The crawl is deliberately bounded. It prioritizes high-signal pages rather than exploring an entire site, limits retries, and avoids browser rendering unless the product is changed in a future release.

Useful operational fields such as `pagesCrawled`, `pagesFailed`, and `foundContactDetails` make partial coverage visible instead of silently treating every failure as an empty result.

### What website contact data can it extract?

| Field | Description |
| --- | --- |
| `websiteUrl` | Normalized supplied URL |
| `finalUrl` | Homepage URL after redirects |
| `domain` | Final website hostname |
| `websiteName` | Name inferred from metadata or title |
| `emails` | Unique public email addresses |
| `phoneNumbers` | Unique public phone-number strings |
| `contactPageUrls` | Contact, support, help, or reach-us links |
| `aboutPageUrls` | About, company, team, or who-we-are links |
| `addresses` | Address text from semantic HTML or JSON-LD |
| `socialProfiles` | Links grouped by Facebook, Instagram, LinkedIn, X, YouTube, TikTok, Pinterest, or GitHub |
| `pagesCrawled` | Pages successfully used for the record |
| `pagesFailed` | Pages that failed with concise reasons |
| `foundContactDetails` | Whether any useful detail or contact link was found |
| `scrapedAt` | Completion time in ISO 8601 format |

Arrays are empty when no matching public value is found. `websiteName` can be null when a page exposes no usable title.

### How to get started

1. Open the Actor input.
2. Add public business websites under **Business website URLs**.
3. Keep the default five pages per website for an economical first run.
4. Increase crawl depth only when relevant contact pages are nested.
5. Optionally enable Apify Proxy for a site that blocks direct requests.
6. Click **Start**.
7. Open the dataset and export JSON, CSV, Excel, XML, or RSS.

A full URL such as `https://company.com/contact` is accepted, as is a domain-like value such as `company.com`.

### Input parameters

#### `startUrls`

Required. One or more website URLs. Each unique input produces at most one result record.

#### `maxItems`

Maximum input websites to process. Default: `10`. Range: `1` to `10,000`.

#### `maxPagesPerWebsite`

Maximum pages fetched for each website. Default: `5`. Range: `1` to `20`.

#### `maxDepth`

Number of same-site contact-link levels to follow. Default: `1`. Range: `0` to `2`.

Set it to `0` to inspect only each supplied URL.

#### `includeSubdomains`

Whether contact-related subdomain links may enter the crawl. Default: `false`.

#### `proxyConfiguration`

Optional Apify Proxy configuration. Direct requests are the default. The Actor does not automatically switch to paid residential traffic.

### Example input

```json
{
  "startUrls": [
    { "url": "https://apify.com" },
    { "url": "https://www.w3.org/Consortium/contact" }
  ],
  "maxItems": 2,
  "maxPagesPerWebsite": 5,
  "maxDepth": 1,
  "includeSubdomains": false
}
```

### Example output

The shape below is anonymized from the verified local output contract:

```json
{
  "websiteUrl": "https://sample-business.test/",
  "finalUrl": "https://www.sample-business.test/",
  "domain": "www.sample-business.test",
  "websiteName": "Sample Business",
  "emails": ["hello@sample-business.test"],
  "phoneNumbers": ["+1 555 010 2040"],
  "contactPageUrls": ["https://sample-business.test/contact"],
  "aboutPageUrls": ["https://sample-business.test/about"],
  "addresses": ["123 Sample Street, Example City, CA 90000, US"],
  "socialProfiles": {
    "linkedin": ["https://linkedin.com/company/samplebusiness"]
  },
  "pagesCrawled": [
    "https://sample-business.test/",
    "https://sample-business.test/contact"
  ],
  "pagesFailed": [],
  "foundContactDetails": true,
  "scrapedAt": "2026-08-05T12:00:00.000Z"
}
```

### How much does it cost to extract contact details from websites?

The Actor uses pay-per-event pricing:

- a one-time **Start** event per run;
- one **Item processed** event for each website record saved.

The run starts at `$0.005`. The per-item price is tiered by Apify plan; the BRONZE tier is `$0.0036` per website record, with lower rates on higher plans.

At those rates, excluding optional proxy traffic:

- 1 website is about `$0.0086` on BRONZE;
- 25 websites are about `$0.095` on BRONZE;
- 100 websites are about `$0.365` on BRONZE.

The live Console pricing table is authoritative. Final cost can also be limited with Apify's maximum-charge setting.

### CRM enrichment workflow

Export domains or website URLs from your CRM, run them as `startUrls`, and join the dataset back on `domain` or `websiteUrl`.

Useful follow-up steps include:

1. retain only records where `foundContactDetails` is true;
2. explode email or phone arrays when your CRM expects one value per row;
3. route `pagesFailed` records to a retry queue;
4. preserve `scrapedAt` to compare scheduled runs;
5. review public-contact purpose and consent before outreach.

### Scheduled monitoring and change detection

Apify Tasks can run this Actor on a schedule. Store each run's dataset externally, then compare fields such as `emails`, `phoneNumbers`, `contactPageUrls`, and `socialProfiles`.

The Actor itself returns a current snapshot. It does not maintain history, send alerts, or label changes between runs.

### Export and integrations

Dataset results work with:

- JSON, JSONL, CSV, Excel, XML, and RSS exports;
- Apify webhooks;
- Zapier and Make;
- Google Sheets workflows;
- cloud warehouses and object storage;
- CRM enrichment jobs;
- custom Node.js or Python pipelines.

Because each row represents one website, downstream deduplication is typically based on `domain`.

### Use the API with cURL

```bash
curl "https://api.apify.com/v2/acts/automation-lab~website-contact-details-extractor/runs?token=$APIFY_TOKEN" \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{
    "startUrls": [{"url": "https://www.w3.org/Consortium/contact"}],
    "maxPagesPerWebsite": 5,
    "maxDepth": 1
  }'
```

Use the returned run ID to read status and the default dataset.

### Use the API with JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/website-contact-details-extractor').call({
  startUrls: [{ url: 'https://www.w3.org/Consortium/contact' }],
  maxPagesPerWebsite: 5,
  maxDepth: 1,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Use the API with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/website-contact-details-extractor').call(run_input={
    'startUrls': [{'url': 'https://www.w3.org/Consortium/contact'}],
    'maxPagesPerWebsite': 5,
    'maxDepth': 1,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/website-contact-details-extractor"
```

Claude Desktop, Cursor, and VS Code can use the same HTTP MCP server configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/website-contact-details-extractor"
    }
  }
}
```

- **Claude Desktop:** add the JSON under `mcpServers` in the desktop configuration.
- **Cursor:** add the same server in Cursor MCP settings.
- **VS Code:** add the same server to your MCP configuration.

Example prompts:

- “Extract public contact details from these supplier websites and return one row per domain.”
- “Find the public support page and social profiles for each URL in this CRM export.”
- “Run contact enrichment for these websites and summarize pages that failed.”

### Accuracy tips

- Supply the canonical company homepage or a known contact page.
- Increase `maxPagesPerWebsite` when navigation contains several relevant branches.
- Use depth `2` only when contact pages are nested.
- Enable subdomains when support or company pages live on a corporate subdomain.
- Review phone and address strings before using them in automated outreach.
- Use a proxy only after a direct run shows a site-specific access failure.

### Limits and failure behavior

The Actor does not render JavaScript. A JavaScript-only site can expose fewer values than a server-rendered site.

Authenticated pages, forms behind login, CAPTCHA challenges, and private intranets are outside scope. Local and private-network targets are rejected.

A related page failure is added to `pagesFailed`; details from successful pages are retained. A website that loads but publishes no matching values still produces a record with empty arrays and `foundContactDetails: false`.

The Actor discovers contact-form URLs but does not submit forms.

### Responsible use and legality

Only process public websites you are allowed to access. Follow applicable website terms, robots policies, privacy law, anti-spam law, and contractual obligations.

Public availability does not automatically authorize unsolicited outreach or unlimited reuse. Establish a lawful purpose, minimize collected data, secure exports, honor deletion and opt-out requests, and avoid collecting sensitive personal information.

The user is responsible for how extracted contact details are stored and used.

### Troubleshooting

#### Why are the arrays empty?

The site may not publish contact details in server-rendered HTML, or the details may be behind a form, script, login, or image. Check `pagesCrawled`, increase the page limit, and verify the public page manually.

#### Why does `pagesFailed` contain HTTP 403 or 429?

The website rejected or rate-limited the request. Reduce repeated runs or enable an appropriate Apify Proxy configuration if your access is permitted.

#### Why did one website return only social links?

That can be valid. Some sites publish social profiles but no email, phone, or semantic address.

#### Does the Actor verify emails or phone numbers?

No. It extracts public values as written. Use a separate verification workflow when deliverability or normalization matters.

#### Can it submit website contact forms?

No. It discovers contact-form pages but does not fill or submit forms.

### Related automation-lab Actors

- [Social Media Profile Finder](https://apify.com/automation-lab/social-media-profile-finder) for focused social-profile discovery.
- [SMTP Email Verifier](https://apify.com/automation-lab/smtp-email-verifier) for a separate email-verification workflow.
- [Phone Number Validator](https://apify.com/automation-lab/phone-number-validator) for normalization and validation after extraction.

These are optional downstream tools. Their pricing and output contracts are separate from this Actor.

### FAQ

#### Does one page equal one charged item?

No. One saved website-level record equals one `item` event, even when several pages contributed to it.

#### Can I provide a contact page directly?

Yes. The supplied URL is fetched first, and related same-site links can still be discovered within the configured bounds.

#### Are duplicate websites removed?

Exact normalized input strings are deduplicated. Different URLs on the same domain can still produce separate records when supplied separately.

#### Does it crawl an entire website?

No. It follows only high-signal links and stops at the configured page and depth limits.

#### Can I use the result in a spreadsheet?

Yes. Export the default dataset as CSV or Excel, or connect it to a scheduled integration.

#### Is browser rendering available?

Not in this release. The HTTP-first route keeps runs economical and transparent; JavaScript-only content is a documented limitation.

# Actor input Schema

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

Websites to enrich. Enter full URLs or domain names; each website produces one aggregated dataset record.

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

Maximum number of input websites to process and output records to save.

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

Maximum homepage, contact, about, company, support, and related pages fetched from each website.

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

How many same-site link levels to follow for contact-related pages. Zero fetches only the supplied page.

## `includeSubdomains` (type: `boolean`):

Allow contact-related links on subdomains of the supplied website to enter the crawl.

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

Optional Apify Proxy settings. Direct requests are used by default to keep extraction economical.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.w3.org/Consortium/contact"
    },
    {
      "url": "https://apify.com"
    }
  ],
  "maxItems": 10,
  "maxPagesPerWebsite": 5,
  "maxDepth": 1,
  "includeSubdomains": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the default dataset view containing one aggregated contact-details record per input website.

# 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://www.w3.org/Consortium/contact"
        },
        {
            "url": "https://apify.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/website-contact-details-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://www.w3.org/Consortium/contact" },
        { "url": "https://apify.com" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/website-contact-details-extractor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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://www.w3.org/Consortium/contact"
    },
    {
      "url": "https://apify.com"
    }
  ]
}' |
apify call automation-lab/website-contact-details-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/website-contact-details-extractor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/JAMv7BETWkJbPAR2Y/builds/NUOMdCJnrdkgwbw5k/openapi.json
