# Website Contact Scraper: Emails, Phones & Socials (`green_trousers/website-contact-scraper`) Actor

Turn a list of websites into a ready-to-use lead list: emails, phone numbers, LinkedIn, X, Facebook, Instagram and more, one clean row per site. Checks the homepage plus contact, about, team and impressum pages. Fast plain-HTTP crawl, and you only pay for sites where contacts are found.

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

## Pricing

from $3.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.

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 & Socials

Paste a list of websites, get back a clean lead list: **one row per website** with every email address, phone number and social profile the site publishes, plus the company name.

You **only pay for websites where at least one contact was found.** Dead domains, blocked sites and pages with nothing on them cost you nothing.

### What it does

For every website you give it, the scraper:

1. Opens the homepage.
2. Finds the pages most likely to hold contact details, such as **Contact, About, Team, Impressum/Imprint, Support and Privacy**, including common non-English slugs like *kontakt*, *contacto*, *mentions légales* or *über uns*. It checks up to 5 of them by default, and you can raise that to 20.
3. Extracts and de-duplicates:
   - **Emails**: from `mailto:` links, page text, structured data (JSON-LD), Cloudflare-protected addresses and simple obfuscations like `info [at] acme [dot] com`. Junk is removed: image names like `logo@2x.png`, placeholder addresses like `you@example.com`, and tracking addresses like Sentry/Wix. Addresses on the site's own domain are listed first.
   - **Phone numbers**: `tel:` links first, then structured data, then a careful text match that only accepts phone-shaped numbers next to words like "phone" or "call". Trademark numbers, dates and IDs are skipped.
   - **Social profiles**: LinkedIn, X/Twitter, Facebook, Instagram, YouTube, TikTok, GitHub and Pinterest, each in its own column. When several accounts appear, the company's own account is chosen, not a share button or a partner's page. Threads, Bluesky, Discord, Telegram, Reddit, Medium, Yelp, Crunchbase, WhatsApp and others go into `other_socials`.
   - **Company name**, taken from the site's `og:site_name`, or else the part of the page title that matches the domain.
4. Writes **one row per website**, ready for a spreadsheet, CRM import or outreach tool.

It uses fast, plain HTTP requests: no browser and no proxies. That makes it cheap and quick, with about 10 sites processed in parallel and hundreds of sites per minute. Sites that refuse automated visitors (HTTP 403) are skipped politely and marked `blocked`, and they are free.

### Use cases

- **Lead generation**: turn a list of company domains from Google Maps, a trade-show list, a directory or a CSV export into emails, phone numbers and LinkedIn pages.
- **Enrich your CRM**: fill in missing phone numbers and social profiles for accounts you already have.
- **Agency prospecting**: build outreach lists of local businesses such as plumbers, restaurants, dentists and shops in any niche.
- **Partnership and PR outreach**: find the `press@`, `partners@` or `hello@` address for a list of brands.
- **Market research**: see which competitors are active on which social networks.
- **Data cleaning**: check which domains in a list are still alive.

### Input

| Field | What it is | Default |
|---|---|---|
| `websites` | Website URLs or bare domains. Duplicates are removed. | `["https://apify.com", "https://stripe.com"]` |
| `maxPagesPerSite` | Extra pages to check per site besides the homepage (0–20). | `5` |
| `maxConcurrency` | Websites processed in parallel (1–50). | `10` |

```json
{
    "websites": ["https://apify.com", "stripe.com", "saddlebackleather.com", "www.hetzner.com"],
    "maxPagesPerSite": 5
}
```

### Output

One row per website. Example (real output, trimmed):

```json
{
    "url": "https://saddlebackleather.com/",
    "domain": "saddlebackleather.com",
    "company_name": "Saddleback Leather Co.",
    "emails": ["ihaveaquestion@saddlebackleather.com", "corporate@saddlebackleather.com"],
    "phones": ["817-402-4550"],
    "linkedin": "https://www.linkedin.com/company/saddleback-leather-co.",
    "twitter": "https://x.com/saddlebackbags",
    "facebook": "https://www.facebook.com/saddlebackleather",
    "instagram": "https://www.instagram.com/saddlebackleather",
    "youtube": "https://www.youtube.com/user/saddlebackLeather",
    "tiktok": null,
    "github": null,
    "pinterest": "https://www.pinterest.com/saddlebackbags",
    "other_socials": [],
    "pages_scanned": [
        "https://saddlebackleather.com/",
        "https://saddlebackleather.com/contact-us/",
        "https://saddlebackleather.com/faq-common-questions/",
        "https://saddlebackleather.com/privacy"
    ],
    "status": "ok",
    "error": null
}
```

