# Facebook Posts Scraper - Pages & Groups, No Login (`a.actors/facebook-posts-scraper`) Actor

Scrape posts from Facebook pages, profiles AND groups without cookies or a Facebook account. Text, timestamps, reactions, comment and share counts, media. Filter by date range at no extra cost, and get a per-target report of whether the range was actually covered.

- **URL**: https://apify.com/a.actors/facebook-posts-scraper.md
- **Developed by:** [ApifyActors](https://apify.com/a.actors) (community)
- **Categories:** Social media, News
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 posts

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

## Facebook Posts Scraper — pages, profiles and groups, without a login

Scrape public Facebook posts without an account, without cookies, and without asking your
users to hand over a session. Point it at a page, a profile or a **group**, set a date range
if you want one, and get clean rows out.

Most Facebook scrapers do one of two things: they ask you to paste your own `c_user`/`xs`
session cookies — which gets accounts checkpointed — or they quietly return whatever the
logged-out page happens to show, which is close to nothing. This one does neither.

### What it does that the others don't

- **Groups, not just pages.** Public group feeds are read with the same engine as pages. Most
  scrapers at this price do pages only, or sell groups as a separate product.
- **Date filtering at no extra charge.** Give it `onlyPostsNewerThan` / `onlyPostsOlderThan`
  and pagination stops as soon as the feed passes your window. Other actors bill a surcharge
  per post for the same filter.
- **It tells you whether it actually covered your range.** Every run writes a `COVERAGE`
  record to the key-value store, with one entry per target saying whether the date range was
  proven exhausted or the run merely stopped. A post count never proves coverage, and an actor
  that returns 40 posts when 300 existed, without saying so, has given you a silently wrong
  dataset. This one says so.
- **Failed targets cost nothing.** A private group, a dead URL or a rate-limited target comes
  back as an `{"type": "error"}` row and is not billed.

### Input

| Field | What it does |
|---|---|
| `startUrls` | Page, profile or group URLs. `profile.php?id=…`, `/groups/<id>` and `/groups/<name>` all work |
| `resultsLimit` | Max posts per target (default 30) |
| `onlyPostsNewerThan` | ISO date, e.g. `2026-01-01`. Drops older posts and stops paging early |
| `onlyPostsOlderThan` | ISO date upper bound, for a closed range |
| `windowedFeed` | Deep historical pull — ask for the date range directly instead of paging back from the newest post. Use it when you want an old window rather than recent posts |
| `countryCode` | Country to browse from (default `US`) |

Minimal run:

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nasa" }],
  "resultsLimit": 100,
  "onlyPostsNewerThan": "2026-09-01"
}
```

### Output

One row per post:

```json
{
  "post_url": "https://www.facebook.com/nasa/posts/pfbid02…",
  "post_id": "284219939974539",
  "page_name": "NASA",
  "author_name": "NASA",
  "page_url": "https://www.facebook.com/nasa",
  "publish_date": "2026-09-22T08:46:51Z",
  "post_text": "Our Webb telescope just returned its deepest look yet at…",
  "likes": 16482,
  "comments": 431,
  "shares": 207,
  "views": "",
  "is_video": "no",
  "media_thumbnail": "https://scontent.xx.fbcdn.net/…",
  "audio_url": "",
  "scraped_date": "2026-09-23T00:28:13Z"
}
```

`audio_url` carries the direct media URL on video posts, which is what you want if you are
feeding them to a transcription model. `views` is populated where Facebook exposes it, which
is mainly video.

### Keyword search

Give it `keywords` and only matching posts come back. Two things worth knowing, because they
decide what it costs:

Facebook's page timeline takes no search term. There is no server-side search on a page feed
for anyone — so matching keywords means reading the feed and testing each post. `scanLimit`
sets how deep to read; `resultsLimit` then caps how many *matches* you get back.

Matching is whole-word by default, and deliberately asymmetric: a term still matches when a
language glues a prefix onto it, but not when it merely sits inside a longer, unrelated word.
That matters in Arabic and Hebrew, where prefix particles are written joined to the stem and a
naive substring match silently over-collects.

### Pricing

**$2.00 per 1,000 posts**, plus Apify's standard $0.00005 actor start. No surcharge for date
filtering, no surcharge for groups, and no charge for targets that fail.

**Keyword runs also cost $0.50 per 1,000 posts scanned.** If you scan 1,000 posts and 20 match,
you pay $0.50 for the scan and $0.04 for the matches. This is billed separately because the
scan is the actual work — the reading happens whether or not a post matches — and pricing it
into the match rate would make a narrow keyword far more expensive than a broad one for no
reason. Runs without keywords never touch this charge.

For comparison, at the time of writing the official Apify Facebook posts scraper charges $4.00
per 1,000 posts plus $1.00 per 1,000 for the start and another $1.00 per 1,000 to use the date
filter — and does not read groups at all.

### Notes and limits

- **Public content only.** A private group needs an account; the actor reports that clearly
  instead of returning an empty dataset.
- **Timestamps and date bounds.** A handful of posts come through with no timestamp. When you
  set any date bound they are excluded, because an undated post cannot be shown to fall inside
  your window. Without a date bound they are returned.
- **Depth.** Recent posts page back freely. For windows deep in a page's history, turn on
  `windowedFeed`.
- **Pacing.** Facebook rate-limits by IP and by target. The actor paces itself and retries
  from a fresh address; very large jobs are better split across several runs than forced into
  one.

### FAQ

**Do I need a Facebook account or cookies?** No. That is the entire point of this actor.

**Will this get my Facebook account banned?** It never touches your account, because it never
logs in.

**Can it scrape comments?** Use the companion Facebook Comments Scraper, which pulls full
threads including replies.

**Can it scrape a private group I'm a member of?** No. Membership lives in your session, and
this actor deliberately has no session.

**Is scraping public Facebook data legal?** Scraping publicly available data is generally
lawful in most jurisdictions, but you are responsible for your own use — particularly for
personal data under the GDPR and for Facebook's own terms. Do not use this to build profiles
of private individuals.

# Actor input Schema

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

Public page, profile or group URLs. profile.php?id=... links work, and so do /groups/<id> and /groups/<vanity-name>. A private group cannot be read without an account and is reported as an error row rather than silently skipped - you are not charged for it. ⚠️ This property deliberately has a prefill (the example shown in the console form) but NO default: Apify injects schema defaults into API runs, and because startUrls is also `required`, a default SATISFIES the requirement instead of failing the run - so an API call that forgot its targets silently scraped the example page and billed for it. Without the default the same call fails loudly. Found 2026-09-29 on the TikTok actor, where it silently added a second account to every run.

## `resultsLimit` (type: `integer`):

How many posts to collect per page or group before stopping. Groups return roughly 29 posts in the first request and page back more slowly after that, so deep group pulls take longer than page pulls of the same size.

## `onlyPostsNewerThan` (type: `string`):

ISO date, YYYY-MM-DD. Posts older than this are dropped and pagination stops early once the feed passes the date. No extra charge for filtering. Posts that carry no timestamp are excluded whenever a date bound is set, because they cannot be shown to be in range.

## `onlyPostsOlderThan` (type: `string`):

ISO date, YYYY-MM-DD. Upper bound, for pulling a closed date range.

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

Only return posts whose text matches one of these terms. Facebook's page-timeline accepts no search term, so this is a filter over the feed rather than a server-side search: the feed is still paged to the scan depth below, and only the output shrinks. Because of that, keyword runs are billed per post SCANNED as well as per post returned - see Pricing in the README.

## `keywordMode` (type: `string`):

'any' keeps a post matching at least one term; 'all' requires every term.

## `keywordWholeWord` (type: `boolean`):

On by default: a term will not match inside a longer word. Written for languages that glue prefixes onto the stem, so an Arabic term still matches when prefixed with ال/و/ب/ل but not when it is merely a substring of a different word. Turn off for plain substring matching.

## `scanLimit` (type: `integer`):

How many posts per target to read before stopping when keywords are set. 'Max posts per target' then caps how many MATCHES come back. This is the number that drives the cost of a keyword run. Ignored when no keywords are given.

## `windowedFeed` (type: `boolean`):

Ask Facebook for the date range directly instead of paging back from the newest post. Reaches much further into the past on pages with a long history. Falls back to normal paging automatically if the page does not support it. Turn this on when you want an old date range rather than recent posts.

## `countryCode` (type: `string`):

ISO country code for the exit location, e.g. US, GB, DE. Worth matching to the page's own audience for geo-restricted content.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/nasa"
    }
  ],
  "resultsLimit": 30,
  "onlyPostsNewerThan": "",
  "onlyPostsOlderThan": "",
  "keywords": [],
  "keywordMode": "any",
  "keywordWholeWord": true,
  "scanLimit": 300,
  "windowedFeed": false,
  "countryCode": "US"
}
```

# Actor output Schema

## `posts` (type: `string`):

One item per post: text, timestamp, author, reactions, comment and share counts, media. Targets that failed appear as error rows and are not charged.

## `coverage` (type: `string`):

One entry per target saying whether the requested date range was proven exhausted or the run merely stopped. A post count never proves coverage, so this is what tells you the dataset is complete.

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

// Run the Actor and wait for it to finish
const run = await client.actor("a.actors/facebook-posts-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 = { "startUrls": [{ "url": "https://www.facebook.com/nasa" }] }

# Run the Actor and wait for it to finish
run = client.actor("a.actors/facebook-posts-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 '{
  "startUrls": [
    {
      "url": "https://www.facebook.com/nasa"
    }
  ]
}' |
apify call a.actors/facebook-posts-scraper --silent --output-dataset

```

## MCP server setup

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