# Facebook Page Posts Scraper — Comments, Photos, 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
- **Features:** Limited permissions
- **Stats:** 259 total users, 95 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?

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 Page Posts Scraper — Comments, Photos, No Login

Scrape Facebook page 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, all seven reaction types, comment and share counts, hashtags/links, link previews, event cards, video duration and views, the original post behind a reshare, collaborators, 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 and public profiles 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. If the Page has not posted since that date, the run succeeds with an empty dataset rather than failing
- **Jump straight to any date** — `onlyPostsOlderThan` is sent to Facebook as a query bound, so a walk for 2021 starts at 2021 instead of paging down through five years of feed first. Both date fields also take relative periods: `3 months`, `2 weeks`, `1 year`
- **Several Pages at once** — up to three Pages are walked in parallel, each on its own exit IP, and your caps still hold exactly across all of them
- **Follower count on every post** — `pageFollowers` (exact, e.g. 28,715,709 for NASA), plus `pageLikes`, `pageTalkingAbout`, `pageCheckins` and a ready-made `engagementRate`. Read from the Page the scraper already opens, so it costs nothing extra and needs no second actor run
- **Only new posts (monitoring mode)** — turn on `monitoringMode` for scheduled runs: each run skips posts an earlier run delivered for that Page, stops once it reaches them, and you pay for new posts only. Nothing new means a successful empty run, not placeholder rows
- **Loud about empty runs** — a run that scrapes nothing is reported as FAILED, never a green run wrapping an empty dataset. The exception is a date window that selects no posts: that is a real answer, not a break
- **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`), `profile.php?id=…`, or Facebook's `/p/Page-Name-ID` links; sub-paths like `/photos` are trimmed for you
- **Public personal profiles too** — a person's public timeline works the same way as a Page's: `https://www.facebook.com/zuck` or `profile.php?id=4` returns their posts, with pagination, the date jump and the follower count (checked 2026-10-08 on Mark Zuckerberg and Bill Gates)
- **Every photo of an album post** — Facebook's feed stops at five photos per post. When a post has more, the scraper opens the post's photo set and adds the rest, so a 10-photo post comes back with 10 photos (`allAlbumPhotos`, on by default)
- **Parity fields** — `ocrText` on photos, `textReferences` / `link`, `collaborators`, `viewsCount` on reels, `pageAdLibrary`, and an optional video-transcript toggle
- **The whole post, not just its text** — every reaction type (`reactions.like` … `reactions.angry`), the link preview card (`linkPreview`: URL, title, description, source, image), event cards (`event`), video length (`videoDurationSeconds`) and plays apart from views (`playCount`), and for a reshare the original post itself (`sharedPost`: its text, author, time, media and link)

### 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" / "All attachments (photos) are not scraped" | `attachments` holds every photo of the post, not the feed's first five; `photoCount` is the album total |
| "Did you remove the array support for pages?" | `startUrls` takes as many Pages as you want in one run, and always has |
| Page follower count missing from post rows | `pageFollowers` on every feed row, plus `engagementRate`, from the same request |
| Re-running a schedule returns posts I already have | `monitoringMode` returns only posts no earlier run delivered |
| "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>`, `.../posts/pfbid…`,
`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.

**Share links work too.** A link from Facebook's Share button (`/share/…`, `/share/p/…`, `/share/r/…`,
`fb.watch/…`) is opened first to see what it points at, then read as that post, reel, photo or Page.
`inputUrl` keeps the link you pasted. A share link to a group, an event or an expired video is skipped
with a message saying what it opened, and a share link and the direct link to the same post are read
and charged once.

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 or public profile URLs, handles, `profile.php?id=…`, `/p/Page-Name-ID`, post permalinks, photo permalinks, or share links (`/share/…`, `fb.watch/…`) |
| `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 | — | `2026-01-01`, or a period such as `3 months`; stops the walk at the first older post. Empty window → succeeded, not failed |
| `onlyPostsOlderThan` | string | — | `2022-12-31`, or a period such as `1 year`; the walk jumps straight there instead of paging down from today |
| `allAlbumPhotos` | boolean | `true` | When a post has more than the five photos the feed sends, fetch the rest (one extra request for that post) |
| `monitoringMode` | boolean | `false` | Only posts no earlier run of yours delivered for the same Page. For Page feeds; post and photo URLs are always fetched |
| `captionText` | boolean | `false` | Pull Facebook's own video/reel transcript when the post has one |
| `includeComments` | boolean | `true` | Attach comment bodies as `topComments`. Each attached comment is billed. 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"
}
```

One month from a Page's history, years back (the walk starts at the end of the window):

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nasa" }],
  "onlyPostsNewerThan": "2021-12-01",
  "onlyPostsOlderThan": "2022-01-01"
}
```

