# Facebook Lead Scraper (`scraper-engine/facebook-lead-scraper`) Actor

Extract targeted leads from Facebook using the Facebook Lead Scraper. Collect profile names, page details, descriptions, emails, websites, phone numbers, and follower counts automatically. Ideal for lead generation, outreach campaigns, and market research.

- **URL**: https://apify.com/scraper-engine/facebook-lead-scraper.md
- **Developed by:** [Scraper Engine](https://apify.com/scraper-engine) (community)
- **Categories:** Automation, Lead generation, Social media
- **Stats:** 21 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$19.99/month + usage

To use this Actor, you pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period. You also pay for the Apify platform usage, which gets cheaper the higher Apify subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#rental-actors

## 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

### **Facebook** Lead Scraper 📱

**Facebook** Lead Scraper finds **public** Facebook entities that match your keywords — **Pages**, **personal profiles**, **post permalinks**, **videos** and **group threads** — and extracts the **email addresses those entities publish publicly**.

This is the **general-purpose** Facebook lead actor: it casts the widest net across every Facebook entity type. If you only want one slice, use the focused siblings instead (`facebook-page-lead-scraper` for business Pages, `facebook-profile-lead-scraper` for personal profiles, `facebook-b2b-lead-scraper` for B2B company Pages).

Every row it writes carries a **real value in every column** — no blanks, no placeholders. If a keyword yields nothing, the run says so in the log and writes nothing rather than padding the dataset.

### How it actually works 🔬

Facebook has no public search API and its own site search is behind the login wall, so lead discovery has to go through public web search. The actor runs a three-stage pipeline:

**1. Discovery — where the Facebook URLs come from**

Google no longer serves the destination URL in its result anchors: every organic link is an opaque `/goto?url=CAES…` wrapper whose target is encrypted and returns HTTP 400 if you follow it. The actor recovers the real URLs from the **inline JSON data island** Google still embeds in the same page — plain-text `["url","title","snippet"]` triples sitting right beside those wrappers. Four merged recovery layers are tried in order (direct anchors → JSON island → bare island URLs → raw-text sweep), then **Brave Search** and **DuckDuckGo** are used to top up. Two query scopes are merged: a broad `site:facebook.com` scope that returns every entity type, and a `site:facebook.com/*/about` scope that concentrates on Page About tabs.

**2. Email — where the text actually comes from**

The `emailSource` column on every row states exactly where the address was read from — **and, critically, who it belongs to**:

| `emailSource` | Where it came from | Whose address is it? |
| --- | --- | --- |
| `page_about_contact` | The **Contact info** block on the entity's own logged-out `/about` tab (Facebook's `profile_fields` payload). **Strongest provenance** — this is the address the owner typed into their own contact form. | The owner of the account in `url` |
| `facebook_page` | The public contact payload embedded in the entity's own Facebook page. | The owner of the account in `url` |
| `page_snippet` | The search snippet for that Page/profile URL, when the account's own surface could not be read. | The owner of the account in `url` |
| `linked_website` | Facebook exposed no address, so the actor followed the external website that account links to and read its home / contact / about page. | The owner of the account in `url` |
| `post_body_author` | The address is written **in the body of a post or group thread**. | ⚠️ **The person who wrote that post — NOT the owner of the timeline or group the permalink sits on.** |

That last distinction matters and the actor never blurs it. A `post_body_author` row means "someone posted this address in this thread"; it is a real, useful lead, but the address is *not* a claim about whoever owns the URL. Rows whose address belongs to the account itself are always one of the other four labels. Accounts are also processed first, so a run fills up with owner-verified contacts before it falls back to post bodies.

**3. Filtering** — `emailDomains` is applied to the final address, `maxEmails` caps each keyword, and infrastructure noise (Meta CDN addresses, asset filenames, Sentry DSNs, package version specs, tutorial placeholders like `you@example.com`, and prose false positives like `that@gmail.com`) is rejected before a row is written.

### Honest limits ⚠️

Verified live against Facebook, not assumed. These are the things that **do not** work, so you know what the actor is not doing:

