Facebook Pages & Recent Posts Scraper
Pricing
$19.99/month + usage
Facebook Pages & Recent Posts Scraper
Facebook Page Scraper collects data from Facebook pages, including posts, followers, page info, and engagement metrics. It automates public page extraction for research, analytics, or marketing insights, exporting clean JSON or CSV data for easy integration into workflows.
Pricing
$19.99/month + usage
Rating
5.0
(3)
Developer
Scraper Engine
Maintained by CommunityActor stats
1
Bookmarked
297
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Facebook Pages Scraper — Followers, Contacts and Recent Posts
Facebook Pages & Recent Posts Scraper extracts public Facebook Page profiles into structured JSON — Page name, category, follower and like counts, intro text, email, phone, address, website and linked Instagram — and, when you switch on Fetch recent posts, adds each Page's latest posts as separate child rows with post text, permalink, publish time, a seven-type reaction breakdown, comment and share counts, and posting-cadence metrics on the parent row. Paste Page URLs or bare usernames, no login, no HTML parsing.
⚠️ Three things worth knowing before your first run. maxPagesToScan defaults to 10 and truncates your URL list — paste 50 Pages and only the first 10 are scraped. pageUrls is prefilled in the Apify Console with a sample Page, so a Console run you never edit will scrape that sample; an API call that omits pageUrls fails outright instead. And unlike most scrapers here, a Page that could not be fetched still gets a row, and that row is charged — filter it out with success !== false.
What is Facebook Pages & Recent Posts Scraper?
Facebook Pages & Recent Posts Scraper is an Apify Actor that reads a public Facebook Page the way a logged-out visitor sees it, and returns two record types in one dataset: one page row per Page with its full public profile block, and — optionally — one post row per recent post from that Page's timeline. It resolves each Page's numeric id and Facebook's own timeline GraphQL operation from the live page on every run, so nothing is hard-coded against Facebook's markup.
No Facebook account, login, cookie or session is used anywhere in the run. The only credential you need is your Apify token.
It is built for lead researchers building prospect lists from public business Pages, social media analysts benchmarking competitor Pages, and developers and AI engineers piping Page profiles and post engagement into dashboards, RAG indexes or agent tools.
What Facebook Page and post data is publicly available to scrape?
Facebook renders a public Page to logged-out visitors: the Page name, category, intro, follower and like counts, whatever contact details the Page owner chose to publish, and the top of the timeline with each post's text, permalink, time and engagement counters. What sits behind the login wall is who engaged, and anything a Page admin sees.
| Data Category | Publicly Available | Gated behind |
|---|---|---|
| Page name, category, intro/about text, profile and cover photo | ✅ Public | — |
| Follower count and like count, as Facebook displays them | ✅ Public | — |
| Contact block — email, phone, address, website, linked Instagram | ✅ Public when the Page owner publishes it | — |
| Recommendation percentage and review count | ✅ Public when the Page enables recommendations | — |
| Recent post text, permalink and publish time | ✅ Public | — |
| Per-post reaction totals, comment count and share count | ✅ Public | — |
| Comment text and commenter identities | ❌ Not returned — counts only, and not part of the timeline feed this Actor reads | A dedicated comments scraper |
| Identities of the people who reacted to or shared a post | ❌ | Logged-in Facebook account |
| Page insights — reach, impressions, click-throughs | ❌ | Page admin access |
Facebook Pages & Recent Posts Scraper only returns publicly visible data — what any logged-out visitor sees on the Page. Nothing behind a login wall.
⚠️ What "recent posts" actually means here
"Recent" is not a date window, and it is not a fixed number of days. It is the top of the Page's timeline, newest first, cut off by the count you set.
With fetchPosts on, the Actor bootstraps the Page's timeline feed and then pages through it with Facebook's own cursor, collecting maxPostsPerPage posts per Page (default 25, schema range 1–5,000) in batches of eight posts per request. It stops at whichever comes first:
- your
maxPostsPerPagecount is reached — the Actor's limit; - Facebook stops returning a next cursor, i.e. the public feed will not go deeper — Facebook's limit;
- the Actor's own pagination guard of 300 timeline requests per Page trips. At eight posts per request that is an effective ceiling of roughly 2,400 posts per Page, below the 5,000 the schema accepts — the Actor's limit;
- with
onlyPostsNewerThanset, a whole batch comes back older than your date floor, at which point paging stops early — the Actor's limit.
So on default settings, "recent" means the newest 25 posts Facebook will serve to a logged-out visitor. Set onlyPostsNewerThan if you want a genuine time window instead — it is the only input that turns the count into a date-bounded window. How far back that reaches depends entirely on how often the Page publishes.
With fetchPosts off — the default — no posts are collected at all and you get Page profile rows only.
What data can I extract with Facebook Pages & Recent Posts Scraper?
Two row shapes share the dataset, told apart by type and isChild: a page row carries the Page's identity, contact block, audience figures and rating summary; a post row carries one recent post's text, timing, media and engagement.
Page identity, contact and profile fields
A successful page row carries 41 keys, or 42 when fetchPosts is on.
| Field Name | Description |
|---|---|
type | Always "page" on this row shape |
isChild | Always false on page rows |
facebookUrl | The normalised Page URL the row was collected from |
pageUrl | Same value as facebookUrl, kept as a compatibility alias |
title | The Page's display name |
pageName | First path segment of the URL — the Page slug, not the display name |
pageId | Facebook's numeric id for the Page |
facebookId | Same value as pageId, kept as a compatibility alias |
categories | Array of category names as Facebook lists them. [] when none was found |
category | First entry of categories, or null |
intro | The Page's intro / best-description text |
about_me | Object: text — the same intro string, in the legacy nested shape |
info | Array built from the Page's meta description, typically the "N likes" fragment and the remainder |
email | Public email from the Page's intro card, when published |
phone | Public phone number, when published |
address | Public street address, when published |
addressUrl | A Bing Maps search URL the Actor builds from address — not a link Facebook published |
websites | Array of every outbound URL found on the Page — website, Google Maps link and Instagram, de-duplicated and with l.facebook.com redirects decoded |
website | Bare domain of the first website found, e.g. copperkettleyqr.com |
instagram | Array of objects: username, url |
alternativeSocialMedia | The linked Instagram URL as a plain string |
services | Services line from the Page's intro card |
business_services | Same value as services, kept as a compatibility alias |
business_hours | Hours line from the intro card, or an "Open now" / "Closed now" fragment |
priceRange | Price band normalised to dollar symbols — a ££ Page is rewritten as $$ |
business_price | Human-readable form: "Price Range · $$" |
creation_date | Page creation date. String; format varies with the source — usually "March 14, 2011" |
ad_status | "This Page is currently running ads." when Facebook exposes it, otherwise null |
pageAdLibrary | Object: is_business_page_active, id — the ids you need for Meta Ad Library lookups |
profilePictureUrl | Direct CDN URL of the profile picture |
coverPhotoUrl | Direct CDN URL of the cover photo |
profilePhoto | A facebook.com/photo/?fbid=… link, reconstructed from the profile picture id |
scrapedAt | ISO-8601 UTC timestamp of when the row was built |
success | true on a scraped Page, false on a Page that could not be fetched |
error | Only present when success is false — the failure message |
Page audience, rating and posting-cadence fields
| Field Name | Description |
|---|---|
likes | Page like count, parsed from the Page's meta description |
followers | Follower count as an integer, expanded from the compact string Facebook displays — 2.6K becomes 2600, so it inherits Facebook's own rounding |
followings | How many Pages this Page follows, as an integer, when exposed |
rating | Recommendation summary text, e.g. "94% recommend (202 Reviews)" |
ratings | Same value as rating, kept as a compatibility alias |
ratingOverall | The recommendation percentage as an integer |
ratingCount | Number of reviews behind that percentage |
recentPosts | Only present when fetchPosts is on. Object with postsFetched, postsWithDate, newestPostAt, oldestPostAt, postsPerWeek, avgGapDays, spanDays |
recentPosts is computed from the timestamps of the posts actually collected for that Page, so it describes your sample, not the Page's whole history. postsPerWeek, avgGapDays and spanDays are null when fewer than two dated posts were collected — the Actor leaves them empty rather than inventing a cadence from a single post.
Recent post fields
Every post row carries the same 21 keys, always present.
| Field Name | Description |
|---|---|
type | Always "post" on this row shape |
isChild | Always true on post rows |
parentPageId | Numeric id of the Page the post came from — the join key back to the page row |
parentPageUrl | That Page's facebookUrl |
parentPageName | That Page's display name |
postId | Facebook's numeric post identifier. null when Facebook did not expose one |
postUrl | Post permalink. null when absent |
postType | Attachment class: only ever "photo" or "video" — see the note below |
text | Full post message. null, not "", on a media-only post |
publishedAt | Publish time as an ISO-8601 UTC string, e.g. 2026-07-22T16:05:11Z |
publishedAtUnix | The same moment as a Unix timestamp in seconds |
reactionsTotal | Total reactions of every type, from Facebook's reaction_count |
reactions | Object with all seven keys always present: like, love, care, haha, wow, sad, angry |
commentsCount | Total comment count on the post |
sharesCount | Total share count on the post |
engagementTotal | reactionsTotal + commentsCount + sharesCount, computed by the Actor |
imageUrl | Direct CDN URL of the post image. null on posts without one |
videoUrl | Direct playable video URL. null on posts without one |
externalUrl | Outbound link on a link-preview post. null otherwise |
author | Object: id, name, url, profile_picture_url — empty strings when Facebook did not expose them |
scrapedAt | ISO-8601 UTC timestamp of when the post was parsed |
Three shape rules to code against. First, postType has only two values, "photo" and "video", and "photo" is the fallback — so a text-only post with no attachment is still labelled "photo". Treat an empty imageUrl and videoUrl as the real signal for a text post, not postType. Second, the seven reactions counts come from Facebook's top reactions list for that post, so they do not necessarily sum to reactionsTotal — a reaction type Facebook did not surface stays at 0. Use reactionsTotal for the true total and the breakdown for the mix. Third, author.profile_picture_url is snake_case inside an otherwise camelCase row; that is the shape as published, kept stable rather than quietly renamed.
Counts are read from each story's own feedback node, so a post's reaction, comment and share figures always belong to that exact post — never a Page-wide total leaking into every row.
🤖 Add-on: Need additional Facebook data?
Post rows carry counts, not conversations. Facebook Posts Scraper goes far deeper on a single Page or profile timeline — media arrays, feedback ids and video transcripts — when the Page profile block is not what you are after. Facebook Group Post Scraper reaches posts inside public groups, which a Page timeline never contains. For discovery rather than a known Page, Facebook Hashtag Search Scraper and Facebook Groups Search Scraper find content and communities by keyword.
How does Facebook Pages & Recent Posts Scraper differ from the official Facebook Graph API?
Meta publishes the Graph API, and it is the supported route when you administer the Page in question — versioned fields, documented terms, and access to insights no visitor can see. The gap this Actor fills is third-party access: reading a Page you do not administer requires an approved app and, on most surfaces, permission from the Page owner.
| Feature | Meta Graph API | Facebook Pages & Recent Posts Scraper |
|---|---|---|
| App id, app secret and access token | ✅ Required | ❌ Not required — an Apify token is enough |
| App Review before production access | ✅ Required | ❌ Not required |
| Reading a Page you do not administer | Needs an approved app and, per Meta's docs, the Page owner's permission | ✅ Any public Page URL |
| Page profile block and recent posts together | Separate edges and separate permission scopes | ✅ One run returns both row types |
| Quota and throttling terms | Governed by Meta's published platform limits | Bounded by this Actor's own per-Page request budget |
| Page insights — reach, impressions | ✅ Available to Page admins | ❌ Not returned — not publicly visible |
| Output shape | Meta's versioned Graph schema | Two fixed row shapes, same keys every run |
Every constraint above should be verified against Meta's current published Graph API documentation before you choose — permission scopes, review requirements and platform limits change, and no figures from them are reproduced here.
Use the Graph API when you own or manage the Page, need Meta's contractual guarantees, or want admin-only insight metrics. Use this Actor for competitor, prospect and market research across Pages you have no relationship with.
How to use Facebook Pages & Recent Posts Scraper
The Actor runs on Apify. Start it from the Apify Console or call it through the Apify API — there is no separate signup and no other key to obtain.
- Open Facebook Pages & Recent Posts Scraper on Apify and click Try for free
- Replace the prefilled sample in Facebook page URLs or usernames (
pageUrls) with your own targets — full URLs and bare usernames both work, and you can mix them - Raise Max pages to scan (
maxPagesToScan) to at least the number of URLs you pasted — the default10silently cuts a longer list - Switch on Fetch recent posts (
fetchPosts) if you want post rows, and set Max posts per page (maxPostsPerPage) - Optionally set Only posts newer than (
onlyPostsNewerThan) to2026-01-01or3 monthsto bound the feed by date - Click Start, then export the dataset as JSON, CSV, Excel or XML
Pages are processed sequentially, one after another with a short pause between them, and each Page's post rows are written straight after its page row, so the dataset reads as page-then-posts, page-then-posts.
How to scale to bulk Page extraction
pageUrls is a list, so one run can cover as many Pages as you need — plain strings and { "url": "..." } objects are both accepted, which means output from another Actor can usually be fed straight in. Just remember maxPagesToScan is the run's real ceiling: it is applied to your list, not to a crawl, so it must be raised to match. maxPostsPerPage then applies per Page, not per run — twenty Pages at 25 posts is a ceiling of 500 post rows plus 20 page rows.
Input URLs are not de-duplicated. The same Page listed twice is fetched twice and billed twice, so de-duplicate your list before you submit it. For a recurring sweep, put the same input on an Apify schedule and attach a webhook to push each finished dataset into your own store.
What can you do with Facebook Page and post data?
- 📍 A local-lead researcher building a restaurant prospect list uses
category,email,phone,addressandwebsitesto qualify public business Pages before any outreach, dropping Pages with no published contact route. - 📊 A social media analyst benchmarking a competitor set enables
fetchPostsand ranks Pages byrecentPosts.postsPerWeekandavgGapDays, separating the accounts that publish constantly from the ones coasting on an old audience. - 😀 A brand manager watching sentiment reads
reactions.angryandreactions.sadagainstreactions.loveacross a Page's newest posts, catching a negative shift thatreactionsTotalalone would hide. - 🕐 A content strategist planning a calendar groups
publishedAtUnixby weekday and hour across the collected posts and correlates each slot withengagementTotalto find when that audience actually responds. - 🤖 An AI engineer building a brand-monitoring agent indexes
title,introand every post'stextinto a vector store, keying each chunk onparentPageIdand citingpostUrl, so the agent answers questions against live public Pages rather than a stale export.
Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.
How does Facebook Pages & Recent Posts Scraper handle rate limits and blocking?
The Actor does not drive a browser and does not solve CAPTCHAs. It defends against blocking with residential egress, session rotation and layered retries.
Egress. Every request goes through Apify Residential proxy, always. A fresh residential session URL is drawn before each Page, so one Page's blocked IP does not poison the next.
Retries on the Page fetch. A Page gets up to four attempts — the first fetch plus three residential rotations — but rotation is triggered only by a block signal: a response mentioning 429, 403, 401, blocked, rate limit, forbidden, login or captcha, or a redirect to the login page. Any other error ends that Page after a single attempt. There is a two-second pause between rotations and between Pages.
Retries on the posts fetch. Collecting a Page's posts gets three attempts with a new residential session each time, and inside each attempt the underlying HTML and GraphQL requests retry three times of their own with a short escalating backoff. Posts are gathered fully into memory before any row is written, so a mid-collection rotation can never duplicate rows in your dataset.
If a Page still cannot be fetched, the Actor writes a failure row for it — success: false and an error string — and moves to the next Page. If every Page in the run fails, the run itself is marked failed. Proxy access must be enabled on your Apify plan; if the residential proxy cannot be started at all, the run stops immediately rather than falling back to a direct connection.
⬇️ Input
Six parameters and none of them is required by the schema — which makes the two notes under the table the most important part of this section.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
pageUrls | No | array | Facebook Page targets, one per line. Full links and bare usernames both work; a username is expanded to facebook.com/{username}. Plain strings and { "url": "..." } objects are both accepted. A startUrls list is accepted as an alias. | ["https://www.facebook.com/copperkettleyqr", "nytimes"] |
maxPagesToScan | No | integer | How many Page URLs to process this run — counts Pages, not posts. Minimum 1, maximum 10000. Default 10. | 50 |
fetchPosts | No | boolean | When on, each Page also returns its recent posts as child rows and gains a recentPosts cadence object. Default false — Page data only. | true |
maxPostsPerPage | No | integer | Cap on recent posts collected per Page. Minimum 1, maximum 5000. Default 25. | 100 |
onlyPostsNewerThan | No | string | Keep only posts published on or after this date. Absolute YYYY-MM-DD, or relative with a unit — 7 days, 2 weeks, 1 month, 1 year. Empty means no date limit. | "2026-01-01" |
proxyConfiguration | No | object | Accepted for the Console UI only. The Actor always uses Apify Residential — see the note below. | {"useApifyProxy": true} |
Nothing is required, but an empty run does not silently do something. With no pageUrls and no startUrls, the Actor fails immediately with Missing pageUrls: add at least one Facebook page URL or username. — there is no hidden default Page, and no rows are written or charged. The trap is the other way round: pageUrls carries a Console prefill of https://www.facebook.com/copperkettleyqr, so a Console user who never touches the field starts a run against that sample Page and pays for the row, while an API caller who omits the same field gets a hard failure. The prefill only exists in the Console; the API applies no default.
maxPagesToScan truncates, it does not extend. It is applied to the list you supplied — the first maxPagesToScan entries are scraped and the rest are dropped without a warning row. At its default of 10, a 50-URL list becomes a 10-URL run. Values outside 1–10,000 are clamped, and a non-numeric value falls back to 10.
Four more honest notes on the remaining inputs:
proxyConfigurationis not an override. The value is read and then discarded: the Actor always requests an Apify Residential proxy. Selecting datacenter groups, supplying customproxyUrlsor unchecking Apify Proxy changes nothing and does not produce a direct connection. If residential proxy is unavailable on your account, the run fails at startup with a message saying so.maxPostsPerPageis a ceiling you may not reach. It is clamped to 1–5,000, but the Actor's own guard of 300 timeline requests per Page — eight posts each — caps a Page at roughly 2,400 posts regardless of what you type. Facebook's public feed depth and the Page's actual post count usually end collection long before either. A non-numeric value falls back to100, not to the schema default of25.onlyPostsNewerThanis validated even whenfetchPostsis off. An unparseable value fails the whole run withInvalid onlyPostsNewerThan: …before any Page is fetched. Relative values need a unit word —7 days,2 weeks,1 month,1 year; a bare number such as7is rejected rather than ignored. When the filter is active, a post whose timestamp could not be read is dropped, not kept.maxPostsPerPageandonlyPostsNewerThando nothing whilefetchPostsisfalse. They are parsed, but with posts off no timeline is ever requested.
Example input
{"pageUrls": ["https://www.facebook.com/copperkettleyqr","https://www.facebook.com/nytimes/","bbcearth"],"maxPagesToScan": 50,"fetchPosts": true,"maxPostsPerPage": 25,"onlyPostsNewerThan": "3 months","proxyConfiguration": {"useApifyProxy": true}}
⬆️ Output
Two typed, normalized row shapes with stable keys across runs: page rows with 41 keys — 42 when fetchPosts is on — and post rows with 21. Post rows for a Page follow that Page's own row, so the dataset reads in page-then-posts order. Export as JSON, CSV, Excel or XML, or read the dataset through the Apify API. With fetchPosts on, post rows are also mirrored into a named per-run dataset called {runId}-posts, so you can consume posts on their own without filtering.
Three shape rules to code against:
- Failure rows exist, and they are charged. A Page that could not be fetched produces
{"facebookUrl": …, "error": "Failed to scrape page after 4 attempts", "success": false, "type": "page", "isChild": false}— plus a zeroedrecentPostsobject whenfetchPostsis on. Every profile key is omitted, not nulled, so never index a page row blindly. Onerow_resultevent is charged per row pushed, failure rows included; the mirrored{runId}-postscopies are not charged again. - The exact filter for real data is
success !== false. Do not filter onsuccess === true: post rows carry nosuccesskey at all, so that expression would discard every post. In Python,[r for r in items if r.get("success") is not False]. success: trueon a page row does not mean posts were collected. If the timeline fetch fails for an otherwise healthy Page, the page row still reports success,recentPosts.postsFetchedis0, and no post rows follow. CheckrecentPosts.postsFetchedwhen you need posts specifically.
Example output
A page row with fetchPosts on:
{"facebookUrl": "https://www.facebook.com/copperkettleyqr","categories": ["Restaurant"],"info": ["Copper Kettle Restaurant, Regina, SK. 2,384 likes","1,205 talking about this · 3,410 were here"],"likes": 2384,"priceRange": "$$","title": "Copper Kettle Restaurant","address": "1953 Scarth St, Regina, SK, Canada","pageId": "100063568041234","pageName": "copperkettleyqr","pageUrl": "https://www.facebook.com/copperkettleyqr","intro": "Family-run restaurant in downtown Regina serving pizza, pasta and Greek classics since 1976.","websites": ["http://www.copperkettleyqr.com/","https://www.instagram.com/copperkettleyqr"],"phone": "+1 306-525-3545","email": "hello@copperkettleyqr.com","alternativeSocialMedia": "https://www.instagram.com/copperkettleyqr","website": "copperkettleyqr.com","services": "Dine-in · Takeout · Delivery","rating": "94% recommend (202 Reviews)","followers": 2600,"followings": null,"profilePictureUrl": "https://scontent-ord5-2.xx.fbcdn.net/v/t39.30808-1/302145678_436705561807015_5583912047_n.jpg","coverPhotoUrl": "https://scontent-ord5-2.xx.fbcdn.net/v/t39.30808-6/419883012_899104455112307_1120448371_n.jpg","profilePhoto": "https://www.facebook.com/photo/?fbid=436705561807015","ratingOverall": 94,"ratingCount": 202,"category": "Restaurant","addressUrl": "https://www.bing.com/maps/default.aspx?v=2&pc=FACEBK&mid=8100&where1=1953+Scarth+St%2C+Regina%2C+SK%2C+Canada&FORM=FBKPL1&mkt=en-US","instagram": [{"username": "copperkettleyqr","url": "https://www.instagram.com/copperkettleyqr"}],"ratings": "94% recommend (202 Reviews)","business_hours": "Open now","business_price": "Price Range · $$","business_services": "Dine-in · Takeout · Delivery","creation_date": "March 14, 2011","ad_status": "This Page is currently running ads.","about_me": {"text": "Family-run restaurant in downtown Regina serving pizza, pasta and Greek classics since 1976."},"facebookId": "100063568041234","pageAdLibrary": {"is_business_page_active": true,"id": "100063568041234"},"scrapedAt": "2026-07-25T09:14:22.481Z","success": true,"type": "page","isChild": false,"recentPosts": {"postsFetched": 25,"postsWithDate": 25,"newestPostAt": "2026-07-22T16:05:11Z","oldestPostAt": "2026-04-03T18:30:02Z","postsPerWeek": 1.59,"avgGapDays": 4.58,"spanDays": 109.9}}
The first post row that follows it:
{"type": "post","isChild": true,"parentPageId": "100063568041234","parentPageUrl": "https://www.facebook.com/copperkettleyqr","parentPageName": "Copper Kettle Restaurant","postId": "1198437265510934","postUrl": "https://www.facebook.com/copperkettleyqr/posts/pfbid02rTk9Ye7mQwLdX4sVbnH3pKcAqZ1uWgRf8yNjM6xEoTs","postType": "photo","text": "Patio season is officially open. Doors at 11, kitchen until 10, and the Greek platter is back on the board for the summer.","publishedAt": "2026-07-22T16:05:11Z","publishedAtUnix": 1784649911,"reactionsTotal": 143,"reactions": {"like": 96,"love": 31,"care": 4,"haha": 0,"wow": 2,"sad": 0,"angry": 0},"commentsCount": 18,"sharesCount": 6,"engagementTotal": 167,"imageUrl": "https://scontent-ord5-2.xx.fbcdn.net/v/t39.30808-6/620114873_1198437198844274_7739021845_n.jpg","videoUrl": null,"externalUrl": null,"author": {"id": "100063568041234","name": "Copper Kettle Restaurant","url": "https://www.facebook.com/copperkettleyqr","profile_picture_url": "https://scontent-ord5-2.xx.fbcdn.net/v/t39.30808-1/302145678_436705561807015_5583912047_n.jpg"},"scrapedAt": "2026-07-25T09:14:41.902Z"}
Note that the seven reactions values sum to 133 while reactionsTotal is 143 — the breakdown reflects the reaction types Facebook surfaced for that post, so the gap is expected rather than a parsing error.
How does it work?
Facebook builds a public Page from its own embedded JSON and a GraphQL timeline feed, and that is what this Actor reads. For each target it normalises your entry into a Facebook URL, draws a fresh Apify Residential session, and loads the Page once with a full desktop Chrome header set. From that single HTML response it pulls the Page's structured profile blocks — the description payload and the intro-card context list — and falls back to targeted regex over the raw HTML for fields the JSON does not carry.
If fetchPosts is on, it then bootstraps the timeline: the Page's numeric id comes from Facebook's own deep-link meta tags, and the id of the timeline GraphQL operation is lifted from Facebook's live JavaScript bundles on every single run — nothing is hard-coded, so a renamed bundle or a rotated operation id does not break it. It calls that operation directly, following the feed cursor eight posts at a time, and reads each post's engagement from that post's own feedback node.
Only publicly visible data is collected — no account, cookie or session is used anywhere. Because the Actor reads structured feed data rather than rendered markup, a Facebook visual redesign generally does not move your field names.
Integrations
Facebook Pages & Recent Posts Scraper is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset.
Calling Facebook Pages & Recent Posts Scraper from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/facebook-pages-recent-posts-scraper").call(run_input={"pageUrls": ["https://www.facebook.com/copperkettleyqr", "nytimes"],"maxPagesToScan": 50,"fetchPosts": True,"maxPostsPerPage": 25,"onlyPostsNewerThan": "3 months",})for row in client.dataset(run["defaultDatasetId"]).iterate_items():if row.get("success") is False:continueif row["isChild"]:print(" post", row["publishedAt"], row["engagementTotal"], (row["text"] or "")[:60])else:print(row["title"], row["followers"], row.get("email"))
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Guard on success is not False before reading profile keys, since failure rows omit them entirely.
Scheduling and webhooks
Put the same input on an Apify schedule for a recurring sweep — a weekly pull of the last seven days is "onlyPostsNewerThan": "7 days" on a weekly cron. Attach an Apify webhook on run success to have each finished dataset posted to your own endpoint, so new Page data lands in your warehouse without polling.
No-code tools (n8n, Make, Zapier)
In n8n, use the Apify node — or an HTTP Request node pointed at the Apify run endpoint with your token — and pass the same JSON input shown above; an Item Lists node then splits page and post rows for downstream steps, and a Filter node on isChild separates them cleanly. In Make, the Apify module supports run-and-wait, so a daily competitor monitor can feed Google Sheets, Airtable or Slack directly. Both platforms handle scheduling, so a recurring Page sweep needs no code at all.
Is it legal to scrape Facebook Pages and posts?
Scraping publicly visible Pages is broadly treated as permissible where no authentication is bypassed, and this Actor returns only public data — what any logged-out visitor sees. But this output contains personal data, and that changes your obligations rather than the Actor's.
A Page belonging to an organisation is a business identity; a Page belonging to an individual creator, consultant or public figure is a person, and the same fields then carry personal data — which is why you should treat the whole output as personal data unless you have segmented your targets. The identifying fields are specific: on page rows, title, pageName, pageId, facebookId, email, phone, address, intro, about_me.text, profilePictureUrl, coverPhotoUrl, instagram and alternativeSocialMedia; on post rows, author.id, author.name, author.url, author.profile_picture_url, parentPageName, text, postId and postUrl. Under GDPR, the UK GDPR and CCPA, storing or processing those needs a lawful basis you can document — legitimate interest, consent, or another ground — and public availability is not a lawful basis by itself.
Practise data minimisation: drop email, phone, address, the author object and text first, since they carry the most identifying weight. What remains — followers, likes, reactionsTotal, commentsCount, sharesCount, engagementTotal, publishedAtUnix and the recentPosts cadence object — supports most engagement analysis with no personal data at all. Set a retention limit rather than keeping rows indefinitely, and be ready to honour access and deletion requests against whatever you store.
Consult legal counsel if your use case involves bulk storage of personal data, building contact lists for outreach, profiling individuals, or re-publishing post text and author identities.
❓ Frequently asked questions
What Facebook Page fields does Facebook Pages & Recent Posts Scraper return?
The five most used are title, category, followers, email and phone on the page row, with text, publishedAt, reactionsTotal, commentsCount and engagementTotal leading the post rows. A page row carries 41 keys — 42 with fetchPosts on — and every post row carries 21. See the data fields tables above for all of them.
Does Facebook Pages & Recent Posts Scraper require a Facebook account, login or cookie?
No. No account, no cookie, no session, no Facebook app credentials, and none of that is hidden in an optional field either — the Actor sends logged-out requests throughout and reads only what a public Page serves to an anonymous visitor. The one credential you need is your Apify token, and Apify Proxy must be enabled on your plan because residential egress is mandatory.
How many Pages and posts can I extract in one run?
maxPagesToScan sets the Page ceiling — up to 10,000 — and maxPostsPerPage applies per Page, up to 5,000. In practice the post number is bounded twice more: the Actor stops a Page after 300 timeline requests, which at eight posts per request is roughly 2,400 posts, and Facebook's public feed only goes as deep as it chooses to serve. How many posts you actually get depends on how many the Page has published in the window you asked for.
What happens if a Page has no recent posts, is unpublished, or is region-restricted?
You still get a page row, but what is in it depends on which layer failed. An unpublished, deleted or region-restricted Page that will not render to a logged-out visitor produces a failure row — success: false with an error string, every profile key omitted — after four attempts, and the run continues to the next Page. A healthy Page that simply has no posts in your window produces a normal page row with success: true, recentPosts.postsFetched: 0, postsPerWeek: null, and no post rows following it. The same shape appears when the Page loads fine but its timeline cannot be read, so branch on recentPosts.postsFetched rather than on success when posts are what you need. If every Page in the run fails, the run itself is marked failed.
Can I scrape multiple Facebook Pages at once?
Yes. pageUrls is a list — add as many Pages as you like, mixing full URLs and bare usernames freely, including usernames containing dots, which are expanded verbatim to facebook.com/{username}. Two caveats: raise maxPagesToScan above its default of 10 or the list is truncated, and de-duplicate your list first, because a repeated URL is scraped and billed twice.
Does Facebook Pages & Recent Posts Scraper return comment text or the names of people who reacted?
No — it returns counts. commentsCount and sharesCount are totals, and reactions breaks reactions into seven types, but comment text, commenter names, sharer identities and reactor identities are not part of the timeline feed this Actor reads. Collect postUrl and postId from the rows and pass them to a dedicated Facebook comments Actor when you need the underlying conversation.
Does Facebook Pages & Recent Posts Scraper work with Claude, ChatGPT and other AI agent tools?
Yes. It is callable as a standard HTTP endpoint through the Apify API, so LangChain, CrewAI, n8n or a hand-written tool definition can invoke it and receive typed JSON with no parsing step.
How does Facebook Pages & Recent Posts Scraper compare to other Facebook Page scrapers?
Checked on the Apify Store on 25 July 2026: danek/facebook-pages-posts-ppr is the most used of the alternatives and is posts-only — its listing documents post_id, url, message, timestamp, comments_count, reactions_count, author, image, video and attached_post_url, with no per-type reaction breakdown, no share count and no Page profile record; it states pricing of "$2.99 per 1k results", that the Actor is "only for paid users" with free users limited to 2 results, and that no proxy is needed. powerful_bachelor/Facebook-Posts-Scraper describes page bio, category, followers and engagement metrics in prose, but its listing documents no input parameters and no output field names. pear_fight/facebook-scraper is Playwright-driven, documents pageUrls, maxPosts (default 20) and proxyConfig as inputs and page name plus post text, date, likes, comments, shares, images and videos as output, described as "either page info or post data" without a field list.
What this Actor documents that they do not: the full contact block as named keys, a seven-type reaction breakdown read from each post's own feedback node, recentPosts cadence metrics computed on the parent Page row, exactly which keys are omitted rather than nulled, and the fact that failure rows are written and charged.
Does Facebook Pages & Recent Posts Scraper return data in a format LLMs can use directly?
Yes. Every row is typed, normalized JSON with the same field names on every run. No HTML parsing, no selectors. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool — no transformation step required.
What happens when Facebook changes its layout or anti-bot system?
The scraper is maintained, and because it reads Facebook's embedded JSON and structured timeline feed rather than rendered HTML, a visual redesign generally does not affect it. The parts most exposed to change are the bootstrap step — the Page's numeric id and the timeline operation id are rediscovered from the live page and its JavaScript bundles on every run, which is exactly what makes them resilient to renaming — and the anti-bot layer, handled by residential egress, session rotation and backoff. Your field names and types do not change on your end.
Can I use Facebook Pages & Recent Posts Scraper without managing proxies or browser infrastructure?
Yes. There is no browser to run — the Actor talks to Facebook over plain HTTP with a full desktop Chrome header set, so there is no Chromium to provision. Proxying is automatic and not optional: Apify Residential is requested for you, with a fresh session per Page and rotation on block signals. You never create a proxy account or rotate an IP. Note that it does not solve CAPTCHAs; it rotates away from them and backs off.
Which Facebook Page fields work best for AI training data and RAG indexing?
For RAG indexing: intro and title describe what the Page is, and each post's text is the primary document body — chunk per post, key on parentPageId, and cite postUrl. For training data: followers, likes, reactionsTotal, commentsCount, sharesCount, engagementTotal and publishedAtUnix are the most structurally consistent values across records, since the post row's 21 keys are always present and always the same type. All values come back as typed primitives, arrays or plain nested objects, so no normalization pass is needed before indexing — and if your index does not need identity, drop the author object and the contact fields before you store anything.
🔗 Related scrapers
| Scraper Name | What it extracts |
|---|---|
| Facebook Posts Scraper | Deep post extraction from a single Page or personal profile timeline — flat post rows with media arrays, feedback ids and optional video transcripts, much higher per-target post limits, but no Page profile record. Use it when posts are the whole job; use this Actor when you also want the Page's category, followers and contact block, or cadence metrics |
| Facebook Group Post Scraper | Posts, comments and engagement from public Facebook groups — content a Page timeline never contains |
| Facebook Hashtag Search Scraper | Posts and reels found by hashtag, with reactions and attachments |
| Facebook Video Search Scraper | Public video results by keyword — URL, caption, thumbnail, duration, creator |
| Facebook Groups Search Scraper | Groups by keyword — id, name, privacy, member count, recent activity |
| Instagram Profile Scraper | Instagram profile data, for following the instagram link on a Page row cross-platform |
💬 Your feedback
Found a bug, or need a field that is in Facebook's Page payload but not in the output? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the target Page URL are the fastest to reproduce and fix.