# Website Contact Form Scraper (`automation-lab/javascript-website-contact-extractor`) Actor

Crawl public websites and bounded contact/about pages. Extract published emails, phone numbers, social profiles, contact URLs, form fields, and page provenance, rendering JavaScript when needed.

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

## Pricing

from $9.60 / 1,000 item extracteds

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/actors/running/actors-in-store.md#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 Form Scraper

Turn a list of public websites into source-attributed contact records. Website Contact Form Scraper finds published emails, website contact numbers, social profiles, contact/about URLs, and contact-form structure while preserving the page where each value was found.

It requests lightweight HTML first and renders JavaScript only when needed. You can also force browser rendering for a client-side contact page or force HTTP-only crawling for predictable low-cost jobs.

### What does Website Contact Form Scraper do?

For each supplied website, the Actor:

1. validates that the target is a public HTTP(S) address;
2. loads the homepage or supplied page;
3. discovers bounded same-site contact, support, about, team, help, and legal pages;
4. renders client-side pages with Chromium when automatic detection requires it;
5. extracts public email addresses, phone numbers, and social links;
6. describes contact forms without submitting them;
7. returns one normalized website record with page-level provenance.

The Actor never submits a form, sends a message, logs in, or guesses private contact data.

### Who is it for

- **Lead enrichment teams** adding public contact channels to prospect domains.
- **Sales operations teams** preparing website records for a CRM import.
- **Directory operators** checking contact and social links published by listed organizations.
- **Researchers** comparing how organizations expose contact pages and forms.
- **Developers** building repeatable website contact extraction into data pipelines.

