# Facebook Posts Scraper — Pages & Post URLs, No Login (`memo23/facebook-page-posts-scraper`) Actor

Scrape Facebook posts without logging in: walk any public Page's feed, or paste individual post, reel or video URLs. Get text, date, permalink, photos, per-emoji reactions, comment and share counts. Your post limit is enforced before each request, so you are never charged past it.

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

## Pricing

from $0.99 / 1,000 post scrapeds

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?

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 Posts Scraper — Pages & Post URLs, No Login

Scrape Facebook posts two ways: walk any public Page's feed, or paste the URLs of individual posts, reels and videos and get exactly those. Each row carries the post text, publish date, permalink, photos, reaction breakdown, comment and share counts, hashtags/links, collaborators, reel view counts, and the Page's Ads Library stub. Turn on Include video transcript to pull Facebook's own captions when a video has them. No login, no cookies, no browser.

**Ask for 23 posts and you get 23 posts, charged for 23 posts.** Facebook serves its feed ten at a time; the extras in the last batch are discarded rather than delivered and billed.

### Why Use This Scraper?

- **Your limit is a limit** — `maxPosts` and `maxItems` are checked *before* each request, so the run never fetches, delivers, or charges for a post beyond what you asked for
- **Residential proxy included, not billed to you** — Facebook rate-limits and blocks datacenter IPs; the proxy that gets past that runs on our own pool, so your invoice carries no proxy line
- **No login, no cookies** — nothing to supply, nothing to get banned. Public Pages only, by design
- **Reads Facebook's own query id per run** — the id for the first page of a feed comes out of the Page's own HTML every run rather than being pinned in source, so the most common cause of a sudden breakage does not apply to it. The pagination query's id is looked up too, with a known-good value as the fallback when that lookup times out
- **Date cutoff that saves money** — `onlyPostsNewerThan` stops the walk at the first older post instead of paginating through years you will throw away
- **Loud about empty runs** — a run that scrapes nothing is reported as FAILED, never a green run wrapping an empty dataset
- **Rate limits handled honestly** — Facebook's "Rate limit exceeded" answer is an HTTP 200 with no posts in it. This actor detects it, rotates its exit IP, re-issues its tokens and retries; it never files a rate-limited response as "this page has no more posts"
- **Flexible input** — full URLs, bare handles (`nasa`), or `profile.php?id=…`; sub-paths like `/photos` are trimmed for you
- **Parity fields** — `ocrText` on photos, `textReferences` / `link`, `collaborators`, `viewsCount` on reels, `pageAdLibrary`, and an optional video-transcript toggle

### Coming from another Facebook posts scraper?

These are the complaints on competing Page-post scrapers' public issue boards, and what this actor does about each.

| Their issue board says | Here |
|---|---|
| "max\_posts limit being ignored right now" | Caps are enforced before the fetch, not after |
| "charging for more results than specified" | Only the posts inside your cap are pushed and charged |
| "Scrapes 3 posts per query, when max parameter is set to 1" | Exact count, verified live: `maxPosts: 23` returns exactly 23, charged 23 |
| "Actor quits after 3-6 posts scraped" | Rate limits are detected and retried on a fresh IP with fresh tokens, instead of ending the walk |
| "Images from Album does not come with the output" | Every post carries `attachments` with the full-size image URL, its thumbnail, and `photoCount` / `additionalPhotoCount` |
| "Did you remove the array support for pages?" | `startUrls` takes as many Pages as you want in one run, and always has |
| "Error fetching data" with no detail | Every failure names its cause, per page, in the log and the run summary |

### Two ways to use it

**Walk a Page** — hand it a Page and it pages the feed under your caps.

**Walk the photos tab** — set `scrapeMode` to `photos` (or `both`) and you get one row per
photo: id, full-size URL, thumbnail, Facebook permalink, and Facebook's accessibility caption
as `ocrText`. Date cutoffs go to Facebook as query bounds, so a windowed run does not walk
the whole tab. Photo permalinks (`/photo/?fbid=…`, `photo.php?fbid=…`) are fetched one request
each, same as post URLs.

