Facebook Posts Scraper
Pricing
$2.00 / 1,000 post results
Facebook Posts Scraper
Scrape public Facebook posts from pages and profiles, or discover posts by keyword. One normalized row per post: text, author, timestamp, media, reactions with a per-reaction breakdown, comment and share counts, and outbound links. No Facebook login, cookies or session ID needed.
Pricing
$2.00 / 1,000 post results
Rating
0.0
(0)
Developer
Delowar Munna
Maintained by CommunityActor stats
0
Bookmarked
5
Total users
2
Monthly active users
6 days ago
Last modified
Categories
Share

Scrape public Facebook posts from pages and profiles, or discover posts by keyword. Get clean post text, author, timestamps, media, reactions with a per-reaction breakdown, comment and share counts, and outbound links. One global result limit keeps multi-source runs predictable, and you never need to provide Facebook login credentials, cookies or a session ID.
Quick start
- Put one or more Facebook URLs in Facebook URLs — a post link, or a page/profile whose recent posts you want.
- Set Maximum posts.
- Leave Proxy configuration on Apify Proxy (Datacenter) — it keeps a multi-page run from being blocked. It will not give you deeper timeline history; see Why depth is limited.
- Run.
{"startUrls": ["https://www.facebook.com/nasa"],"maxResults": 100,"sort": "latest"}
Every run also writes a RUN_SUMMARY record to the key-value store with counts, stop reason and
diagnostics, and a RETRY_INPUT record you can re-run as-is if anything was blocked.
Two input modes
Post URLs — exact, reliable, one request each
Paste post links directly. Every URL shape Facebook uses is accepted, including the long search-engine form with the post text in the path:
https://www.facebook.com/NASA/posts/1617674169727953https://www.facebook.com/NASA/posts/pfbid0p9U45uPC39zamcxn3YC…https://www.facebook.com/NASA/posts/hello-moon-its-great-to-be-back/1496561611839210/https://www.facebook.com/copperkettleyqr/videos/1399396352119095/https://www.facebook.com/reel/1234567890123456https://www.facebook.com/photo/?fbid=1617674156394621https://www.facebook.com/permalink.php?story_fbid=…&id=…
Page and profile URLs — the current post from each
https://www.facebook.com/nasa a page by handlenasa a bare handle works toohttps://www.facebook.com/profile.php?id=100090722241114https://www.facebook.com/people/Central-Pub/100090722241114/
Personal profiles work exactly like pages. Facebook groups do not — see What this Actor cannot do.
Keyword discovery
Put keywords in Search queries to find posts about a topic. Facebook publishes no logged-out search of its own, so discovery searches the open web for indexed Facebook posts. Roughly half of what a keyword returns is Facebook pages rather than individual posts; the run reports how many, and you can paste those into Facebook URLs to pick up the current post from each.
Add Locations to turn one keyword into several searches ("artemis mission" + "Australia").
That returns more than searching one keyword more deeply.
Sample inputs
Three runs that cover the shapes people actually use. All three work in the Console and the API unchanged.
1 — Brand monitoring. The current post from every page you track. This is what the Actor is best at: each page contributes the post Facebook renders on it, so the result count scales with the number of pages, not with how deep you ask.
{"startUrls": ["https://www.facebook.com/nasa","https://www.facebook.com/BurgerKing","https://www.facebook.com/rolex","https://www.facebook.com/starbucks"],"maxResults": 200,"sort": "latest","proxyConfiguration": { "useApifyProxy": true }}
2 — Post URLs, the volume path. Permalinks are one request each, are never rate-limited, and return the complete post. Use this shape when you want a large, reliable run.
{"startUrls": ["https://www.facebook.com/NASA/posts/1617674169727953","https://www.facebook.com/NASA/posts/hello-moon-its-great-to-be-back/1496561611839210/","https://www.facebook.com/copperkettleyqr/videos/1399396352119095/","https://www.facebook.com/photo/?fbid=1617674156394621"],"maxResults": 500,"includeUnavailableRows": false,"proxyConfiguration": { "useApifyProxy": true }}
3 — Keyword discovery with filters. Finds indexed posts about a topic, then narrows them. Filtering happens after collection — a filtered-out post is never charged.
{"searchQueries": ["artemis mission", "lunar lander"],"locations": ["United States", "Australia"],"maxResults": 300,"dateFrom": "2026-08-01","minReactions": 50,"mediaTypes": ["photo", "video", "reel"],"sourceTypes": ["page", "post"],"proxyConfiguration": { "useApifyProxy": true }}
For a scheduled job, add skipPostIds or skipUrls from the previous run's RUN_SUMMARY so each
run returns only what is new — see Incremental runs.
⚠️ Why depth is limited without a login
This is the single most important thing to know before you set Maximum posts, and it is a property of Facebook rather than of this Actor.
A logged-out Facebook page renders exactly one post in its HTML. Everything after that comes from the same timeline endpoint the page's own JavaScript uses. That endpoint does answer logged-out requests — but it is rate-limited per IP address, and it refuses Apify's addresses on the first call. Measured across four runs on Apify Proxy spanning eight days: 25 timeline requests, 25 refused — every one on the first call of a fresh session — while every page request from those same addresses returned in full.
So plan on one post per page URL. Everything else about that post is complete — text, author, timestamp, media, reactions with the per-reaction breakdown, comment and share counts, links.
| You want | Do this |
|---|---|
| Volume | Paste post URLs. One request each, never rate-limited, complete data. This is the path to use for a large run. |
| Recent posts across many pages | List many pages. Each contributes the post Facebook renders on it — so 200 pages gives you 200 current posts. |
| Deep history from one page | Not available on Apify's addresses. Your own residential proxy under Custom proxy URLs does get a small allowance; otherwise run on a schedule and use Skip these post IDs to build up over time. |
Every run reports exactly where its posts came from, in the log and in RUN_SUMMARY.economics.feed:
Posts emitted: 16from post URLs: 6 ← permalinks you supplied or search foundfrom page HTML: 10 ← the one post each page rendersfrom feed paging: 0 ← the timeline endpoint (refused on Apify addresses)
Running without a proxy sends every request from one address, which raises the chance of Facebook blocking the run as a whole. It does not cost you timeline depth — there is none to lose on Apify addresses either way. The run warns when you do this.
Output
One row per unique post. Unknown values are null — never invented, never silently zero.