- **`mbasic.facebook.com` is gone** for anonymous use — it resolves but redirects straight to the login wall.
- **`m.facebook.com`** likewise redirects to `www` and then to login.
- **The unauthenticated Graph API is closed** — `graph.facebook.com/<page>` answers `403 (#200) Provide valid app ID`.
- **Page oEmbed is retired** (`/plugins/page/oembed.json/` → 404), and the page-plugin iframe renders only a name and cover image, no contact data.
- **Google's `<cite>` breadcrumb is useless for Facebook** — for facebook.com results it renders engagement metadata ("40+ reactions · 2 years ago"), not the URL. That is why the JSON data island is used instead.
- **Bing returns nothing** for site-scoped Facebook queries; it silently drops the `site:` operator.
- **Brave and DuckDuckGo rate-limit aggressively** per exit IP, so they are used only as a top-up behind Google, each across a rotating proxy ladder.
- `www.facebook.com` **does** answer logged-out requests — including the `/about` tab with its full `profile_fields` contact payload — but **only** when the complete Chrome navigation header set (`sec-ch-ua`, `sec-ch-ua-mobile`, `sec-ch-ua-platform`, `sec-fetch-dest/mode/site/user`, `upgrade-insecure-requests`) is sent. A bare User-Agent gets a 1,542-byte HTTP 400 stub. The actor always sends the full set.
- The email inside that payload is **JSON-escaped**: the at-sign is emitted as the six-character sequence backslash-u-0-0-4-0, never as a literal `@`, so a naive email regex over the raw HTML returns **zero** matches. The actor un-escapes every such sequence in the blob before scanning.
- An `/about` page can be **19 MB**, but the `profile_fields` block is emitted early and consistently (measured at offsets 635,209–636,471, closing by 645,800, across Pages ranging from 868 KB to 19.7 MB). The actor caps the read at 2 MB rather than streaming the whole document for one address.
- Entity type is reported as `page_or_profile` when a bare vanity slug genuinely cannot be told apart from a personal profile without fetching it; it is refined to `page` or `profile` whenever the page is actually fetched.

### Support and feedback

- **Bug reports**: Open a ticket in the repository Issues section
- **Custom features**: Contact our enterprise support team
  *Lead: dev.scraperengine@gmail.com*

### Extractable Data Table 📊

| Data Type | Description |
| --- | --- |
| Email address | The public email address published by the Facebook entity, its page, or the website it links to. |
| Title | The name of the Page / profile, or the headline of the post or group thread. |
| Description | The public bio, About text, or post body that surrounded the contact detail. |
| URL | The canonical `https://www.facebook.com/…` link to the entity. |
| Entity type | Which kind of Facebook object the lead came from: `page`, `profile`, `page_or_profile`, `post`, `group`, `group_post`, `video`, `photo` or `event`. |
| Email source | Which of the five sources the address was read from, and therefore whose address it is. |
| Network | The Facebook domain the result was served on (`facebook.com`, `fb.com`, `fb.me`, `fb.watch`). |
| Keyword | The keyword that surfaced the lead. |

### Key Features of **Facebook** Lead Scraper

- ⭐ **Widest coverage** of the four Facebook lead actors — Pages, profiles, posts, videos and group threads in one run
- ⭐ **Resilient URL recovery** that survives Google's encrypted `/goto` link wrappers
- ⭐ **Multi-engine discovery** — Google, Brave and DuckDuckGo, each behind its own proxy ladder
- ⭐ **Four-tier contact extraction** — the Page's own `/about` contact block first, then the page payload, the search snippet, and finally the linked external website
- ⭐ **Unambiguous provenance** — every row states where its email came from *and whose address it is*
- ⭐ **No empty rows, ever** — a row is written only when every field carries a real value
- ⭐ **Domain filtering** to keep only the mailbox providers or corporate domains you care about
- ⭐ **Location targeting** to narrow leads to a city or region
- ⭐ **Aggressive junk rejection** — CDN, asset, tracking and placeholder addresses never reach your dataset
- ⭐ **Real-time dataset writes** so partial results are available while the run is still going

### How to use **Facebook** Lead Scraper 🚀

1. ✅ Enter one or more **keywords** describing the leads you want (`photographer`, `dentist`, `marketing agency`…)
2. ✅ Optionally set a **location** to geo-narrow the search
3. ✅ Optionally set **email domains** to keep only e.g. `@gmail.com` addresses
4. ✅ Set **maximum leads per keyword**
5. ✅ (Optional) Open **Advanced Settings** to change search depth or turn the enrichment stages off
6. ✅ **Start** the run — rows appear in the dataset as they are found
7. ✅ **Export** to CSV, Excel, JSON or via the API

### Use Cases 🎯

Marketing and Outreach
🎯 Build targeted lead lists for campaigns
🎯 Reach out to potential customers using published contact information

Research and Analysis
🎯 **Analyze** which niches publish contact details openly
🎯 Study how businesses present themselves on Facebook

Business Development
🎯 **Identify** potential leads and partners across Pages, profiles and group threads
🎯 Expand your customer base with relevant, verified-public contacts