If you need only one row per displayed email, use [Website Email Extractor](https://apify.com/automation-lab/website-email-extractor). If you need static business contact details including postal-address text, use [Website Contact Details Scraper](https://apify.com/automation-lab/website-contact-details-extractor).

### Why use this website contact extractor?

- **JavaScript-aware:** automatic HTTP-first crawling falls back to Chromium for page shells or pages without usable contact/link signals.
- **Bounded crawling:** page and depth controls prevent an input website from becoming an unbounded crawl.
- **One record per website:** convenient for spreadsheet, CRM, and database enrichment.
- **Contact-form structure:** captures action, method, field names/types, labels, and required flags without submitting data.
- **Page provenance:** every page records its URL, title, rendering method, and extracted values.
- **Public-target validation:** local/private network destinations and unsafe redirects are rejected.
- **No automatic paid proxy fallback:** proxy costs remain under your control.

### What data can I extract?

| Field | Description |
| --- | --- |
| `websiteUrl` | Normalized input URL |
| `finalUrl` | Homepage URL after redirects |
| `domain` | Final hostname |
| `websiteName` | Name inferred from metadata or title |
| `emails` | Unique published emails across crawled pages |
| `phoneNumbers` | Unique published phone strings |
| `socialProfiles` | Network, profile URL, and source page |
| `contactPageUrls` | Same-site contact, support, or help URLs |
| `aboutPageUrls` | Same-site about, company, or team URLs |
| `contactForms` | Form action, method, name, and public field metadata |
| `pages` | Page URL, title, rendered flag, and per-page contacts |
| `pagesCrawled` | Number of successfully inspected pages |
| `pagesFailed` | URL and concise failure reason for failed pages |
| `foundContactDetails` | Whether useful public contact evidence was found |
| `scrapedAt` | ISO 8601 completion timestamp |

Empty arrays mean that the inspected pages did not expose that field. They do not prove that the organization has no contact channel elsewhere.

### How to scrape a website contact form

1. Open the Actor in Apify Console.
2. Add one or more public website URLs.
3. Keep **JavaScript rendering** set to **Automatic** for most jobs.
4. Choose a page limit and link depth.
5. Click **Start**.
6. Open the Dataset tab to review one result per input website.
7. Export results as JSON, CSV, Excel, XML, or RSS, or consume the dataset API.

A useful first run is:

```json
{
  "startUrls": [{ "url": "https://apify.com" }],
  "maxWebsites": 1,
  "maxPagesPerWebsite": 3,
  "maxDepth": 1,
  "renderingMode": "auto"
}
```

### Input parameters

#### `startUrls`

Required list of public HTTP(S) websites. Domain-only values such as `apify.com` are normalized to HTTPS. Duplicate strings are processed once.

#### `maxWebsites`

Maximum unique input websites to process. The default is 10 and the maximum is 1,000.

#### `maxPagesPerWebsite`

Maximum successful pages inspected for each website. The default and maximum are 5, keeping browser-mode cost and runtime bounded. A smaller value lowers runtime; a larger value can improve coverage on sites with several contact or team pages.

#### `maxDepth`

Maximum same-site contact/about link depth from the supplied page. Use 0 for only the supplied URL, 1 for its relevant links, or up to 3 for deeper bounded discovery.

#### `renderingMode`

- `auto` — request HTML first; render when the response fails, looks like a JavaScript shell, or exposes no usable contact/link signals.
- `browser` — render every inspected page with Chromium.
- `http` — never launch Chromium.

#### `includeSubdomains`

When enabled, relevant pages on subdomains are eligible for crawling. It does not turn the run into a general subdomain crawler.

#### `proxyConfiguration`

Optional Apify Proxy or custom proxy settings. The Actor does not silently switch to residential proxies. Choose and pay for a proxy only when your target requires one and you are authorized to access it.

### Output example

A shortened real-behavior example looks like this:

```json
{
  "websiteUrl": "https://apify.com/",
  "finalUrl": "https://apify.com/",
  "domain": "apify.com",
  "websiteName": "Apify",
  "emails": ["hello@apify.com"],
  "phoneNumbers": [],
  "socialProfiles": [
    {
      "network": "github",
      "url": "https://github.com/apify",
      "sourceUrl": "https://apify.com/contact"
    }
  ],
  "contactPageUrls": ["https://apify.com/contact"],
  "aboutPageUrls": ["https://apify.com/about"],
  "contactForms": [],
  "pages": [
    {
      "url": "https://apify.com/contact",
      "title": "Contact us · Apify",
      "rendered": false,
      "emails": ["hello@apify.com"],
      "phoneNumbers": [],
      "socialLinks": [],
      "contactFormCount": 0
    }
  ],
  "pagesCrawled": 3,
  "pagesFailed": [],
  "foundContactDetails": true,
  "scrapedAt": "2026-08-24T12:00:00.000Z"
}
```

The complete record includes all social links and all inspected page provenance.

### How much does it cost to enrich websites with public contacts?

Pricing uses one `start` event per run plus one `item` event per website returned. Contact fields, social profiles, contact forms, and page provenance included in that website record have no separate charge.

Current BRONZE pricing:

- run start: **$0.005**;
- website processed: **$0.016**.

Examples at BRONZE, excluding optional proxy charges:

| Websites | Estimated Actor price |
| ---: | ---: |
| 1 | $0.021 |
| 10 | $0.165 |
| 100 | $1.605 |

Subscription tiers apply lower per-website rates. Actual platform compute and any user-selected proxy usage appear separately in Apify usage details.

### JavaScript rendering strategy

Automatic mode is designed to avoid paying browser cost on ordinary server-rendered pages. It uses browser rendering when:

- the HTTP request fails and a browser may still load the page;
- visible HTML resembles a client-side shell;
- the HTML provides neither direct contact evidence nor bounded contact/about links.

Browser mode blocks images, media, and fonts because they are not needed for contact extraction. Scripts, styles, and data requests remain available so client-side pages can render.

### Contact form data and website contact form examples

A discovered form is included only when its fields or text look contact-related. The Actor records public HTML metadata such as:

- form page and action URL;
- `GET` or `POST` method;
- form name or ID;
- input, textarea, and select names;
- control types, labels/placeholders, and required flags.

It does not type into fields, bypass validation, solve CAPTCHAs, or submit the form. Use the metadata to compare forms or route a later authorized workflow.

### Prospect enrichment workflow

For recurring enrichment:

1. export website URLs from your CRM;
2. map them to `startUrls`;
3. schedule the Actor daily, weekly, or monthly;
4. join dataset rows back to your domain key;
5. store `scrapedAt` and page provenance with the contact values;
6. review changed values before outreach.

The Actor returns current observations. Change detection and CRM updates belong in your downstream workflow.

### Export and integrations

The default dataset works with:

- Apify CSV, JSON, Excel, XML, and RSS exports;
- webhooks triggered after successful runs;
- Make and Zapier Apify integrations;
- Google Sheets or database loaders;
- Python, JavaScript, and REST API clients;
- scheduled Apify Tasks.

One item represents one input website, which makes domain-keyed joins straightforward.

### Run with the Apify API using cURL

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

Never commit an API token to source control.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/javascript-website-contact-extractor').call({
    startUrls: [{ url: 'https://apify.com' }],
    maxWebsites: 1,
    maxPagesPerWebsite: 3,
    maxDepth: 1,
    renderingMode: 'auto',
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/javascript-website-contact-extractor").call(
    run_input={
        "startUrls": [{"url": "https://apify.com"}],
        "maxWebsites": 1,
        "maxPagesPerWebsite": 3,
        "maxDepth": 1,
        "renderingMode": "auto",
    }
)
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Actor to Claude Code:

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

#### Claude Desktop, Cursor, and VS Code setup

Use this MCP configuration in **Claude Desktop**, **Cursor**, or **VS Code** clients that support remote HTTP MCP servers:

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