The last three months, with relative dates:

```json
{
  "startUrls": [{ "url": "https://www.facebook.com/nasa" }],
  "onlyPostsNewerThan": "3 months"
}
```

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",
  "pageFollowers": 28715709,
  "pageLikes": null,
  "pageTalkingAbout": 91280,
  "pageCheckins": null,
  "engagementRate": 0.0046,
  "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,
  "reactionCareCount": 11,
  "reactionHahaCount": 0,
  "reactionWowCount": 15,
  "reactionSadCount": 0,
  "reactionAngryCount": 0,
  "reactions": { "like": 969, "love": 124, "care": 11, "haha": 0, "wow": 15, "sad": 0, "angry": 0 },
  "likesCount": 1119,
  "likes": 1119,
  "commentsCount": 94,
  "comments": 94,
  "sharesCount": 115,
  "shares": 115,
  "viewsCount": 27270,
  "playCount": null,
  "videoDurationSeconds": 65.3,
  "isVideo": true,
  "isSharedFromAnotherPage": false,
  "sharedPost": null,
  "linkPreview": null,
  "event": null,
  "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`, `pageFollowers`, `pageLikes`, `pageTalkingAbout`, `pageCheckins`, `engagementRate`, `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`, `reactionCareCount`, `reactionHahaCount`, `reactionWowCount`, `reactionSadCount`, `reactionAngryCount`, `reactions`, `likesCount` / `likes`, `commentsCount` / `comments`, `sharesCount` / `shares`, `viewsCount`, `playCount`, `videoDurationSeconds`, `isVideo`, `isSharedFromAnotherPage`, `sharedPost`, `linkPreview`, `event`, `textReferences`, `link`, `collaborators`, `pageAdLibrary`, `captionText` (only when the transcript toggle is on), `topComments`, `feedbackId`, `isMarketplaceListing`.

A reshare carries the post it points at:

```jsonc
"sharedPost": {
  "postId": "1641027247687747",
  "url": "https://www.facebook.com/NASAKennedy/posts/pfbid0B7K…",
  "text": "🔴 Tune in now to watch NASA's SpaceX Crew-13…",
  "time": "2026-09-26T16:55:59.000Z",
  "author": { "id": "100053414617500", "name": "NASA's Kennedy Space Center", "profileUrl": "https://www.facebook.com/NASAKennedy" },
  "media": [],
  "linkPreview": { "url": "https://www.youtube.com/watch?v=2B8stL_C-R4", "title": "NASA's SpaceX Crew-13 Arrival at Kennedy Space Center", "description": "NASA's SpaceX Crew-13 NASA astronauts…", "source": "youtube.com", "imageUrl": "https://external…" }
}
```

An event post carries the event card. Facebook shows the day as text to logged-out visitors, so `when` stays text:

```jsonc
"event": { "id": "1036015482730574", "name": "NASA's SpaceX Crew-13 Launch", "url": "https://www.facebook.com/events/1036015482730574/", "when": "Thu, 1 Oct", "place": null, "isCanceled": false, "interestedText": "322 people interested", "interestedCount": 322, "coverPhotoUrl": "https://scontent…" }
```

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 fee per post or photo (`post` / `photo`, $0.00099) and, when Include comment bodies is on, one fee per public comment attached (`comment`, $0.001 — $1 per 1,000). The comment event is in code now; the Store price card gets it once Apify unlocks pricing (16 Oct 2026) or Support adds it. Failed pages cost nothing, and items beyond your cap are never delivered, so they are never charged. Free Apify accounts can run a taste: 10 items from one page. A paid plan removes that cap.

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 and public profiles only.** A profile whose owner limits who sees their posts, a group or an event URL has no public timeline feed; the run says so per URL rather than returning an empty dataset. Whether a personal profile is public is the owner's setting, so some people's timelines open and others do not.
- **Album photos come from the post's photo set.** Photos added there are the ones Facebook shows to logged-out visitors, at the size it serves on that page; most are full size, a few come back at a smaller resolution. A very large album continues past the set page's first screen, and the log says how many photos were not fetched.
- **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 types add up to the total.** Facebook lists every reaction type a post received, so a type it leaves out really is zero. In the rare case where its list is cut short, the missing types come back `null` (not reported) rather than a guessed `0`.
- **`playCount` is only filled when Facebook publishes it**, which in practice means reels. Ordinary videos carry views and duration but no separate play count.
- **Pages run three at a time.** Rows from different Pages can interleave in the dataset; each row's `pageUrl` says where it came from.

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

**Can I scrape a person's profile, not a Page?**
Yes, when the profile is public. Paste the profile URL (`facebook.com/<username>` or `profile.php?id=<id>`) exactly as you would a Page. If Facebook does not show that timeline to logged-out visitors, the run says "No timeline feed on this page" for that URL and moves on.

**How is `engagementRate` calculated?**
Reactions + comments + shares on the post, divided by the Page's followers, as a percentage with four decimals. It is `null` when the follower count or all three counts are unknown. Feed rows have it; rows from pasted post URLs do not, because those runs never open the Page itself.

**How does monitoring mode know what I already have?**
It keeps the ids of posts it delivered, one list per Page, in a key-value store in your own Apify account (`facebook-page-posts-seen`). Each run skips those posts and stops after it meets a dozen of them in a row. Delete that store to start over. A run that finds nothing new succeeds with an empty dataset and says so in its status message.

**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, facebook page followers, facebook profile posts scraper, facebook album photos, facebook engagement rate, facebook new posts monitoring, public facebook page scraper, facebook scraper no cookies

# Actor input Schema

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

Facebook Pages or public profiles to walk (https://www.facebook.com/<page or username>, 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=...`). Share links from Facebook's Share button (`/share/...`, `fb.watch/...`) are opened and read as whatever they point at. 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: a date such as <code>2026-01-01</code>, or a period counted back from the run's start such as <code>3 months</code> or <code>2 weeks</code> (UTC). The feed is newest-first, so the walk stops as soon as it reaches an older post, which also makes the run cheaper. If the Page has not posted since then, the run succeeds with an empty dataset.

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

Upper bound of the date window: a date such as <code>2022-12-31</code>, or a period such as <code>1 year</code> (posts published more than a year ago). The walk jumps straight to this date, even years back, instead of paging down from today. Pair it with "Only posts newer than" to pull one month out of a Page's history.

## `monitoringMode` (type: `boolean`):

Return only posts that no earlier run of yours has delivered for the same Page. Built for scheduled runs: each run picks up where the last one stopped and ends once it reaches posts you already have, so you pay for new posts only. A run with nothing new succeeds with an empty dataset. Applies to Page feeds, not to post or photo URLs.

## `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 — each attached comment is billed as a separate comment event. The feed often inlines none, so a post with a comment count and an empty list costs an extra permalink request. Turn off to keep only the comment count and skip the comment charge.

## `allAlbumPhotos` (type: `boolean`):

Facebook's feed sends at most five photos per post. When a post has more, fetch the rest from the post's photo set (one extra request for that post only) so `attachments` holds every photo. Turn off to keep only the first five; `photoCount` still tells you the total.

## `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,
  "onlyPostsOlderThan": "",
  "monitoringMode": false,
  "captionText": false,
  "includeComments": true,
  "allAlbumPhotos": 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/YcDpbiRITpBxxK9Qr/openapi.json
