# Website Contact Extractor: Emails, Phones & Socials (`malmurk/website-contact-extractor`) Actor

Find emails, phone numbers and social media profiles (LinkedIn, Facebook, Instagram, X, YouTube, TikTok, WhatsApp, Telegram...) on any list of company websites. Checks the homepage and contact, about and imprint pages. Pay only for websites where contacts are found.

- **URL**: https://apify.com/malmurk/website-contact-extractor.md
- **Developed by:** [Catalin Friptu](https://apify.com/malmurk) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 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.

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 Extractor: Emails, Phones & Socials

Give it a list of company websites and get back, for each one, **every email address, phone number and social media profile** published on the site: **LinkedIn, Facebook, Instagram, X/Twitter, YouTube, TikTok, Pinterest, GitHub, Telegram, WhatsApp, Threads and Discord**. One clean row per website, ready for a CRM, a spreadsheet or an outreach tool.

- **Pay only for results: $2 per 1,000 websites where contacts are found.** Websites that are down, blocked or have no contact details cost nothing.
- **Looks where contacts actually are.** It reads the homepage, then follows links to *Contact*, *About*, *Team*, *Imprint/Impressum* and *Legal* pages in 20+ languages (Kontakt, Contacto, Contatti, Despre noi, Контакты, Hakkımızda...).
- **Finds hidden emails** too: `mailto:` links, plain text, `info [at] company [dot] com` spellings and Cloudflare-protected addresses.
- **Phone numbers in international format (E.164)**, from click-to-call links and page text. Only numbers that are valid for their country are kept, so dates, prices and order numbers don't sneak in. The country is taken from the domain (`.de`, `.co.uk`, `.md`...) or from your setting.
- **Real profiles, not share buttons.** `facebook.com/sharer`, `twitter.com/intent`, YouTube videos and LinkedIn share links are dropped; profile URLs are cleaned and de-duplicated (`github.com/acme/repo` becomes `github.com/acme`).
- **The company's own email comes first** (`info@acme.com` before `acme.office@gmail.com`).
- **Fast and light.** Plain HTTP requests, 20 websites in parallel, no browser.

### Who it's for

- **Sales and lead-generation teams** enriching a list of company domains (from a CRM export, a directory or a Google Maps / OpenStreetMap scrape) with emails, phones and socials.
- **Agencies and recruiters** building contact lists for outreach.
- **Data teams** keeping company records up to date.

### Input

| Field | Default | Description |
|---|---|---|
| `startUrls` | | Websites or plain domains (`acme.com`). Several pages of one website count as one website. |
| `maxPagesPerWebsite` | `5` | Homepage + the most promising contact/about/team/imprint pages. |
| `defaultCountry` | from domain | Two-letter code (`US`, `GB`, `DE`...) to read local phone numbers written without `+country`. |
| `phonesFromText` | `true` | Also read numbers from the page text, not only `tel:` links. |
| `respectRobotsTxt` | `true` | Skip pages a website's `robots.txt` disallows. |
| `maxConcurrency` | `20` | Websites processed in parallel. |

```json
{
  "startUrls": ["acme-dental.com", "https://www.example-bakery.co.uk/"],
  "maxPagesPerWebsite": 5,
  "defaultCountry": "US"
}
```

### Output

One row per website. Lists are empty (`[]`) when nothing was found.

| Field | Description |
|---|---|
| `input_url`, `url`, `domain` | What you gave, the homepage after redirects, and the domain without `www.`. |
| `title`, `description` | The homepage's title and meta description. |
| `email`, `emails` | The best email (own domain first), and all of them. |
| `phone`, `phones` | The first phone number and all of them, in E.164 format (`+442079460958`). |
| `linkedin`, `facebook`, `instagram`, `twitter`, `youtube`, `tiktok`, `pinterest`, `github`, `telegram`, `whatsapp`, `threads`, `discord` | Profile URLs. |
| `contact_pages` | Pages where something was found. |
| `pages_crawled` | Pages read on this website. |
| `contacts_found` | `true` if at least one email, phone or profile was found (these rows are the ones you pay for). |
| `crawled_at` | UTC timestamp. |
| `error` | Why a website could not be read (`HTTP 403`, `ConnectError: ...`, `blocked by the site's robots.txt`). |

Example row from a real run (a restaurant website taken from OpenStreetMap):

```json
{
  "input_url": "https://www.armeniantaverna.co.uk/",
  "url": "https://armenianrestaurant.co.uk/",
  "domain": "armeniantaverna.co.uk",
  "title": "Armenian Restaurant in Manchester, by Albert Square | Armenian Restaurant & Bar",
  "description": "Armenian food in Manchester since 1968, facing the Town Hall by Albert Square. ...",
  "email": "contact@armenianrestaurant.co.uk",
  "emails": ["contact@armenianrestaurant.co.uk"],
  "phone": "+441618349025",
  "phones": ["+441618349025"],
  "facebook": ["https://facebook.com/armenianrestaurantbar"],
  "instagram": ["https://instagram.com/armenianrestaurantbar"],
  "linkedin": [], "twitter": [], "youtube": [], "tiktok": [], "pinterest": [], "github": [],
  "telegram": [], "whatsapp": [], "threads": [], "discord": [],
  "contact_pages": ["https://armenianrestaurant.co.uk/"],
  "pages_crawled": 1,
  "contacts_found": true,
  "crawled_at": "2026-10-24T03:01:45+00:00",
  "error": null
}
```

The dataset has three views: **Contacts**, **Leads** (one email and phone per row, best for CSV/Excel) and **All fields**. Export to CSV, Excel or JSON, send it to Google Sheets or a CRM through Apify integrations, or read it from the API:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("malmurk/website-contact-extractor").call(run_input={
    "startUrls": ["acme-dental.com", "example-bakery.co.uk"],
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["domain"], row["email"], row["phone"], row["linkedin"])
```

### Pricing

**Pay per event:**

| Event | Price | Charged for |
|---|---|---|
| `website` | **$2 per 1,000 websites** ($0.002 each) | A website on which at least one email, phone number or social profile was found. |
| Actor start | $0.00005 per run | Once per run (per GB of memory). |

- Websites that fail to load, are blocked, or have no contact details are **free** (you still get a row with the reason).
- The price is per website, not per page: reading 5 pages of a website costs the same as reading 1.
- The run stops cleanly at the **maximum charge** you set for it.

10,000 websites with 6,000 hits cost **$12**.

### FAQ

**Why is a website missing an email I can see in my browser?**
The address may be drawn by JavaScript, shown as an image, or sit behind a contact form. This Actor reads the HTML the server sends (no browser), which keeps it fast and cheap. Try a higher `maxPagesPerWebsite` if the email is on a deeper page.

**Why are there no phone numbers on my `.com` websites?**
A local number like `(212) 555-0182` can only be read if the country is known. `.com` doesn't say which country, so set `defaultCountry`. Numbers with a `+country` prefix and click-to-call links are always read.

**Does it use proxies or bypass bot protection?**
No. A website that blocks it gets an error row and is free. No logins, no CAPTCHA solving.

**Is this legal?**
It reads publicly published business contact details from websites you choose, and respects `robots.txt`. Email addresses and phone numbers can still be personal data (GDPR, CCPA, CAN-SPAM, ePrivacy). You are responsible for having a lawful basis to store and use them, and for honouring opt-outs.

**Can I feed it the output of another Actor?**
Yes. Pass a list of website URLs (from Google Maps, OpenStreetMap, a directory scraper or your CRM) as `startUrls`, or chain it with an Apify integration.

### Limitations

- No JavaScript rendering, so contacts that only appear after scripts run are missed.
- Contact forms, images of emails and PDFs are not read.
- Personal social profiles linked from a website (e.g. a founder's LinkedIn) are reported like company profiles.

### Local development

```bash
python -m venv .venv && .venv/Scripts/pip install -r requirements-dev.txt   # Linux/macOS: .venv/bin/pip
python -m pytest -q
```

Phone parsing uses [phonenumbers](https://github.com/daviddrysdale/python-phonenumbers) (Apache-2.0), HTML parsing lxml (BSD), `robots.txt` [Protego](https://github.com/scrapy/protego) (BSD-3).

# Actor input Schema

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

Company websites, one per line (use Bulk edit to paste a whole list). A full URL (https://acme.com/) or just the domain (acme.com). Several pages of the same website count as one website.

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

The homepage plus the most promising contact, about, team and imprint pages (in 20+ languages). 5 covers most small-business sites; raise it for big corporate sites.

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

Two-letter country code (US, GB, DE, FR, RO...) used to read local phone numbers written without a +country prefix. Leave empty to guess it from the website's domain (.de = Germany, .co.uk = UK); on .com sites without it only international (+...) and click-to-call numbers are read.

## `phonesFromText` (type: `boolean`):

Also find numbers written in the page text, not only click-to-call (tel:) links. Only numbers valid for their country are kept, so dates and order numbers are ignored.

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

Skip pages a website's robots.txt disallows.

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

How many websites are processed at the same time. Pages of one website are always fetched one by one.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.djangoproject.com/",
    "mozilla.org"
  ],
  "maxPagesPerWebsite": 5,
  "phonesFromText": true,
  "respectRobotsTxt": true,
  "maxConcurrency": 20
}
```

# 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 = {
    "startUrls": [
        "https://www.djangoproject.com/",
        "mozilla.org"
    ]
};

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

# Run the Actor and wait for it to finish
run = client.actor("malmurk/website-contact-extractor").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 '{
  "startUrls": [
    "https://www.djangoproject.com/",
    "mozilla.org"
  ]
}' |
apify call malmurk/website-contact-extractor --silent --output-dataset

```

## MCP server setup

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

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/ZSs9IiDM0neVqqjdQ/builds/vjNRxDsLr3BvvZKyZ/openapi.json
