# Company Website Contact Extractor (`wei964991/company-website-contact-extractor`) Actor

Enrich company websites with published role emails, organization contacts and sources. Import CSV, Excel or a business-list Dataset; discover multilingual contact pages, compare observations and identify company/e-commerce metadata. Optional JavaScript rendering.

- **URL**: https://apify.com/wei964991/company-website-contact-extractor.md
- **Developed by:** [Wei Wang](https://apify.com/wei964991) (community)
- **Categories:** Lead generation, Marketing
- **Security:** Limited permissions
- **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 contact informations

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

## Company Website Contact Extractor

Turn a list of company websites into structured contact records with source links. Extract published department emails, organization phone numbers and addresses, contact-form pages, and company social links. Import business lists from a Dataset, CSV or Excel file. Download CSV, Excel or JSON, or open an HTML report after each run.

Static HTML is the default, with optional constrained JavaScript rendering. It favors traceable results and conservative collection over broad coverage. It does not guess email addresses, verify email deliverability, or send messages.

### Quick start

1. Click **Try for free** to open the Actor in Apify Console.
2. Enter company website URLs in **Website domains or URLs**. Start with a few websites.
3. Choose the maximum number of websites and pages per website, then click **Start**.
4. When the run finishes, open **Dataset** for records, **HTML result report** for the visual report, or **CSV export** / **Excel workbook** to download results.

The report is a static result page. No continuously running web server is needed, and the Actor exits after saving its output.

### What it extracts

| Field | Scope |
| --- | --- |
| Emails | Same-domain role addresses such as `sales@`, `info@`, and `support@`, using a conservative allowlist |
| Phones and addresses | The website's own Organization-type structured data; phone numbers must use an explicit international format |
| Contact forms | Pages containing a supported message form; forms are never submitted |
| Social links | Links already published on the website; LinkedIn links are restricted to company pages |
| Company profile | Organization name, website description and declared language, with page sources |
| Business categories | Metadata keyword rules; inferred labels, not verified business classifications |
| E-commerce signals | Supported generator/asset references and Product structured data; platform, product category and evidence |
| Observation changes | Compare a previous results Dataset by domain, distinguishing newly observed and not observed contacts |
| Sources | The page URL supporting each extracted value |

Personal-name emails and external-domain mailboxes are filtered out. Role classification is heuristic and can miss valid business addresses. Business contact details may still identify individuals.

### Import a business list

Clear the prefilled website if you only want to process your imported list. Choose one input method or combine them; the first website per domain wins, within `maxWebsites`.

- **Import business-list Dataset** (`inputDatasetId`): select a dataset you may read, such as an export from your own business-directory workflow. Only supported website, business name and business ID columns are read. This Actor does not launch another Actor or search Google Maps.
- **Upload business list (CSV / Excel)** (`inputFileUrl`): upload UTF-8 CSV or `.xlsx`, up to 5 MB. Excel uses the first worksheet and first header row, at most 100 columns. Macros, formula inputs and oversized archives are rejected or ignored.
- **Paste CSV business list** (`csvText`): paste a header row and company rows, up to 2 MB.
- **Website column name** (`websiteColumn`): optional custom source column. Otherwise `website`, `websiteUrl`, `domain`, `homepage`, `site`, `官网`, `网站`, `网址` or `url` is recognized.

Example CSV:

```csv
website,title,placeId
https://www.python.org/about/help/,Python Software Foundation,sample-1
```

`inputCompanyName` and `inputSources` preserve the imported business name and row reference separately from the website's own company metadata. Duplicate source rows are linked to one domain result (up to 20 row references). A Dataset import reads at most `maxWebsites` rows; a baseline reads at most 1,000 rows. Upload parsing is similarly capped. Missing or invalid websites are skipped and counted. Arbitrary reviews, personal profiles and other unrelated source columns are not copied into results.

### Compare successive runs

Set **Previous results Dataset for comparison** (`previousDatasetId`) to a prior results Dataset, then run the current list. These Dataset fields request read-only storage permissions; they do not request full account access.

`changes.status` is `new`, `changed`, `unchanged`, `inconclusive`, `baseline_ambiguous` or `not_requested`. `added` lists newly observed contacts; `notObserved` lists earlier values not found within the current page budget. **Not observed does not prove deletion.** Failed, partial or timed-out observations do not establish changes; duplicate baseline domains are marked ambiguous. Coverage, page budgets and site changes can affect comparisons. Domains absent from the current input are not compared.

### Discovery and dynamic pages

Contact-link discovery recognizes common English, European, Chinese, Japanese and Korean contact/about terms. With `discoverSitemaps` enabled (default), up to two same-site sitemap documents are examined. XML entity expansion is blocked. The same robots rules and HTML-page attempt budget apply to discovered pages; sitemap requests are additional requests.

Enable **Render JavaScript pages** (`renderJavaScript`) for supported dynamic sites. Chromium executes the page's JavaScript with a wait of 0–3,000 ms (default 1,000). All HTTP resources pass through the same domain, public-DNS, robots and interval checks. Only same-site GET scripts, stylesheets and fetch/XHR resources are supported, with a 20-resource limit per page. Third-party CDNs, WebSockets, service workers, form submissions and login flows are blocked. No stealth, CAPTCHA bypass or proxy rotation is supplied. These restrictions deliberately limit rendering coverage.

JavaScript mode has a different contact-event price: **$12 per 1,000 qualifying websites instead of $3** under the pricing below. It uses more time and compute; use at least **1,024 MB** memory and start with a small sample. Static mode remains the default. The result report is a saved HTML document, not an always-on server.

### Example input

```json
{
  "websites": ["https://www.python.org/about/help/"],
  "maxWebsites": 20,
  "maxPagesPerWebsite": 4,
  "maxConcurrency": 3,
  "requestDelaySeconds": 1.5,
  "requestTimeoutSeconds": 15,
  "websiteTimeoutSeconds": 60,
  "runTimeoutSeconds": 300,
  "excludedDomains": []
}
```

Alternatively, supply `startUrls: [{"url": "https://your-company.example"}]`. Use one URL per domain; the first supplied URL is the starting page. The bare domain and its `www` alias are deduplicated. The Actor follows relevant links on that host and its `www` alias, within the page budget.

| Setting | Default | Allowed range |
| --- | --- | --- |
| Maximum websites | 20 | 1–1,000 |
| Maximum pages per website | 4 | 1–10 |
| Concurrent websites | 3 | 1–10 |
| Minimum interval per host | 1.5 seconds | 1–30 seconds |
| Request timeout | 15 seconds | 3–30 seconds |
| Website timeout | 60 seconds | 5–180 seconds |
| Crawl time limit | 300 seconds | 10–1,800 seconds |

The website limit includes invalid input attempts. The page budget includes failed HTML-page attempts; robots checks and bounded redirects are additional requests. Robots delays can increase the request interval. Sitemap requests and optional browser resource requests are additional to the HTML-page budget. The crawl time limit excludes output export and SDK shutdown. When raising it, also raise the Actor's platform timeout.

### Output

The default dataset contains one record per processed, deduplicated website, including diagnostic records for unsuccessful websites. A record does not necessarily contain a contact.

```json
{
  "domain": "example-company.test",
  "companyName": "Example Company (synthetic example)",
  "status": "ok",
  "primaryEmail": "sales@example-company.test",
  "emails": ["sales@example-company.test"],
  "phones": [],
  "addresses": [],
  "socials": {},
  "contactForms": [],
  "pagesCrawled": 1,
  "hasContacts": true,
  "sources": [{
    "field": "emails",
    "value": "sales@example-company.test",
    "url": "https://example-company.test/contact"
  }],
  "emailVerification": "not_performed"
}
```

This abbreviated example is synthetic. Actual records also include timestamps, page attempts, privacy notes, and diagnostics.

| Status | Meaning |
| --- | --- |
| `ok` | Supported contact information found without recorded access errors |
| `partial` | Contacts found, with some access errors or incomplete coverage |
| `no_contacts` | No contact in the supported categories was found; social links may still be present |
| `blocked` | Access or robots rules prevented the crawl |
| `error` | The website could not be processed |
| `timeout` | A website or crawl time budget was reached |
| `invalid_input` | The supplied website was rejected |

The default key-value store contains **report.html** (with **REPORT** compatibility alias), **results.csv** (also **CSV**), **results.xlsx**, **social-links.csv**, **changes.json**, **RESULTS** (all records), **OUTPUT** / **summary.json** (summary), and **THIRD_PARTY_NOTICES** / **notices.txt** (dependency acknowledgments). Social-link exports contain platform/profile/source columns for manual review or an independently chosen downstream tool; no paid social Actor is called. If no website can be read, the Actor fails after saving the diagnostics.

### Typical workflows

- Enrich a company directory with published contact channels and evidence links.
- Process website URLs exported from a separate business-directory tool.
- Review contact pages across websites you own or are authorized to process.
- Export results for manual review before importing them into another system.

The Actor does not include Google Maps search, social-platform scraping, a contact database, or outreach functionality.

### Access controls and limitations

Only public HTTP/HTTPS domain URLs on standard ports are supported. Private/reserved network addresses and credential-bearing URLs are rejected. Query strings and fragments are removed. Cross-host redirects, other than the same site's `www` alias, and HTTPS downgrades are rejected.

The crawler identifies itself, honors robots.txt and declared crawl delays, and stops when robots access is uncertain. It does not bypass logins, CAPTCHAs, access blocks, or rate limits. No proxy rotation or login cookies are provided. Optional browser execution is constrained as described above.

Image-only emails, complex obfuscation, externally hosted contact forms, many text-only phone listings and dynamic pages requiring blocked resources remain unsupported. Results are not guaranteed complete, current, accurate, or deliverable. No general accuracy or speed claim is made from the small release-validation sample.

### Pricing

**Pay-per-event pricing is active from November 4, 2026 (Asia/Shanghai).** Check this Actor's Pricing tab for the current prices. A local CLI run does not call paid cloud charging endpoints.

| Mode | Price per qualifying website | Per 1,000 qualifying websites |
| --- | ---: | ---: |
| Static HTML (default) | $0.003 | $3 |
| JavaScript enabled (`renderJavaScript=true`) | $0.012 | $12 |

Pricing rules:

- A website qualifies only when at least one page was read and the result contains a supported published **role email, organization phone or message form**, with `ok` or `partial` status. A single form or phone also qualifies; an email is not required.
- Multiple contacts and pages on the same domain cause **at most one contact event per run**. JavaScript mode replaces the static price for the entire run, even if a particular contact was visible without rendering. The two prices are not added together.
- No-contact, profile-only, social-only, blocked, timeout and invalid-input diagnostics do not trigger a contact event. These diagnostic records remain visible in the output, so total Dataset rows can exceed charged events.
- There is **no separate startup fee or automatic Dataset-row fee**. Standard-run Apify platform usage is included in the event price. Separate tools and storage retained or used outside the run can have their own platform charges.
- Re-running a website is a new extraction and may incur a new event fee. There is no subscription or lifetime access charge for this Actor.
- Set a maximum cost per run in Apify Console. The minimum is $0.003; JavaScript mode needs at least $0.012 for one qualifying website. The website attempt limit is conservatively capped at the number of events the remaining budget can afford, so a run may stop below `maxWebsites` even if some attempted sites return no contacts. A budget too low for one event reads no websites. `budget_skipped` records contain no extracted contacts.
- `summary.json` reports the run's pricing model, event price, charged count and budget limit. Each result's `billing` field describes event eligibility; eligibility alone does not prove an actual charge.

For example, 100 qualifying websites cost $0.30 in static mode or $1.20 with JavaScript enabled. Use the Pricing tab and actual run billing as the authoritative record. Start with a small sample; contact coverage and extraction speed vary by website.

### Data handling and responsible use

Uploaded files are stored by Apify; file URLs may contain access signatures and should be treated as private. The uploader URL is used only to download the selected file (HTTPS, public DNS, same-host redirects and size limits); it is not copied into result records. This download is separate from target-website crawling.

The Actor processes the website list supplied for the current run and saves extracted records and sources to that run's Apify dataset and key-value store. It does not build a shared cross-customer contact database or save raw page HTML. Platform logs and queues can contain supplied URLs; do not include secrets or sensitive personal paths.

Keep runs and storage private unless you have permission to share their contents. Manage retention, exports, and deletion in your Apify account. Apify's platform storage and access controls apply; this Actor does not promise automatic deletion after a fixed period.

Public availability, a role email, or a robots allowance does not by itself authorize collection, reuse, or marketing. Use only websites and purposes for which you have the necessary rights and legal basis, respect website terms and objections, and review results before use. The tool does not establish a legal basis or provide legal advice.

For technical issues or a privacy/opt-out concern, use this Actor's **Issues** tab. Provide only the domain and a non-sensitive description; never post credentials, private run links, or personal contact data publicly. `excludedDomains` can prevent specified domains from being visited in your runs.

### Open-source acknowledgments

The extraction and reporting logic was independently written. The project uses Crawlee for Python and the Apify SDK under Apache-2.0, Beautiful Soup under MIT, and other dependencies under their respective licenses. Original packaged license and notice texts are retained in the deployment. The **THIRD_PARTY_NOTICES** output provides acknowledgments after a run.

This product is independent of those projects and of the websites it visits. No affiliation, endorsement, or ownership of third-party content is implied.

### 中文说明

输入企业官网，或清空示例网站后选择 Dataset／上传 CSV、Excel／粘贴 CSV，点击 **Start**。运行结束后查看 Dataset，或打开 **HTML result report** 查看网页报告，下载 CSV、Excel、社交链接及变化结果。可选上次结果 Dataset 做变化对比；“本次未发现”不等于已删除。默认支持多语言联系页及 sitemap 发现；可选 JavaScript 渲染，建议至少 1,024 MB，覆盖受同站资源限制。公司分类、电商平台为基于元数据的规则判断，并非认证结果。仅提取支持范围内的公开角色邮箱和企业结构化联系信息，并保留来源；不猜测邮箱、不验证可投递性、不发送营销消息。按结果收费已于北京时间 2026 年 11 月 4 日启用，以 Pricing 页当前价格为准。普通模式每个有支持范围内邮箱／企业电话／留言表单的网站 $0.003（每千个 $3），开启 JavaScript 后为 $0.012（每千个 $12），不叠加；同一域名每次运行最多计一次，重跑可再次计费。无联系信息或仅诊断记录不收结果费，无启动费，标准运行平台用量包含在事件价格内。预算会限制本次尝试的网站数量。请先小批量试用，并确认网站访问和数据处理权限。

# Actor input Schema

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

Enter URLs directly, or clear this field and import a Dataset / CSV / Excel list. Sources can also be combined.

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

Alternative input for integrations, in standard {url: ...} format.

## `inputDatasetId` (type: `string`):

Read your existing business-list dataset. Supported website/name/placeId columns only; no Maps search or other Actor is started.

## `inputFileUrl` (type: `string`):

UTF-8 CSV or .xlsx, maximum 5 MB; first worksheet/header row; no macros. The uploader supplies a signed HTTPS download URL.

## `inputFileFormat` (type: `string`):

Auto detects CSV or XLSX.

## `csvText` (type: `string`):

Header row required, e.g. website,title,placeId. Up to 1,000 rows / 2 MB.

## `websiteColumn` (type: `string`):

Exact source column name if automatic website / websiteUrl / domain / 官网 recognition does not match.

## `previousDatasetId` (type: `string`):

Read a previous extraction dataset (up to 1,000 rows). Differences mean added / not observed, never proven deletion.

## `discoverSitemaps` (type: `boolean`):

Also inspect up to two same-site sitemap XML documents, honoring robots. These requests are additional to the HTML page budget.

## `renderJavaScript` (type: `boolean`):

Use Chromium for supported dynamic content. PPE pricing: USD 0.012 per website with supported contacts instead of USD 0.003 in static mode (not stacked). Check the Pricing tab for current prices. Only same-site GET resources; no login or access bypass. 1,024 MB recommended.

## `renderWaitMs` (type: `integer`):

Extra wait after the page loads when JavaScript rendering is enabled.

## `maxWebsites` (type: `integer`):

Caps attempted input websites, including invalid entries.

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

HTML-page attempt limit. robots.txt and bounded redirects are additional requests.

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

Maximum different websites processed concurrently; per-host intervals still apply.

## `requestDelaySeconds` (type: `number`):

robots.txt crawl delays or request-rate limits can increase this interval.

## `requestTimeoutSeconds` (type: `integer`):

Maximum duration of one HTTP request.

## `websiteTimeoutSeconds` (type: `integer`):

Maximum crawl duration of one website, including delays and robots checks.

## `runTimeoutSeconds` (type: `integer`):

Pending websites become timeout diagnostics. Export/SDK shutdown can take additional time.

## `excludedDomains` (type: `array`):

Domains not to visit, for example opt-out or contractual exclusions. www and bare domain are treated together.

## Actor input object example

```json
{
  "websites": [
    "https://www.python.org/about/help/"
  ],
  "inputFileFormat": "auto",
  "discoverSitemaps": true,
  "renderJavaScript": false,
  "renderWaitMs": 1000,
  "maxWebsites": 20,
  "maxPagesPerWebsite": 4,
  "maxConcurrency": 3,
  "requestDelaySeconds": 1.5,
  "requestTimeoutSeconds": 15,
  "websiteTimeoutSeconds": 60,
  "runTimeoutSeconds": 300,
  "excludedDomains": []
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `report` (type: `string`):

No description

## `csv` (type: `string`):

No description

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

No description

## `notices` (type: `string`):

No description

## `excel` (type: `string`):

No description

## `socialLinks` (type: `string`):

No description

## `changes` (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://www.python.org/about/help/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wei964991/company-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 = { "websites": ["https://www.python.org/about/help/"] }

# Run the Actor and wait for it to finish
run = client.actor("wei964991/company-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 '{
  "websites": [
    "https://www.python.org/about/help/"
  ]
}' |
apify call wei964991/company-website-contact-extractor --silent --output-dataset

```

## MCP server setup

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