# Facebook Posts Search Scraper (`simpleapi/facebook-posts-search-scraper`) Actor

🔎 Facebook Posts Search Scraper extracts public posts by keyword or URL—text, author/page, date, link, reactions, comments, shares & media. 📊 Ideal for social listening, brand monitoring, competitor research & trend analysis. ⚡ Fast, scalable, CSV/JSON export.

- **URL**: https://apify.com/simpleapi/facebook-posts-search-scraper.md
- **Developed by:** [SimpleAPI](https://apify.com/simpleapi) (community)
- **Categories:** Social media, Developer tools, Automation
- **Stats:** 179 total users, 45 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.99 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

### Facebook Scraper — Posts by Keyword, Engagement and Page Contact Data

Search Facebook for any keyword and collect every public post that matches it — no login, no Graph API access, and no Facebook Developer account. The Facebook Posts Search Scraper returns `pageName`, `text`, `likes`, `comments`, `shares`, `time`, `url`, and `thumb` for each post it finds, plus optional page `contact` details (email, phone, website, address) for outreach. Social listening teams, market researchers, and lead-generation agencies use it to turn a topic into a structured dataset of public conversation. Results are written to the dataset the moment each post is scraped.

### What is Facebook Posts Search Scraper?

Facebook Posts Search Scraper is an Apify Actor that takes one or more search keywords, discovers matching public Facebook posts, and extracts each post's text, engagement counts, media, and page identity into a structured dataset. It does not require a Facebook account, session cookie, or Graph API token — it reads only what is publicly visible on facebook.com.

- Discovers posts by free-text keyword (`searchQueries`) rather than requiring a list of Page URLs
- Extracts core identity fields (`pageName`, `pageId`, `facebookUrl`) and engagement counts (`likes`, `comments`, `shares`) for every post
- Filters results to a recency window (`postTimeRange`) enforced before a row is ever saved or charged
- Optionally fetches each post's top-level comments into a separate, uncharged child dataset
- Optionally enriches each post's Page with public email/phone/website/address for lead generation
- Exports to JSON, CSV, Excel, or via the Apify API

### What data can I extract with Facebook Posts Search Scraper?

Every scraped post is pushed as one row with the fields below (the default dataset view surfaces a 13-field subset; every field listed here is present on the underlying row).

| Field | Example Value | Use Case |
| --- | --- | --- |
| `facebookUrl` | `https://www.facebook.com/examplepage` | Link back to the Page that authored the post |
| `pageId` | `"123456789"` | Stable numeric ID for de-duplicating a Page across posts |
| `pageName` | `"Example Page"` | Display name of the author for reporting |
| `facebookId` | `"123456789"` | Alias of `pageId` used in some post-page variants |
| `postId` | `"987654321"` | Unique ID of the post |
| `postFacebookId` | `"987654321"` | Alias of `postId` |
| `url` | `https://www.facebook.com/examplepage/posts/987654321` | Direct link to the post |
| `topLevelUrl` | `https://www.facebook.com/examplepage/posts/987654321` | Canonical URL used to reach the post |
| `time` | `"2026-06-01 14:32:10"` | Human-readable publish date (UTC) |
| `timestamp` | `1748788330` | Unix epoch seconds for sorting/filtering |
| `likes` | `542` | Reaction count for engagement ranking |
| `comments` | `38` | Comment count as reported by Facebook |
| `shares` | `12` | Share count as a virality signal |
| `text` | `"Excited to announce our new store opening..."` | Post body for keyword/sentiment analysis |
| `link` | `null` | External link attached to the post, if any |
| `thumb` | `https://scontent.xx.fbcdn.net/...` | Thumbnail/media URL |
| `searchQuery` | `"coffee shop"` | The keyword that surfaced this row |
| `contact` | `{ "email": "hello@example.com", "phone": null, "website": "https://example.com/", "address": null }` | Page contact details when `enableContactLookup` is on |
| `commentsFetched` | `10` | Count of comments written to the child dataset for this post |
| `scrapedAt` | `"2026-07-25T10:12:03+00:00"` | Timestamp of extraction for freshness checks |

#### Engagement and contact data

Each row carries the three core engagement counters — `likes`, `comments`, and `shares` — read directly from the post's own page at scrape time, giving a snapshot of how a post is performing right now. When `enableContactLookup` is turned on, the Actor additionally fetches the post's own Facebook Page and extracts a `contact` object (`email`, `phone`, `website`, `address`) from that Page's public intro/contact card. Fields that genuinely aren't published on the Page are returned as `null` — never guessed or filled in. A common workflow: sort scraped rows by `likes` descending to find the highest-performing posts on a topic, then cross-reference `contact` on the top results to build an outreach list without a second tool.

#### Keyword and time-range targeting

`searchQueries` and `postTimeRange` are the two fields that decide which posts end up in the dataset. `searchQueries` accepts a list of free-text topics — each one runs as its own search, so broader coverage means adding more entries rather than tuning one query. `postTimeRange` narrows every query to a recency window (`24h`, `7d`, `30d`, `90d`, or all time) that is enforced against each post's actual publish `timestamp` before the row is written, so an out-of-window post never reaches the dataset or gets charged.

### Why not build this yourself?

Meta's Graph API does not expose arbitrary keyword-based search over public posts — the Graph API's search endpoints were locked down years ago and today mostly serve apps already connected to a Page or user token, not open topic discovery across all of Facebook. Building keyword-based post discovery yourself means reverse-engineering Facebook's own search surface, handling its anti-bot defenses, rotating proxies for both the discovery step and the per-post fetch, and re-deriving fields like publish time and reaction counts from embedded page JSON that changes shape without notice. Facebook Posts Search Scraper already does this: it runs the discovery step through Apify's `GOOGLE_SERP` proxy group (required for that method to return rendered results), fetches each discovered post through your own `proxyConfiguration`, and normalizes the output into a consistent schema regardless of which page layout Facebook served that day.

### How to use data extracted from Facebook?

#### Trend and topic researchers

Enter a topic as one or more `searchQueries` entries, set `postTimeRange` to `7d` or `30d`, and let the run populate the dataset live as each post is found. The output's `text`, `likes`, `comments`, and `shares` fields are enough to see which angles on a topic are getting traction and which pages are driving the conversation, without opening Facebook manually post by post.

#### Social listening agencies

Agencies running the same set of brand or client keywords on a recurring basis can schedule this Actor per client, with `searchQueries` set to the client's brand terms and competitor names. Each run's `pageName` and `text` fields show who is talking about the brand and what they're saying, while `time`/`timestamp` let the agency compare volume week over week without re-building a search each time.

#### Market intelligence teams

Running a category keyword (e.g. an industry term or product category) across `targetCountry` values surfaces how a topic is discussed differently across public Facebook activity in different markets. Aggregating `likes` and `comments` by `searchQuery` and country gives a rough engagement distribution that's useful for spotting where a topic or category is gaining traction before committing budget to it.

#### AI agents and automated pipelines

Because the Actor is a standard Apify Actor with a JSON input and output, it can be wired into an agent as a tool: an agent passes a topic as `searchQueries`, gets back structured posts with `text` and engagement counts, and uses that as grounding context for summarization, trend-alerting, or RAG pipelines — without the agent needing its own scraping or proxy logic.

### 🔼 Input sample

All 8 parameters are optional — the Actor runs with defaults if you provide nothing.

| Parameter | Required | Type | Description | Example Value |
| --- | --- | --- | --- | --- |
| `searchQueries` | No | array | Keywords/topics to search for on Facebook. Each keyword runs its own search. Default `[""]` (falls back to `"football"` if left empty at run time). | `["digital marketing", "coffee shop"]` |
| `maxPosts` | No | integer | Max posts to collect per keyword, 1–5,000. Default `10`. | `25` |
| `postTimeRange` | No | string (enum) | Recency window enforced before rows are saved: `""` (all time), `24h`, `7d`, `30d`, `90d`. Default `""`. | `"30d"` |
| `targetCountry` | No | string (enum) | Biases the underlying Google search toward a country's results (`gl` param). Default `""` (no bias). | `"us"` |
| `targetLanguage` | No | string (enum) | Language for the search UI/results (`hl` param). Default `""` (English). | `"es"` |
| `enableContactLookup` | No | boolean | When `true`, fetches each post's Page and attaches a `contact` object (email/phone/website/address). Default `false`. | `true` |
| `maxCommentsPerPost` | No | integer | Max top-level comments to fetch per post, 0–500, saved to an uncharged `comments-<runId>` dataset. Default `0` (disabled). | `10` |
| `proxyConfiguration` | No | object | Proxy used to fetch each discovered post/Page's HTML. The keyword-discovery step always uses Apify's `GOOGLE_SERP` proxy group regardless of this setting. Default `{ "useApifyProxy": true }`. | `{ "useApifyProxy": true }` |

```json
{
  "searchQueries": ["digital marketing", "coffee shop"],
  "maxPosts": 25,
  "postTimeRange": "30d",
  "targetCountry": "us",
  "enableContactLookup": true,
  "maxCommentsPerPost": 10,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

**Common pitfall:** `maxPosts` is a per-keyword limit, not a total — if `searchQueries` has 5 entries and `maxPosts` is 25, the run can produce up to 125 charged rows, not 25. Size `maxPosts` and the length of `searchQueries` together before starting a large run.

### 🔽 Output sample

Output is typed, normalized JSON with a consistent schema across every row, exportable as JSON, CSV, or Excel from the dataset, or fetched via the Apify API.

```json
{
  "facebookUrl": "https://www.facebook.com/examplepage",
  "pageId": "123456789",
  "pageName": "Example Page",
  "facebookId": "123456789",
  "postId": "987654321",
  "postFacebookId": "987654321",
  "url": "https://www.facebook.com/examplepage/posts/987654321",
  "topLevelUrl": "https://www.facebook.com/examplepage/posts/987654321",
  "time": "2026-06-01 14:32:10",
  "timestamp": 1748788330,
  "likes": 542,
  "comments": 38,
  "shares": 12,
  "text": "Excited to announce our new store opening...",
  "link": null,
  "thumb": "https://scontent.xx.fbcdn.net/v/example_thumb.jpg",
  "searchQuery": "coffee shop",
  "contact": {
    "email": "hello@example.com",
    "phone": null,
    "website": "https://example.com/",
    "address": null
  },
  "commentsFetched": 10,
  "scrapedAt": "2026-07-25T10:12:03+00:00"
}
```

When `maxCommentsPerPost` is above `0`, a separate uncharged `comments-<runId>` dataset is created with one row per top-level comment (`parentPostId`, `commentText`, `authorName`, `likesCount`, `commentDate`, and related fields).

### How do you filter and target specific posts?

Two inputs decide result precision here: `searchQueries` and `postTimeRange`. `searchQueries` is free-text, not a category picker — because there's no fixed taxonomy of Facebook posts, precision comes from how specific each keyword is. `"coffee shop"` returns broad brand chatter; `"artisan coffee roastery"` returns a narrower, more qualified set of posts. Running multiple related keywords in one array widens coverage without diluting any single query's relevance, since each is searched and scored independently.

Scope is narrowed on two axes: recency and market. `postTimeRange` is enforced against each post's real publish `timestamp` before the row is ever written, so a `7d` run genuinely excludes older posts rather than hiding them in a post-hoc summary. `targetCountry` and `targetLanguage` bias the underlying discovery search toward a specific market, which matters when a keyword is common in more than one country and the default (unbiased, English) results skew toward the wrong audience.

There's no minimum-rating or verification-flag filter here — Facebook posts don't carry a public quality score the way reviews or listings do — so quality control means pairing a specific keyword with a tight `postTimeRange` rather than filtering on a threshold field afterward.

Volume is controlled with `maxPosts`, applied per keyword rather than per run — raise it toward the 5,000 ceiling for a deep sweep on one keyword, or keep it low while spreading a wider net across many `searchQueries` entries for a fast pulse-check.

```json
{ "searchQueries": ["artisan coffee roastery"], "maxPosts": 500 }
```

```json
{ "searchQueries": ["product recall"], "postTimeRange": "24h", "maxPosts": 50 }
```

```json
{ "searchQueries": ["real estate agency"], "targetCountry": "gb", "targetLanguage": "en", "maxPosts": 100 }
```

### ▶️ Want to try other Facebook scrapers?

| Scraper Name | What it extracts |
| --- | --- |
| Facebook Page Posts & Comments Scraper | Posts and their full comment threads from specific Facebook Pages |
| Facebook Comments Scraper: Sentiment & Toxicity Analysis | Comment-level sentiment and toxicity scoring for given post/video links |
| Facebook Groups Search Scraper With Group Profile Details | Discovers Facebook Groups by keyword or URL with group profile details |
| Facebook Group Profile Scraper | Full profile details for a given Facebook Group by URL/username/ID |
| Facebook Ads Library Scraper: Creative & Ad Text Details | Competitor ad creatives and ad text from the Facebook Ads Library |
| Facebook User Search Scraper: Related Profiles & Pages | Discovers related public profiles, Pages, and Groups by keyword |

### How to extract Facebook data programmatically

This Actor runs as a standard Apify Actor: one API call starts the run, structured JSON comes back from the dataset — no separate scraping infrastructure to maintain.

#### Python example

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("SimpleAPI/facebook-posts-search-scraper").call(run_input={
    "searchQueries": ["digital marketing"],
    "maxPosts": 50,
    "postTimeRange": "30d",
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["pageName"], item["likes"], item["text"][:80])
```

#### Export to spreadsheets or CRM

Export the dataset directly as CSV from the Apify Console or via the API, then import into a spreadsheet or CRM. Map `pageName` to a Company/Contact Name column, `contact.email` and `contact.phone` to your CRM's email/phone fields, and `url` to a source-link column so every imported row traces back to the original post.

### Is it legal to scrape Facebook posts?

Yes — this Actor collects only what is publicly visible on facebook.com, without logging in or accessing content behind an authentication wall, and public posts exist to be seen and shared. That said, scraped rows can contain personal data — a page owner's or individual's name, and when `enableContactLookup` is on, an email, phone number, or address. Where that applies, GDPR and CCPA govern how you may store and use it: you need a lawful basis for holding personal data and must honor deletion/access requests from the people it describes. Consult legal counsel for commercial applications involving bulk storage of personal data.

### ❓ FAQ

**What happens to posts that get deleted after being scraped?** The dataset holds a snapshot from scrape time — `scrapedAt` records when that was. Re-run the same `searchQueries` periodically if you need to detect posts that have since been removed or edited.

**Can I get comments along with the main posts?** Yes — set `maxCommentsPerPost` above `0` (up to 500) and each post's top-level comments are written to a separate, uncharged `comments-<runId>` dataset alongside the main run.

**How accurate is the engagement data?** The Actor returns `likes`, `comments`, and `shares` exactly as they appear on the post at request time. Counts change continuously on Facebook itself, so treat each row as a snapshot and re-scrape if you need current numbers.

**How many posts can I get per run?** `maxPosts` caps collection at up to 5,000 posts per keyword; with multiple `searchQueries` entries, the total across the run is `maxPosts` × number of keywords.

**How does the country/language targeting work?** `targetCountry` and `targetLanguage` bias the underlying Google-based discovery step toward a specific market's results (`gl`/`hl` search parameters) — useful when a keyword returns mostly one country's posts by default and you need coverage from another.

**Does this work with Claude, ChatGPT, and AI agent frameworks?** It's callable as a standard HTTP endpoint via the Apify API by any agent framework that can make an authenticated REST call — pass `searchQueries` as input and read structured posts back from the dataset.

**Can I use this without a Facebook API key or developer account?** Yes — no Facebook login, session cookie, API key, or Developer account is required. The only credential needed is your own Apify account to run the Actor.

**Why does discovery always use one specific proxy group?** The keyword-search step runs over Apify's `GOOGLE_SERP` proxy group by technical necessity — that proxy group is what returns rendered search-results HTML for this discovery method. Your own `proxyConfiguration` still controls the proxy used to fetch each discovered post and Page.

**Can I use this for lead generation?** Yes — turn on `enableContactLookup` and each row gets a `contact` object with any public email, phone, website, and address found on the post's own Page, with `null` for fields that genuinely aren't published.

**What's the one real limitation to know before running this at scale?** Discovery depends on a Google-based search of `site:facebook.com`, so keyword coverage is bounded by what that search surfaces rather than by Facebook's own internal search index — very niche or very new topics may return fewer posts than a broader keyword would.

### Conclusion

Facebook Posts Search Scraper turns a keyword into a structured dataset of public Facebook posts — engagement counts, post text, media, and page identity, with optional comment and contact enrichment layered on top. It's built for researchers, social listening agencies, and market intelligence teams who need repeatable, filterable post data without a Graph API token or manual browsing. Start a run in Apify Console with your `searchQueries` to see results populate the dataset live, post by post.

# Actor input Schema

## `searchQueries` (type: `array`):

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 Enter topics you want to explore on Facebook!

Examples: football • cricket • tech news • recipes • travel

🎯 Each keyword runs its own search → more posts discovered.

## `maxPosts` (type: `integer`):

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📈 How many posts to collect per keyword?

🎚️ Range: 1 → 5,000 posts
💯 Default: 10 (quick runs)
🚀 Need more? Crank it up to 5000!

## `postTimeRange` (type: `string`):

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📅 Filter by recency—only posts published within this window are saved to the dataset (genuinely enforced before saving, not just logged).

⏳ All time (default) • 24h • 7d • 30d • 90d

## `targetCountry` (type: `string`):

Bias the search toward a country's results (SERP `gl` param). Leave blank for no country bias (default).

## `targetLanguage` (type: `string`):

Language for the search UI/results (SERP `hl` param). Leave blank to use the default (English).

## `enableContactLookup` (type: `boolean`):

When ON, fetches each post's Facebook Page and extracts public email / phone / website / address from the page's own intro/contact card, attached as `contact` on the row. Off by default (no extra page fetches). Missing fields are `null`, never fabricated.

## `maxCommentsPerPost` (type: `integer`):

Maximum number of top-level comments to fetch per post, saved to a separate `comments-<runId>` dataset (not charged). Set to 0 (default) to disable comment fetching entirely.

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

Proxy used to fetch each discovered post/Page's HTML. Optional — Apify Proxy is used by default.

## Actor input object example

```json
{
  "searchQueries": [
    "football"
  ],
  "maxPosts": 10,
  "postTimeRange": "",
  "targetCountry": "",
  "targetLanguage": "",
  "enableContactLookup": false,
  "maxCommentsPerPost": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped items in the Actor's default dataset.

# 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 = {
    "searchQueries": [
        "football"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("simpleapi/facebook-posts-search-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 = {
    "searchQueries": ["football"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("simpleapi/facebook-posts-search-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 '{
  "searchQueries": [
    "football"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call simpleapi/facebook-posts-search-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,simpleapi/facebook-posts-search-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/IKvFqAEWd6Ms91VsH/builds/k9KXFQyonvlGmV1xD/openapi.json