### Why choose us? 💎

Every claim in this README was verified against the live site rather than assumed, and the actor tells you in the log exactly which source produced each lead — or truthfully reports that a keyword produced nothing. Facebook's anonymous surface changes often; the multi-layer discovery and three-tier extraction exist precisely so a single change on their side does not take the whole actor to zero.

### **Facebook** Lead Scraper Legal Guidelines ⚖️

The actor reads only **publicly available** information from **public** Facebook entities — nothing behind a login, nothing private, no authentication and no session tokens are used.

⚖️ **Ensure** compliance with Facebook's terms of service when using the scraper
⚖️ **Do not** use the tool to extract private or sensitive information without consent
⚖️ **Use** extracted data responsibly and ethically for legitimate purposes only
⚖️ Respect user privacy and adhere to applicable data protection laws (GDPR, CCPA)
⚖️ Refrain from using the scraper for spamming or unsolicited communication

### Input Parameters 🧩

📦 Example Input (JSON)

```json
{
  "keywords": ["photographer"],
  "platform": "Facebook",
  "location": "",
  "emailDomains": ["@gmail.com"],
  "maxEmails": 20,
  "engine": "legacy",
  "maxSearchPages": 4,
  "enrichFromFacebookPage": true,
  "enrichFromLinkedWebsite": true,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

### Input Table

| Field | Description |
| --- | --- |
| keywords | **Required.** Keywords to find relevant Facebook entities |
| platform | Platform to scrape (`Facebook`) |
| location | Optional city/region to narrow the search |
| emailDomains | Keep only emails on these domains; empty = all domains |
| maxEmails | Maximum leads to collect per keyword (default 20) |
| engine | Engine type (`legacy`) |
| maxSearchPages | Search-result pages to walk per keyword (default 4) |
| enrichFromFacebookPage | Open the logged-out Facebook page when the snippet shows no email (default true) |
| enrichFromLinkedWebsite | Follow the entity's external website as a last resort (default true) |
| proxyConfiguration | Proxy used for page fetching (default RESIDENTIAL) |

### Output Format 📤

📝 Example Output (JSON) — real rows from a live run

```json
[
  {
    "network": "facebook.com",
    "keyword": "photographer",
    "title": "Kate Milford Photographer",
    "description": "Kate Milford Photographer. 192 likes. Photographs: of people, buildings, places and parties; musicians, artists, people with disabilities, family; journeys; cities.",
    "url": "https://www.facebook.com/katemilfordphotographer",
    "email": "jkatemilford@gmail.com",
    "entityType": "page",
    "emailSource": "page_about_contact"
  },
  {
    "network": "facebook.com",
    "keyword": "photographer",
    "title": "Brian Coffman, Photographer",
    "description": "Brian Coffman, Photographer, Sacramento. 108 likes. Show and event photography. Specialises in no flash and low light conditions to capture your events without drawing your audiences eye from the action.",
    "url": "https://www.facebook.com/briancoffmanphotography",
    "email": "bgcoffman@gmail.com",
    "entityType": "page",
    "emailSource": "page_about_contact"
  }
]
```

### Output Table

| Field | Description |
| --- | --- |
| network | The Facebook domain the result came from |
| keyword | Keyword that surfaced the lead |
| title | Page/profile name or post headline |
| description | Public bio, About text or post body |
| url | Canonical Facebook URL of the entity |
| email | The extracted public email address |
| entityType | `page`, `profile`, `page_or_profile`, `post`, `group`, `group_post`, `video`, `photo`, `event` |
| emailSource | `page_about_contact`, `facebook_page`, `page_snippet`, `linked_website` or `post_body_author` |

### FAQ ❓

#### Where exactly does the email come from?

From one of five places, and the `emailSource` column always tells you which — and whose address it is. Best case (`page_about_contact`) it is read straight out of the Contact info block on the account's own logged-out `/about` tab, i.e. the address the owner entered themselves. Worst case (`post_body_author`) it was written in the body of a post, and belongs to that post's author rather than to the owner of the URL.

#### Does it log in to Facebook?

**No.** No credentials, no cookies, no session tokens. Only anonymous, logged-out requests to public URLs.

#### Can it read private profiles or group member lists?

**No.** Anything behind the login wall is out of reach and the actor does not pretend otherwise.

#### Why is `entityType` sometimes `page_or_profile`?

Because a bare Facebook vanity slug (`facebook.com/somename`) is used by both business Pages and personal profiles, and the URL alone cannot distinguish them. When the actor actually fetches the page it refines this to `page` or `profile`.

#### Why did a keyword return zero rows?

Because no public Facebook entity matching it published an email that passed your `emailDomains` filter. The log states this explicitly. The actor never pads the dataset with sample or placeholder rows.

#### Which proxy should I use?

The default (RESIDENTIAL) is correct. Facebook answers logged-out requests from residential exits; datacenter exits are far more likely to be shown the login wall. Search-engine traffic uses its own dedicated proxy groups regardless of this setting.

#### Can I speed up a run?

Lower `maxSearchPages`, or turn off `enrichFromLinkedWebsite` (and then `enrichFromFacebookPage`) in Advanced Settings. You will get fewer leads per keyword in exchange.

#### What formats can I export to?

CSV, Excel, JSON, XML, or straight from the Apify API.

# Actor input Schema

## `keywords` (type: `array`):

🎯 The words that power your lead search! Add one or more keywords (e.g. \['marketing', 'founder', 'coach', 'agency']) and the actor finds public Facebook profiles, Pages, posts, videos and group threads matching them, then 📧 extracts the email addresses they publish. 💡 Tip: more specific keywords = more targeted leads.

## `platform` (type: `string`):

📱 The social network to mine for leads. Currently locked to Facebook — more platforms coming soon! 🚀

## `location` (type: `string`):

🗺️ Optional geo-targeting! Add a city or region (e.g. 'London' 🇬🇧, 'New York' 🗽, 'Dubai' 🏙️) to narrow leads down to entities mentioning that place. ✨ Leave empty to search the whole globe 🌍.

## `emailDomains` (type: `array`):

🎛️ Keep only leads whose email is on the domains you care about — type as many as you like! ✍️ Popular picks: 📮 @gmail.com · 📨 @outlook.com · 💌 @hotmail.com · 💜 @yahoo.com · ☁️ @icloud.com · 📬 @aol.com · 🔒 @protonmail.com · 📥 @live.com · 📧 @gmx.com · 🟡 @yandex.com · 📗 @zoho.com. 🏢 Custom/business domains work too — e.g. @yourcompany.com. 🕳️ Leave empty to collect leads from ALL domains. 💡 Adding a domain also sharpens the search 🎯.

## `maxEmails` (type: `integer`):

🎚️ How many leads (emails) to collect per keyword before moving on. ⚡ Higher = more leads but longer runs. Default is 20 ✅ (range 1 – 5,000).

## `engine` (type: `string`):

🔧 The processing engine. Currently 'legacy' 🛡️ — a reliable, battle-tested extraction pipeline. More engines on the roadmap! 🚧

## `maxSearchPages` (type: `integer`):

🔎 How many search-result pages to walk per keyword while discovering Facebook URLs. Higher = a wider net (and a longer run). Default 4 ✅ — that is usually 40+ distinct Facebook entities per keyword.

## `enrichFromFacebookPage` (type: `boolean`):

📖 When a search snippet does not already show an email, open the logged-out Facebook page itself and read the public contact block embedded in it. ✅ Recommended — this is where most Page and profile emails come from. Turn off for a faster, snippet-only run.

## `enrichFromLinkedWebsite` (type: `boolean`):

🌍 Last resort: if the Facebook entity links out to its own website, fetch that site's home / contact / about pages and read the email published there. ✅ Recommended — it rescues leads whose Facebook page keeps contact details hidden.

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

🌐 Controls the proxy used to fetch Facebook pages and external websites. 🆓 Leave it as-is and the actor uses the RESIDENTIAL group, which is what Facebook actually answers logged-out requests from. 🔄 A fresh proxy session is drawn per request and any failure is retried. ⚠️ Search-engine traffic always uses its own dedicated proxy groups and is not affected by this setting.

## Actor input object example

```json
{
  "keywords": [
    "photographer"
  ],
  "platform": "Facebook",
  "location": "",
  "emailDomains": [
    "@gmail.com"
  ],
  "maxEmails": 20,
  "engine": "legacy",
  "maxSearchPages": 4,
  "enrichFromFacebookPage": true,
  "enrichFromLinkedWebsite": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# 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 = {
    "keywords": [
        "photographer"
    ],
    "emailDomains": [
        "@gmail.com"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraper-engine/facebook-lead-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 = {
    "keywords": ["photographer"],
    "emailDomains": ["@gmail.com"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("scraper-engine/facebook-lead-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 '{
  "keywords": [
    "photographer"
  ],
  "emailDomains": [
    "@gmail.com"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call scraper-engine/facebook-lead-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scraper-engine/facebook-lead-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/4aaPndSsa6qkaTLEg/builds/MbDv7y8XNhF04L96T/openapi.json
