Facebook Pages & Recent Posts Scraper avatar

Facebook Pages & Recent Posts Scraper

Pricing

$19.99/month + usage

Go to Apify Store
Facebook Pages & Recent Posts Scraper

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

Scraper Engine

Maintained by Community

Actor stats

1

Bookmarked

297

Total users

1

Monthly active users

5 days ago

Last modified

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 CategoryPublicly AvailableGated 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 readsA dedicated comments scraper
Identities of the people who reacted to or shared a postLogged-in Facebook account
Page insights — reach, impressions, click-throughsPage 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 maxPostsPerPage count 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 onlyPostsNewerThan set, 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 NameDescription
typeAlways "page" on this row shape
isChildAlways false on page rows
facebookUrlThe normalised Page URL the row was collected from
pageUrlSame value as facebookUrl, kept as a compatibility alias
titleThe Page's display name
pageNameFirst path segment of the URL — the Page slug, not the display name
pageIdFacebook's numeric id for the Page
facebookIdSame value as pageId, kept as a compatibility alias
categoriesArray of category names as Facebook lists them. [] when none was found
categoryFirst entry of categories, or null
introThe Page's intro / best-description text
about_meObject: text — the same intro string, in the legacy nested shape
infoArray built from the Page's meta description, typically the "N likes" fragment and the remainder
emailPublic email from the Page's intro card, when published
phonePublic phone number, when published
addressPublic street address, when published
addressUrlA Bing Maps search URL the Actor builds from address — not a link Facebook published
websitesArray of every outbound URL found on the Page — website, Google Maps link and Instagram, de-duplicated and with l.facebook.com redirects decoded
websiteBare domain of the first website found, e.g. copperkettleyqr.com
instagramArray of objects: username, url
alternativeSocialMediaThe linked Instagram URL as a plain string
servicesServices line from the Page's intro card
business_servicesSame value as services, kept as a compatibility alias
business_hoursHours line from the intro card, or an "Open now" / "Closed now" fragment
priceRangePrice band normalised to dollar symbols — a ££ Page is rewritten as $$
business_priceHuman-readable form: "Price Range · $$"
creation_datePage 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
pageAdLibraryObject: is_business_page_active, id — the ids you need for Meta Ad Library lookups
profilePictureUrlDirect CDN URL of the profile picture
coverPhotoUrlDirect CDN URL of the cover photo
profilePhotoA facebook.com/photo/?fbid=… link, reconstructed from the profile picture id
scrapedAtISO-8601 UTC timestamp of when the row was built
successtrue on a scraped Page, false on a Page that could not be fetched
errorOnly present when success is false — the failure message

Page audience, rating and posting-cadence fields

Field NameDescription
likesPage like count, parsed from the Page's meta description
followersFollower count as an integer, expanded from the compact string Facebook displays — 2.6K becomes 2600, so it inherits Facebook's own rounding
followingsHow many Pages this Page follows, as an integer, when exposed
ratingRecommendation summary text, e.g. "94% recommend (202 Reviews)"
ratingsSame value as rating, kept as a compatibility alias
ratingOverallThe recommendation percentage as an integer
ratingCountNumber of reviews behind that percentage
recentPostsOnly 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 NameDescription
typeAlways "post" on this row shape
isChildAlways true on post rows
parentPageIdNumeric id of the Page the post came from — the join key back to the page row
parentPageUrlThat Page's facebookUrl
parentPageNameThat Page's display name
postIdFacebook's numeric post identifier. null when Facebook did not expose one
postUrlPost permalink. null when absent
postTypeAttachment class: only ever "photo" or "video" — see the note below
textFull post message. null, not "", on a media-only post
publishedAtPublish time as an ISO-8601 UTC string, e.g. 2026-07-22T16:05:11Z
publishedAtUnixThe same moment as a Unix timestamp in seconds
reactionsTotalTotal reactions of every type, from Facebook's reaction_count
reactionsObject with all seven keys always present: like, love, care, haha, wow, sad, angry
commentsCountTotal comment count on the post
sharesCountTotal share count on the post
engagementTotalreactionsTotal + commentsCount + sharesCount, computed by the Actor
imageUrlDirect CDN URL of the post image. null on posts without one
videoUrlDirect playable video URL. null on posts without one
externalUrlOutbound link on a link-preview post. null otherwise
authorObject: id, name, url, profile_picture_url — empty strings when Facebook did not expose them
scrapedAtISO-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.