| Field | Description |
|---|---|
| `url` | Final homepage URL, after redirects |
| `domain` | Domain without `www.` |
| `company_name` | Brand or company name |
| `emails` | All unique emails, with the site's own domain first |
| `phones` | Unique phone numbers, in the format the site uses |
| `linkedin`, `twitter`, `facebook`, `instagram`, `youtube`, `tiktok`, `github`, `pinterest` | The company's profile URL on each network, or `null` |
| `other_socials` | Other profiles: Threads, Bluesky, Discord, Telegram, Reddit, Medium, Yelp, Crunchbase, WhatsApp, Vimeo and more |
| `pages_scanned` | Every page that was checked |
| `status` | `ok` (contacts found), `no_contacts`, `blocked` (site refused access) or `error` (dead domain, timeout or bad URL) |
| `error` | Why a site failed, if it did |

Download the results as **CSV, Excel, JSON or HTML**, or pull them through the API or into Make, Zapier, n8n or Google Sheets. A run summary is saved in the key-value store under `SUMMARY`.

### Pricing

Simple pay-per-result pricing, **per website and not per page**:

| What happens | You pay |
|---|---|
| Website with at least one email, phone or social profile found | **$0.003** ($3 per 1,000 websites) |
| Website with no contacts found | $0 |
| Blocked, dead or invalid website | $0 |
| Extra pages checked per site (up to 20) | $0 |

Most contact scrapers charge **per page crawled**. Checking the homepage plus 5 likely pages typically costs $0.01 or more per website with them, even when nothing is found. Here you pay a flat $0.003, and only when you get something.

Set a maximum cost per run in Apify Console and the scraper stops cleanly when it reaches your limit.

### FAQ

**How many contacts will I get?** Most business websites list at least one email or social profile. Local businesses usually show a phone number, and SaaS companies usually show an email and LinkedIn/X. Results depend on what each site publishes.

**Does it find personal emails of employees?** It finds the addresses a website publishes, such as `info@`, `sales@`, `support@` or a founder's address on the team page. It does not guess or generate email addresses.

**Why is a site marked `blocked`?** Some sites refuse automated visitors (HTTP 403). The scraper respects that and moves on. You are not charged.

**Does it render JavaScript?** No. It reads the HTML the server sends, which is where nearly all sites put their footer links, `mailto:` links and contact pages. That's why it is fast and cheap. For sites built purely in JavaScript, it also tries `/contact` and `/about` directly.

**Can I pass bare domains?** Yes: `acme.com`, `www.acme.com` and `https://acme.com/some/page` all work. If `https` fails, plain `http` is tried.

**How fast is it?** About 10 websites run in parallel by default. A batch of 10 sites usually finishes in under 10 seconds, and you can raise `maxConcurrency` for large lists.

**Is it legal?** It only collects contact information that businesses publish on their own public websites. You are responsible for using the data in line with the laws that apply to you, such as GDPR, CAN-SPAM and CASL, especially for cold outreach.

**One row per site, or one row per email?** One row per site, so your lead list stays lead-shaped. If you need one row per email, use the "unwind" option when exporting the dataset, or split the `emails` column in your spreadsheet.

# Actor input Schema

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

Website URLs or bare domains (apify.com, https://www.acme-plumbing.com). You get one output row per website.

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

Besides the homepage, how many likely contact pages to check per site (contact, about, team, impressum, support, privacy). More pages = more contacts found, slightly slower runs. Pricing is per website, not per page.

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

How many websites to process at the same time.

## Actor input object example

```json
{
  "websites": [
    "https://apify.com",
    "https://stripe.com"
  ],
  "maxPagesPerSite": 5,
  "maxConcurrency": 10
}
```

# 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://stripe.com"
    ]
};

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

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

```

## MCP server setup

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