**Fetch specific posts** — hand it post permalinks (`.../posts/<id>`, `permalink.php?story_fbid=…`,
`/reel/<id>`, `/videos/<id>`) and it fetches each one directly, one request per post, no feed walk.
You get the body text, publish time, reaction total *and* the per-emoji breakdown, comment count and
video views. Mix Pages and post URLs in the same run; posts are fetched first.

A permalink for a deleted or private post still answers HTTP 200 with an empty shell, so a missing
post is reported as missing and is **not charged**, rather than filed as a blank row.

`sharesCount` is usually present on post URLs as well. On some posts Facebook hides it from logged-out
viewers, and then it comes back `null` rather than `0`, so "not shown" stays distinct from a real zero.
Checked on 2026-09-11: 4 of 5 NASA post URLs returned a share count.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `startUrls` | array | — (required) | Page URLs, handles, `profile.php?id=…`, post permalinks, or photo permalinks |
| `scrapeMode` | string | `posts` | `posts` (feed), `photos` (photos tab), or `both` |
| `maxPosts` | integer | `100` | Hard cap **per Page** for the feed |
| `maxPhotos` | integer | `100` | Hard cap **per Page** for the photos tab |
| `maxItems` | integer | `1000` | Hard cap **for the whole run** (posts + photos) |
| `onlyPostsNewerThan` | string | — | e.g. `2026-01-01`; stops the walk at the first older post |
| `captionText` | boolean | `false` | Pull Facebook's own video/reel transcript when the post has one |
| `includeComments` | boolean | `true` | Attach comment bodies as `topComments`. Off keeps the count only |
| `proxy` | object | included | Leave empty — residential is on and not billed to you |

#### Example input

```json
{
  "startUrls": [
    { "url": "https://www.facebook.com/nasa" },
    { "url": "https://www.facebook.com/natgeo" }
  ],
  "maxPosts": 50,
  "maxItems": 100,
  "onlyPostsNewerThan": "2026-01-01"
}
```