{"recordType": "facebook_post","postId": "1617674169727953","postUrl": "https://www.facebook.com/NASA/posts/pfbid0p9U45uPC39zamcxn3YC…","source": {"sourceType": "page","sourceId": "100044561550831","sourceUrl": "https://www.facebook.com/nasa","inputType": "startUrl","query": null,"queriesMatched": [],"location": null,"locationsMatched": [],"searchProvider": null,"detailSource": "facebook-page-html"},"author": {"id": "100044561550831","name": "NASA - National Aeronautics and Space Administration","url": "https://www.facebook.com/NASA","username": "NASA","profilePictureUrl": "https://scontent…","isVerified": true},"publishedAt": "2026-08-31T21:57:23.000Z","text": "Welcome to the Artemis Accords, Republic of Türkiye 🇹🇷…","postType": "photo","isPinned": null,"permalink": "https://www.facebook.com/NASA/posts/pfbid0p9U45uPC39zamcxn3YC…","externalLinks": ["https://www.nasa.gov/organizations/oiir/artemis-accords/…"],"media": [{"type": "photo","url": "https://www.facebook.com/photo/?fbid=1617674156394621&set=a.416661013162614","thumbnailUrl": "https://scontent…","durationSeconds": null,"width": 843,"height": 529,"altText": "Turkish Minister of Industry and Technology, Mehmet Fatih Kacır, 3rd from left, signs…","id": "1617674156394621"}],"engagement": {"reactions": 621,"comments": 33,"shares": 68,"reactionBreakdown": { "like": 490, "love": 109, "care": 10, "haha": 8, "wow": 3, "angry": 1 }},"video": null,"sharedPost": null,"scrapedAt": "2026-09-01T05:39:18.647Z","unavailableReason": null}
postType is one of photo, video, reel, link, share or text.
sharedPost is filled in when a post reshares another one. Its engagement stays the sharer's
own — a share with 10,000 reactions is not credited with the original's 640,000.
media[].altText is Facebook's own accessibility caption. On photo-only posts it is often the
only textual description the post carries.
Dataset views
Four tabs over the same rows — pick the one that matches what you are doing, or export the full record above. Every sample below is a real row from a live run.
Posts — the default. Everything you normally want, in reading order.
{"postUrl": "https://www.facebook.com/NASA/posts/pfbid0hSgbekvXSQGQJDibHXr71B5kh…","publishedAt": "2026-09-08T16:27:43.000Z","author": {"id": "100044561550831","name": "NASA - National Aeronautics and Space Administration","url": "https://www.facebook.com/NASA","username": "NASA","profilePictureUrl": "https://scontent…","isVerified": true},"text": "Hey, neighbor\n\nDid you know our home galaxy — the Milky Way — has smaller galaxies that orbit around it?…","postType": "photo","engagement": { "reactions": 4347, "comments": 111, "shares": 444, "reactionBreakdown": { "like": 2922, "love": 1251, "wow": 94, "care": 67, "haha": 11, "sad": 2 } },"media": [{ "type": "photo", "url": "https://www.facebook.com/photo/?fbid=1624398355722201&set=a.416661013162614", "thumbnailUrl": "https://scontent…", "width": 640, "height": 509, "altText": "Blue and orange stars shine through a wispy bubble of gas…", "id": "1624398355722201" }],"externalLinks": [],"source": { "sourceType": "page", "sourceId": "100044561550831", "sourceUrl": "https://www.facebook.com/nasa", "inputType": "startUrl", "detailSource": "facebook-page-html" },"postId": "1624398375722199","scrapedAt": "2026-09-09T04:43:15.393Z"}
Engagement — spreadsheet-friendly. Drops media and source so the numbers line up in a CSV, and
keeps text so each row is still identifiable.
{"postUrl": "https://www.facebook.com/Starbucks/posts/pfbid02PmrZNwEN2arNsnNxYzbT9c4…","publishedAt": "2026-09-08T13:31:37.000Z","author": { "id": "100064487329582", "name": "Starbucks", "username": "Starbucks", "isVerified": true },"postType": "text","engagement": {"reactions": 5633,"comments": 165,"shares": 695,"reactionBreakdown": { "like": 3662, "haha": 1707, "love": 228, "care": 23, "sad": 7, "wow": 5, "angry": 1 }},"text": "me pretending one coffee is enough to recover from a 3-day weekend"}
Media — posts that carry attachments, with the video block alongside. video.views and
video.transcript are always null on this surface; see
What this Actor cannot do.
{"postUrl": "https://www.facebook.com/reel/2557680831370435/","publishedAt": "2026-09-08T23:43:10.000Z","author": { "id": "100064916943021", "name": "The Cheesecake Factory", "username": "thecheesecakefactory", "isVerified": true },"postType": "reel","media": [{"type": "video","url": "https://www.facebook.com/reel/2557680831370435/","thumbnailUrl": "https://scontent…","durationSeconds": 12,"width": null,"height": null,"altText": null,"id": "2557680831370435"}],"video": { "durationSeconds": 12, "views": null, "transcript": null },"externalLinks": []}
Unavailable — empty by default. It only fills when you switch on Include unavailable posts as
empty rows; otherwise these are reported in the log, RUN_SUMMARY.notAvailableByReason and
RETRY_INPUT instead of cluttering your dataset. Shape:
{"postUrl": "https://www.facebook.com/SomePage/posts/1234567890123456","postId": "1234567890123456","unavailableReason": "login-wall","source": { "sourceType": "post", "inputType": "startUrl", "detailSource": null },"scrapedAt": "2026-09-09T04:43:15.393Z"}
unavailableReason is one of login-wall, soft-block, rate-limited, blocked, not-found,
group-not-readable-logged-out, timeout, proxy-error or request-failed. The first four mean
the post exists and a later run usually gets it — those are the ones written into RETRY_INPUT.
not-found means deleted or not public, and re-running will not recover it. Unavailable rows are
never charged, whether or not you display them.
What this Actor cannot do
Stated plainly, because each of these was tested rather than assumed, and a scraper that quietly returns nothing is worse than one that says why.
| Not available | Why |
|---|---|
| Comment text | Logged-out Facebook publishes the comment count but no comment bodies — the permalink page carries can_viewer_comment: false, a comments-disabled notice and zero comment records. Comment counts are returned. |
| Video transcripts / captions | Every video attachment served logged out carries an empty captions list and no captions URL. |
| Video view counts | Not published on this surface. video.views is permanently null rather than guessed from reactions. |
| Facebook groups | A logged-out request for any group returns an empty shell with no posts at all — public groups included. Group URLs are rejected with that reason rather than accepted and silently returning nothing. |
| Pinned-post flag | The timeline Facebook serves logged-out explicitly omits pinned posts, so isPinned is null rather than a guess. |
| Deep history from one page | Facebook's timeline endpoint refuses Apify's addresses outright — measured, 25 of 25 across four runs. See Why depth is limited. |
| Private, friends-only or gated content | Out of scope by design. This Actor never asks for credentials and cannot see anything a logged-out visitor cannot. |
The only way to get comments, transcripts or deep history is a logged-in session. This Actor does not ask for your Facebook account, and will not.
Filters
All filters run after posts are collected, because Facebook offers no way to search by engagement or media type. A filtered-out post is never charged, so narrowing costs you nothing.
- Minimum reactions / comments / shares — posts where Facebook published no figure are kept. An unknown count is not a low one.
- Media types —
photo,video,reel,link,share,text. A reel also matchesvideo. - Posted on or after / before — both bounds inclusive. A plain date as
dateTocovers the whole of that day. Posts with no timestamp are dropped when a date bound is set, and counted separately asdateUnknownso you can tell that apart from an over-narrow range. - Only / exclude these authors — matched on name, handle or numeric ID. If a post matches both lists it is excluded.
- Only these source types —
page,profile,post.
Incremental runs
For a scheduled job that should only return what is new:
- Skip these post IDs / Skip these post URLs — anything already collected.
- Posted on or after — plus Stop a timeline after N posts older than your start date, which stops paging a page once it has gone past your window instead of fetching history you filtered out.
RUN_SUMMARY gives you the IDs to carry forward; RETRY_INPUT gives you a ready-to-run input
containing only what was blocked or rate-limited — never posts that are simply gone, because
re-running those spends a request every time to confirm what is already known.
Pricing
Pay per result. You are charged once per unique post returned with data, and never for:
- duplicates — a post found by two searches, or supplied directly and read from a timeline, is charged once;
- posts removed by your filters;
- posts on your skip lists;
- posts that were deleted, not public, blocked or rate-limited.
By default, posts that could not be read are left out of the dataset entirely rather than returned
as blank rows. The run log, the run status, RUN_SUMMARY and RETRY_INPUT all report them, and a
CSV full of empty rows imports junk into whatever you load it into. Switch on Include unavailable
posts as empty rows if you want them anyway — they are free either way.
There is no start fee, no subscription and no monthly rental — you pay only for posts you actually receive, so a run that returns nothing costs you nothing.
This README deliberately quotes no figures. Live prices are on the Actor's Pricing tab, which is the only place that cannot go stale.
🚦 Proxy policy
Use Apify Datacenter proxy — it is the default, and it is what keeps a multi-page run from being blocked as a whole. It does NOT unlock timeline depth: Facebook's feed endpoint refuses Apify addresses on the first call, measured. Depth needs a different class of address, which means your own provider under Custom proxy URLs.
Apify Residential proxy is not supported. The run fails at startup if apifyProxyGroups
includes RESIDENTIAL. Two reasons: residential bandwidth is billed to the Actor developer rather
than to your run, at several times what the results are priced at; and it buys nothing here, because
Facebook gates on the request fingerprint rather than the IP, and a per-address call budget is not
raised by a more expensive address.
If you genuinely need residential routing, supply your own provider under Custom proxy URLs — that traffic goes through your account and is honoured in full:
http://user:pass@proxy.iproyal.com:12321http://user:pass@proxy.brightdata.com:22225http://user:pass@proxy.oxylabs.io:7777
API example
curl -X POST "https://api.apify.com/v2/acts/coregent~facebook-posts-scraper/runs?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"startUrls": ["https://www.facebook.com/nasa","https://www.facebook.com/NASA/posts/1498937668268271"],"searchQueries": ["artemis mission"],"maxResults": 200,"dateFrom": "2026-08-01","minReactions": 10,"mediaTypes": ["photo", "video"],"proxyConfiguration": { "useApifyProxy": true }}'
The Console and the API take the same input. maxResults is global and deterministic, and you never
need to understand a provider cursor.
Troubleshooting
"I only got one post per page." That is the expected result on Apify's addresses — Facebook's timeline endpoint refuses them, so each page contributes the single post it renders. Switching Apify Proxy on will not change it. For volume, supply post URLs directly, or list more pages. See Why depth is limited.
"My keyword search returned pages, not posts." Expected — a web search for posts turns up profile pages too. The run reports how many. Put those page URLs in Facebook URLs to collect their timelines.
"Some posts came back unavailable."
Check RUN_SUMMARY.notAvailableByReason. login-wall and soft-block mean Facebook withheld a
post that exists — those are in RETRY_INPUT, and a later run usually gets them. not-found means
the post is deleted or not public, and re-running will not recover it.
"The run failed saying there was nothing to scrape." You supplied only keywords and keyword discovery is not configured on this Actor, or the search found no posts. Supply Facebook URLs directly, or broaden the keywords.
"Reaction counts changed between runs." They are live figures. A popular post can gain thousands of reactions in an hour.
Privacy and platform disclaimer
This Actor processes publicly accessible Facebook data only — what any logged-out visitor can see. It never requests, stores or uses Facebook credentials, cookies, session IDs, access tokens or browser profiles, and it cannot reach private, friends-only, group or otherwise gated content.
You are responsible for complying with applicable law, Meta's terms, and your own privacy and data-protection obligations, including how you store and use any personal data in the results.