Example prompts:

- “Extract public contact details and provenance from these three company websites.”
- “Force browser rendering on this contact page and list its public form fields.”
- “Return one CRM-ready website record for each supplied domain.”

### Limits and expected failure behavior

- Some sites block automated access or require authentication.
- CAPTCHA, login-only, and private pages are not bypassed.
- A website may publish no email or phone number.
- Regex-based phone extraction can omit unusual formats or capture a number that needs human review.
- Client-side widgets inside blocked third-party iframes may not be visible.
- Automatic mode is heuristic; force `browser` when you know contacts render client-side.
- Page limits intentionally trade completeness for bounded cost and runtime.
- Redirects to private/local networks are rejected.
- A failed page is listed in `pagesFailed`; other successfully fetched pages can still produce a website record.

### Tips for better results

- Start with `auto`, 3–5 pages, and depth 1.
- Supply the canonical company homepage instead of a search result URL.
- Force `browser` only for websites known to render contact content with JavaScript.
- Increase page limits before increasing depth.
- Enable subdomains only when the organization hosts contact pages separately.
- Use a proxy only when direct access is blocked and your use is permitted.
- Review provenance before using contact data operationally.

### Responsible and legal use

Scrape only publicly accessible pages you are authorized to process. Follow applicable website terms, robots guidance, privacy, database, marketing, and anti-spam laws. Public availability does not automatically grant permission for every downstream use.

Do not use the Actor to target private networks, access authenticated content without authorization, build sensitive-person profiles, or send unsolicited messages. The user is responsible for retention, lawful basis, suppression lists, and outreach compliance.

### Troubleshooting

#### Why did a website return empty contact arrays?

Open `pages` and `pagesFailed`. The inspected pages may not publish contact details, the relevant page may exceed your limits, or the website may need browser mode. Try `renderingMode: "browser"` and a slightly higher page limit.

#### Why was my URL rejected as a private target?

The Actor blocks loopback, local, link-local, and private network destinations, including redirects. Supply a public website URL.

#### Why is browser mode slower?

Chromium executes page scripts and waits for the rendered DOM. Automatic or HTTP mode is faster when server-rendered HTML already contains the needed data.

#### Does the Actor submit contact forms?

No. It records public form structure only.

### Related Automation Lab Actors

- [Website Contact Details Scraper](https://apify.com/automation-lab/website-contact-details-extractor) — static contact details plus postal-address text.
- [Website Email Extractor](https://apify.com/automation-lab/website-email-extractor) — one source-attributed row per displayed email.
- [Company Leadership Scraper](https://apify.com/automation-lab/company-leadership-contact-finder) — public leadership records and explicitly published professional contacts.
- [Link-in-Bio Contact Leads Scraper](https://apify.com/automation-lab/link-in-bio-contact-leads-scraper) — creator contacts from supported public bio-link profiles.

### FAQ

#### Can I process multiple websites?

Yes. Add them to `startUrls` and set `maxWebsites` high enough. Each processed website produces one dataset item.

#### Can I crawl an entire domain?

No. This is intentionally a bounded contact/about crawler, not a general website crawler.

#### Are email addresses verified?

They are normalized from public page content but not mailbox-verified. Treat them as observed website data.

#### Are proxy charges included in the per-website event?

The Actor event price is separate from optional user-selected proxy usage shown by Apify. No residential proxy is enabled automatically.

#### Can I schedule recurring runs?

Yes. Save the input as an Apify Task and attach a schedule. The Actor returns snapshots; compare datasets in your own workflow when you need change tracking.

#### What happens when one page fails?

The Actor records that page in `pagesFailed` and continues with other bounded pages. Invalid top-level input still fails the run with a non-zero status.

# Actor input Schema

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

Public HTTP(S) websites to enrich with published contact details.

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

Maximum number of unique input websites to process.

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

Maximum homepage, contact, about, help, team, or legal pages to inspect per website.

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

How many same-site contact/about link levels to follow from each homepage.

## `renderingMode` (type: `string`):

Auto uses HTTP first and renders pages that look client-side; Browser renders every page; HTTP never launches a browser.

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

Allow discovered contact/about pages on subdomains of each input website.

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

Optional Apify Proxy or custom proxies. No paid proxy fallback is enabled automatically.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    }
  ],
  "maxWebsites": 10,
  "maxPagesPerWebsite": 5,
  "maxDepth": 1,
  "renderingMode": "auto",
  "includeSubdomains": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Website contact records in the default dataset overview.

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

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/javascript-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": [{ "url": "https://apify.com" }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/javascript-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": [
    {
      "url": "https://apify.com"
    }
  ]
}' |
apify call automation-lab/javascript-website-contact-extractor --silent --output-dataset

```

## MCP server setup

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