Facebook Posts Scraper — Page Timeline Export
Pricing
Pay per event
Facebook Posts Scraper — Page Timeline Export
Export public Facebook Page timeline posts — text, permalink, timestamp, author, reaction/share/comment counts, and photo/video attachments. No login, no cookies, no Facebook API access token. Works from a Page vanity name, numeric ID, or full URL.
Pricing
Pay per event
Rating
0.0
(0)
Developer
DevilScrapes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 hours ago
Last modified
Categories
Share
Point this Actor at any public Facebook Page (vanity name, numeric ID, or full URL) and it walks the timeline forward from the current cursor, returning one clean row per post. Facebook has no public feed API for this, so we talk to the same internal GraphQL endpoint the Page itself loads, handle the persisted-query id Meta rotates on every deploy, and hand you back structured JSON instead of a browser session to babysit. Pricing is per post landed — a run that finds nothing costs you only the flat start fee.
🎯 What this scrapes
Public Facebook Page timelines — the same posts you'd see scrolling a Page's own feed while logged out. For each page_identifiers entry we:
- Resolve the Page from a vanity name (
nasa), a numeric Page ID, or a fullfacebook.com/...URL. - Walk the timeline forward from the newest post, page by page, until we hit
max_posts_per_pageor the Page runs out of posts. - Emit one row per post: body text, permalink, author, timestamp, and engagement counts.
Groups, personal profiles, Reels, Marketplace listings, and Events are out of scope — see Limitations below.
🔥 Features
- 🛡️ We rotate browser fingerprints via
curl-cffiimpersonation so Facebook's servers see a real browser's TLS handshake, not a bare Python client. - 🔁 We retry with exponential backoff on
408/429/503and network errors — up to 5 attempts per request, honouringRetry-Afterwhen Facebook sends it. - 🌐 We route every request through Apify Proxy and keep sessions isolated per Page, so one blocked identifier never poisons the rest of the run.
- 🔧 We self-heal the persisted-query id. Meta rotates the internal
doc_idthis endpoint depends on with every deploy; when a refetch comes back stale, we re-derive the current id straight from the Page's own JS bundles, cache it, and retry — no manual patch, no waiting on us to ship a fix. - 🧱 We isolate failures per Page. A Page that's private, deleted, or behind a login wall doesn't take down the rest of your batch — it's logged and reported on its own, and the run keeps going.
- 🧊 We keep the dataset clean — Pydantic-validated rows, ISO-8601 timestamps, stable post IDs, structured attachments instead of raw HTML.
- 💰 You pay only for results that land. No posts, no per-post charge — only the small
actor-startwarm-up fee applies.
🧭 Use cases
- Competitive content tracking — monitor how often a competitor's Page posts, what they post, and how it performs (reactions/shares/comments) over time.
- Brand and PR monitoring — pull a Page's own public posts into your analytics stack instead of screenshotting a feed.
- Media/research datasets — build a corpus of public statements from official Pages (news outlets, government agencies, brands) for text analysis.
- Feed archiving — keep a structured, queryable record of a Page's public timeline instead of relying on Facebook's own search.
⚙️ How to use it
- Click Try for free (or run via API/CLI) and open the input form.
- Enter one or more
page_identifiers— vanity names, numeric Page IDs, or full URLs. Mixed forms are fine in the same run. - Set
max_posts_per_pageif you want fewer or more posts than the default40per Page. - Leave
proxy_configurationon its default unless you have a reason to override it. - Run it. Each
page_identifiersentry is processed in turn; results land in the dataset as they're scraped, so you can start exporting before the run finishes.
📥 Input
| Field | Type | Default | Notes |
|---|---|---|---|
page_identifiers | array of string | — | Required. Vanity name, numeric Page ID, or full URL. Mixed forms OK. |
max_posts_per_page | integer | 40 | Stop paginating a Page once this many posts are collected (1–1000). |
posts_per_request | integer | 8 | GraphQL refetch page size (1–20). Larger values don't reliably return more posts per request — Facebook's response size is driven by the feed, not this count. |
proxy_configuration | object | residential (RESIDENTIAL) | Apify Proxy configuration. Datacenter groups get Facebook's own rate limiting; override only if you know what you're doing. |
{"page_identifiers": ["nasa", "nike"],"max_posts_per_page": 10,"posts_per_request": 8,"proxy_configuration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"]}}
📤 Output
| Field | Type | Notes |
|---|---|---|
page_identifier | string | Echo of the input identifier this post was found under. |
post_id | string | Stable post id. |
text | string | null | Post body text. |
permalink | string | pfbid permalink URL. |
creation_time | string (ISO-8601) | Post creation time. |
author_name / author_id / author_url | string | Post author. |
is_cross_post | boolean | true when the author differs from the requested Page's own id. |
reaction_count / share_count / comment_count | integer | Engagement counts. |
attachments | array | Photo/Video attachments: {type, attachment_id, url}. |
{"page_identifier": "nasa","post_id": "1629528961875807","text": "Welcome to the Artemis Accords, Djibouti...","permalink": "https://facebook.com/NASA/posts/pfbid02JgLRZAPvjgGRymFsFcdzYXYdDqg3knu3X2PAsYbgFcjyMtCQw3XWRQZ8VbbUT6N5l","creation_time": "2026-09-06T00:00:00+00:00","author_name": "NASA","author_id": "100044561550831","author_url": "https://facebook.com/NASA","is_cross_post": false,"reaction_count": 393,"share_count": 21,"comment_count": 47,"attachments": [{ "type": "Photo", "attachment_id": "1629528958542474", "url": null }]}
💰 Pricing
Pay-per-event. No subscription, no seat fee — you're billed for a run's warm-up and for every post that actually lands in your dataset.
| Event | Price | Trigger |
|---|---|---|
actor-start | $0.20 | Once per run, the first time any Page's timeline is successfully read (whether that yields posts or a genuine zero-post result). |
post-scraped | $0.003 | Once per post written to the dataset. |
At the default settings that works out to $3.20 per 1,000 posts ($0.20 start + 1,000 × $0.003). A run against a Page with no new posts still costs the $0.20 start fee and nothing more — you're never billed per post for posts that weren't there.
🚧 Limitations
- Public Page timelines only. No login, no cookies beyond the anonymous bootstrap request, and no account of ours is used to reach anything.
- No groups, personal profiles, Reels, Marketplace, or Events. A profile or group URL will fail to resolve rather than return the wrong content.
- No comment-thread expansion. You get the
comment_counttotal, not the individual comments. - No historical date-range seeking. The Actor walks forward from the Page's current timeline cursor; it doesn't jump to an arbitrary past date.
- Depth is bounded by
max_posts_per_page. Facebook's own pagination limits, not this Actor, set the practical ceiling on how far back a single run can walk. - If Facebook's servers can't be reached at all, or a Page is private/deleted/behind a login wall, that identifier is reported as failed rather than as an empty (but successful) result — the two are never conflated.
posts_per_requestis not a real filter. Recon shows Facebook's response size doesn't scale with it — 8 is the measured effective value regardless of what's set. It's kept as an input for forward compatibility, not because it currently changes behaviour.
❓ FAQ
Does this need a Facebook account or access token? No. It reads the same public timeline anyone can see logged out.
Can it scrape groups or personal profiles? No — Page timelines only. See Limitations.
Why did one of my page_identifiers come back with zero rows?
Either the Page genuinely has no posts to return right now (a real, successful zero), or the identifier didn't resolve at all (private, deleted, or a login wall) — the run's status message tells you which, per identifier.
What proxy should I use?
Residential, which is the default — Facebook's own application-level rate limiting hits shared datacenter IP pools within the first request or two. Leave proxy_configuration as-is unless you have a specific reason to override it.
How far back can I go?
As far as max_posts_per_page lets the Actor walk forward through the timeline in one run. There's no date-range jump — see Limitations.
🙋 Your feedback
Found a Page this doesn't handle right, or want a field we don't capture yet? Message DevilScrapes on Apify or open an issue from the Actor's Store page — we read every one and ship fixes fast.