FeatureMeta Graph APIFacebook 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 administerNeeds an approved app and, per Meta's docs, the Page owner's permission✅ Any public Page URL
Page profile block and recent posts togetherSeparate edges and separate permission scopes✅ One run returns both row types
Quota and throttling termsGoverned by Meta's published platform limitsBounded by this Actor's own per-Page request budget
Page insights — reach, impressions✅ Available to Page admins❌ Not returned — not publicly visible
Output shapeMeta's versioned Graph schemaTwo 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.

  1. Open Facebook Pages & Recent Posts Scraper on Apify and click Try for free
  2. 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
  3. Raise Max pages to scan (maxPagesToScan) to at least the number of URLs you pasted — the default 10 silently cuts a longer list
  4. Switch on Fetch recent posts (fetchPosts) if you want post rows, and set Max posts per page (maxPostsPerPage)
  5. Optionally set Only posts newer than (onlyPostsNewerThan) to 2026-01-01 or 3 months to bound the feed by date
  6. 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, address and websites to qualify public business Pages before any outreach, dropping Pages with no published contact route.
  • 📊 A social media analyst benchmarking a competitor set enables fetchPosts and ranks Pages by recentPosts.postsPerWeek and avgGapDays, separating the accounts that publish constantly from the ones coasting on an old audience.
  • 😀 A brand manager watching sentiment reads reactions.angry and reactions.sad against reactions.love across a Page's newest posts, catching a negative shift that reactionsTotal alone would hide.
  • 🕐 A content strategist planning a calendar groups publishedAtUnix by weekday and hour across the collected posts and correlates each slot with engagementTotal to find when that audience actually responds.
  • 🤖 An AI engineer building a brand-monitoring agent indexes title, intro and every post's text into a vector store, keying each chunk on parentPageId and citing postUrl, 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.

ParameterRequiredTypeDescriptionExample Value
pageUrlsNoarrayFacebook 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"]
maxPagesToScanNointegerHow many Page URLs to process this run — counts Pages, not posts. Minimum 1, maximum 10000. Default 10.50
fetchPostsNobooleanWhen on, each Page also returns its recent posts as child rows and gains a recentPosts cadence object. Default false — Page data only.true
maxPostsPerPageNointegerCap on recent posts collected per Page. Minimum 1, maximum 5000. Default 25.100
onlyPostsNewerThanNostringKeep 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"
proxyConfigurationNoobjectAccepted 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:

  • proxyConfiguration is not an override. The value is read and then discarded: the Actor always requests an Apify Residential proxy. Selecting datacenter groups, supplying custom proxyUrls or 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.
  • maxPostsPerPage is 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 to 100, not to the schema default of 25.
  • onlyPostsNewerThan is validated even when fetchPosts is off. An unparseable value fails the whole run with Invalid onlyPostsNewerThan: … before any Page is fetched. Relative values need a unit word — 7 days, 2 weeks, 1 month, 1 year; a bare number such as 7 is rejected rather than ignored. When the filter is active, a post whose timestamp could not be read is dropped, not kept.
  • maxPostsPerPage and onlyPostsNewerThan do nothing while fetchPosts is false. 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 zeroed recentPosts object when fetchPosts is on. Every profile key is omitted, not nulled, so never index a page row blindly. One row_result event is charged per row pushed, failure rows included; the mirrored {runId}-posts copies are not charged again.
  • The exact filter for real data is success !== false. Do not filter on success === true: post rows carry no success key at all, so that expression would discard every post. In Python, [r for r in items if r.get("success") is not False].
  • success: true on 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.postsFetched is 0, and no post rows follow. Check recentPosts.postsFetched when 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 ApifyClient
client = 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:
continue
if 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.

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.

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.

Scraper NameWhat it extracts
Facebook Posts ScraperDeep 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 ScraperPosts, comments and engagement from public Facebook groups — content a Page timeline never contains
Facebook Hashtag Search ScraperPosts and reels found by hashtag, with reactions and attachments
Facebook Video Search ScraperPublic video results by keyword — URL, caption, thumbnail, duration, creator
Facebook Groups Search ScraperGroups by keyword — id, name, privacy, member count, recent activity
Instagram Profile ScraperInstagram 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.