Photos tab only:

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nasa" }],
  "scrapeMode": "photos",
  "maxPhotos": 24
}
```

### Output

One row per post.

```jsonc
{
  "pageUrl": "https://www.facebook.com/nasa",
  "pageName": "NASA - National Aeronautics and Space Administration",
  "pageId": "100044561550831",
  "facebookId": "100044561550831",
  "legacyId": "1620874212741282",
  "postId": "1620874212741282",
  "url": "https://www.facebook.com/nasa/posts/1620874212741282",
  "topLevelUrl": "https://www.facebook.com/100044561550831/posts/1620874212741282",
  "time": "2026-09-04T15:07:02.000Z",
  "timestamp": 1788534422,
  "text": "AvGeeks, assemble! MAX POWER, America's newest and most exciting aerospace technology expo…",
  "topReactionsCount": 4,
  "reactionLikeCount": 969,
  "reactionLoveCount": 124,
  "reactionWowCount": 15,
  "reactionCareCount": 11,
  "likesCount": 1119,
  "likes": 1119,
  "commentsCount": 94,
  "comments": 94,
  "sharesCount": 115,
  "shares": 115,
  "viewsCount": 27270,
  "isVideo": true,
  "isSharedFromAnotherPage": false,
  "photoCount": 1,
  "attachments": [{ "__typename": "Video", "thumbnail": "https://scontent…", "url": "https://www.facebook.com/reel/28631238109803230/", "playableUrl": "https://video…mp4" }],
  "media": [{ "__typename": "Video", "thumbnail": "https://scontent…", "url": "https://www.facebook.com/reel/28631238109803230/", "playableUrl": "https://video…mp4" }],
  "playableUrl": "https://video…mp4",
  "captionText": "MAX POWER NASA Kennedy Space Center nasa.gov/maxpower",
  "textReferences": [],
  "collaborators": [],
  "link": null,
  "pageAdLibrary": { "is_business_page_active": false, "id": "54971236771" },
  "user": { "id": "100044561550831", "name": "NASA - National Aeronautics and Space Administration", "profileUrl": "https://www.facebook.com/100044561550831", "profilePic": "https://scontent…" },
  "topComments": [
    { "commentId": "28187513214220429", "text": "Learn about NASA's ER-2 aircraft: https://www.nasa.gov/…", "threadingDepth": 0, "profileId": "100044561550831", "profileName": "NASA - National Aeronautics and Space Administration", "profilePicture": "https://scontent…", "commentUrl": "https://www.facebook.com/nasa/posts/1620874212741282?comment_id=28187513214220429" }
  ]
}
```

**Fields** — `pageUrl`, `pageName`, `pageId`, `facebookId`, `legacyId`, `postId`, `id`, `url`, `facebookUrl`, `topLevelUrl`, `time`, `timestamp`, `text`, `user` (`id`, `name`, `profileUrl`, `profilePic`), `attachments` / `media` (photos include `ocrText`, `photo_product_tags`, `feedback`; videos include `playableUrl` when Facebook exposes one), `photoCount`, `additionalPhotoCount`, `topReactionsCount`, `reactionLikeCount`, `reactionLoveCount`, `reactionWowCount`, `reactionCareCount`, `likesCount` / `likes`, `commentsCount` / `comments`, `sharesCount` / `shares`, `viewsCount`, `isVideo`, `isSharedFromAnotherPage`, `textReferences`, `link`, `collaborators`, `pageAdLibrary`, `captionText` (only when the transcript toggle is on), `topComments`, `feedbackId`, `isMarketplaceListing`.

A photos-tab row looks like this:

```jsonc
{
  "dataType": "photo",
  "id": "1629624498532920",
  "facebookId": "100044561550831",
  "facebookUrl": "https://www.facebook.com/photo.php?fbid=1629624498532920&set=pb.100044561550831.-2207520000&type=3",
  "url": "https://www.facebook.com/photo.php?fbid=1629624498532920&set=pb.100044561550831.-2207520000&type=3",
  "image": "https://scontent…/1629624501866253_n.jpg",
  "imageHeight": 1487,
  "imageWidth": 2047,
  "thumbnail": "https://scontent…/s206x206…",
  "ocrText": "NASA Deputy Administrator Matt Anderson…",
  "pageUrl": "https://www.facebook.com/nasa",
  "pageName": "NASA - National Aeronautics and Space Administration",
  "pageId": "100044561550831",
  "inputUrl": "https://www.facebook.com/nasa"
}
```

`ocrText` is Facebook's accessibility caption. When Facebook ships the placeholder "No photo description available.", the field is `null`.

### Pricing

Pay-per-event: one flat fee per post or photo delivered (`post`, $0.00099). Failed pages cost nothing, and items beyond your cap are never delivered, so they are never charged.

Platform usage is compute and data transfer only — the residential proxy is on our pool and never appears on your bill.

### Limits & honest caveats

- **Public Pages only.** A private profile, a group or an event URL has no public timeline feed; the run says so per URL rather than returning an empty dataset.
- **Facebook rate-limits the logged-out feed.** The actor paces its requests and retries on a fresh IP, which is why a large run takes minutes rather than seconds. Pushing harder would return fewer posts, not more.
- **Comment bodies are the public ones.** `topComments` is what a logged-out visitor sees on the post — not the full thread. Use the comments scraper when you need every reply.
- **Video transcripts are Facebook's own captions**, not a Whisper pass. Turn on `captionText` and you get the track when Facebook published one; otherwise `captionText` is null.
- **`pageAdLibrary` is a stub** — `{ is_business_page_active, id }`. It is not ad creatives or spend.
- **Reel and video URLs are read through their post.** A `/reel/<id>/` or `/videos/<id>/` link lands on a player shell with little data; the actor follows it to the owning post page (one extra request) so you get the same row a post URL would give.
- **`viewsCount` is Facebook's rendered view count** (the "27K views" number), not the looped-play total. Photo posts have no view count and stay `null`.
- **Reaction sub-counts are what Facebook exposes publicly**, which for some posts is a partial breakdown rather than the full total.

### FAQ

**Do I need a Facebook account or cookies?**
No. Everything comes from the public, logged-out view.

**Why is a big run slower than I expect?**
Facebook rate-limits the logged-out feed endpoint aggressively. The actor deliberately paces its cursor fetches and rotates exit IPs on a rate limit. That is the difference between a run that finishes and one that stops after six posts.

**Will this break when Facebook changes its frontend?**
Less often than most, but honestly: partly. The id for a feed's first page is read from the Page's own HTML on every run, so a rotation there costs nothing. The pagination query's id lives in Facebook's JS bundles; the actor tries to read it per run and falls back to a known-good value when that lookup runs out of its time budget, which on the platform it usually does. So pagination is currently as rotation-proof as a pinned id, and the first page is genuinely self-healing.

**Can I scrape a Facebook group with this?**
No — a group's feed is a different query. This actor tells you when a URL is not a Page instead of returning nothing.

**Does Include video transcript run speech-to-text?**
No. It fetches the VTT/SRT Facebook already attached to the video, when there is one.

### SEO Keywords

facebook posts scraper, facebook post scraper, facebook page posts scraper, facebook photos scraper, facebook photo scraper, scrape facebook posts without login, facebook post url scraper, facebook permalink scraper, facebook page feed scraper, facebook reel post data, facebook video post data, facebook post reactions, facebook comment count, facebook share count, public facebook page scraper, facebook scraper no cookies

# Actor input Schema

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

Facebook Pages to walk (https://www.facebook.com/<page>, a bare handle like `nasa`, or `profile.php?id=<id>`), individual post permalinks (`.../posts/<id>`, `permalink.php?story_fbid=...`, `/reel/<id>`, `/videos/<id>`), and/or photo permalinks (`/photo/?fbid=...`, `photo.php?fbid=...`). Mix them in one run — post and photo URLs are fetched first, one request each.

## `scrapeMode` (type: `string`):

Posts walks the Page feed (default, same as before). Photos walks the public photos tab and writes one row per photo (id, full-size URL, thumbnail, Facebook permalink, OCR/alt text). Both does the feed first, then the photos tab. Date cutoffs apply to photos as query bounds, not a client-side filter.

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

Hard cap per Page. The run stops at exactly this number — Facebook returns posts ten at a time, and any extras in the last batch are discarded rather than delivered and charged.

## `maxPhotos` (type: `integer`):

Hard cap per Page when scraping photos. Facebook returns eight photos at a time; extras in the last batch are discarded rather than delivered and charged. Falls back to Max posts per page if you leave this empty.

## `maxItems` (type: `integer`):

Hard cap across every Page in the run (posts + photos). Whichever limit is reached first ends the run.

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

Optional cutoff date, e.g. <code>2026-01-01</code>. The feed is newest-first, so the walk stops as soon as it reaches an older post — which also makes the run cheaper.

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

Upper bound of the date window, e.g. 2026-06-01. Posts published on or after this date are skipped. Pair it with "Only posts newer than" to pull one month out of a Page's history.

## `captionText` (type: `boolean`):

Extract Facebook's own video/reel transcript when the post has one. Off by default — it costs an extra request per video. Posts without captions still appear, with captionText set to null.

## `includeComments` (type: `boolean`):

Attach the comments Facebook shows on the public post (text, author, avatar, comment URL). On by default. The feed often inlines none, so a post with a comment count and an empty list gets one extra permalink request. Turn off to keep only the comment count.

## `proxy` (type: `object`):

Leave empty. Residential proxy is included and is not billed to you. Set this only if you want to route through your own proxies instead.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/nasa"
    }
  ],
  "scrapeMode": "posts",
  "maxPosts": 100,
  "maxPhotos": 100,
  "maxItems": 1000,
  "captionText": false,
  "includeComments": true,
  "proxy": {}
}
```

# Actor output Schema

## `results` (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 = {
    "startUrls": [
        {
            "url": "https://www.facebook.com/nasa"
        }
    ],
    "onlyPostsOlderThan": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/facebook-page-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" }],
    "onlyPostsOlderThan": "",
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/facebook-page-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"
    }
  ],
  "onlyPostsOlderThan": ""
}' |
apify call memo23/facebook-page-posts-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/facebook-page-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/xbKvkK6O7LcHz1a5F/builds/0p9Fu7bIxru4sCJip/openapi.json
