Facebook Posts Scraper — Facebook Page Posts & Group Posts avatar

Facebook Posts Scraper — Facebook Page Posts & Group Posts

Pricing

from $2.00 / 1,000 posts

Go to Apify Store
Facebook Posts Scraper — Facebook Page Posts & Group Posts

Facebook Posts Scraper — Facebook Page Posts & Group Posts

Facebook posts scraper for pages and public groups: paste a link and get posts as rows — text, date, ten-way reactions, comment count, image and a playable MP4 link on video posts. Optional page details: followers, category, email, phone, address, hours, ads. No login, exact limit, no start fee.

Pricing

from $2.00 / 1,000 posts

Rating

0.0

(0)

Developer

Steadyfetch Team

Steadyfetch Team

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

21 hours ago

Last modified

Share

Click Start with nothing set and the run returns the built-in sample rows — a three-post sample from one public brand page with page details off, so you see the exact output shape before you spend anything: nothing is collected from Facebook, and there is no result fee. Then paste a Facebook page and get its posts, paste a public group and get its posts — same run, same columns. One JSON row per post: the post ID and permalink, the page or group it is in, who posted it, the text, the date, the post type, one image, a playable MP4 link with the moment it expires on video posts, the reaction total and its ten-way breakdown, the comment count, and the top comments Facebook serves with the post. From $2.00/1,000 posts on the Business plan ($5.00 on the Apify free plan), platform usage included, no start fee.

Using an AI agent? Pin this actor in Apify's MCP server with one link: https://mcp.apify.com?tools=steadyfetch/facebook-posts-scraper

  • Actor id: steadyfetch/facebook-posts-scraper
  • Input: { "pages": ["ladbible"] } — the one field you have to set. Or { "groups": ["https://www.facebook.com/groups/dogspotting"] } for a public group, or startUrls with both mixed.
  • Cap the bill: set maxTotalChargeUsd on the run (a run option, not Actor input), e.g. 0.50 — the run stops when it reaches it.
  • Your cap is the cap. resultsLimit (default 20) is exact and per page or group; maxItems (default 1000) caps the whole run. If your input carries limit, count or maxPosts instead — the names other Facebook post scrapers use — the smallest of them is read as maxItems and one uncharged note row says so.

Unofficial. Not affiliated with, endorsed by, or sponsored by Facebook or Meta Platforms, Inc.

What a row does NOT carry

Read this before the feature list, because it is the part a listing usually leaves for the Issues tab. Two numbers are not in the feed a logged-out reader of these pages and groups is shown, so this actor does not sell them:

  • shareCount is always null. How many times a post was shared is not published on the surface this actor reads.
  • videoViewCount is always null on every video post. The key is served, and its value is empty every time.

Both ship as explicit null columns rather than as missing ones, so a sheet or a fixed-schema table never breaks and you never have to wonder whether a zero was really a zero. This actor will not promise a column it cannot fill. Everything else on the row is a real value, or an honest null with a reason beside it.

What you put in

InputWhat it takes
pagesFacebook pages, one per line — a name (ladbible), @ladbible, a full page link, or a profile.php?id=… link. Posts come back newest first.
groupsPublic groups: a group link, a /groups/<numeric id> link, or a bare numeric group id. Same columns as a page, with source: group.
startUrlsPage links and group links in one list, for moving over from another scraper. Each line is sent to the right door by its own shape, before anything is bought.
resultsLimitPosts per page and per group, exact. Default 20 — three targets at 20 collect up to 60. Set it to 0 with page details on for a details-only run.
maxItemsThe ceiling on posts for the whole run. Default 1000. It also answers to limit, count and maxPosts, and the smallest one present binds — one uncharged note row says which name was read.
includePageDetailsOne extra row per PAGE: followers, likes, talking-about, category, intro, creation date, website and links, email, phone, address, opening hours, price range, services, rating, ad status, verification and Instagram handle. Off by default, and groups never get one.
newerThan · olderThanA window on the post's own publish date — 2026-09-01, an ISO timestamp, or 7 days. The walk is newest first, so newerThan ends it at the boundary and buys nothing past it. No filter fee.
maxRunSecondsA wall clock on the run. Default 1800. The run stops cleanly and says what is left instead of being killed.
includeSeen · resumeFromDatasetIdRe-run the same pages and groups without paying again for posts this account already has.

What you get

One row per post, from a page and from a public group alike.

ColumnWhat it holds
postId · urlThe post's own ID and its permalink, on both doors.
sourcepage or group — which door the row came through.
pageName · pageId · groupName · groupIdWhere the post lives.
authorName · authorIdWho posted it.
textWhat was written.
createdAtWhen it was posted, ISO-8601 UTC, from Facebook's own publish time.
postTypevideo, image or text.
imageUrlThe post's image — one image per post, which is what the feed carries.
videoUrl · videoSdUrl · videoUrlExpiresAtA directly playable MP4 link in HD and SD on a video post, and the moment Facebook's signature on it stops working.
thumbnailUrlThe video's cover frame.
reactionCountReactions on the post, all kinds together.
reactionsThe ten-way breakdown, one number each: like, love, care, haha, wow, sad, anger, thankful, pride, confused.
commentCountHow many comments sit under the post.
shareCount · videoViewCountAlways null — see the block above.
topComments0 to 2 comments, exactly as the feed serves them, and never charged.
repeatThis account already had this post and asked for it again.
charged · status · statusReasonWhether this row was billed and what happened, so your invoice reconciles from the dataset itself.

The ten reaction columns are the reason most people are here: a post with 40 anger and 3 like is a different signal from 43 reactions, and you cannot get that from a total.

Column names are ours and they stay put. The wire is read in one place and renamed once, so a Facebook rename does not rename your columns, and a fixed-schema table never breaks on a new row.

Price

Two charged events, and each lands only on a row that was actually delivered. A row is charged as either a Post or a Page details row, never both.

EventApify free planBronzeSilverGold and above
Post — one page or group post$0.005$0.0035$0.0026$0.002
Page details — one page row, only with includePageDetails$0.012$0.008$0.006$0.005

The store header is the cheapest tier of the primary event: from $2.00/1,000 posts. Platform usage is included in both prices — there is no separate compute bill on top. No start fee, no filter fee, and no result fee on a miss. A date window costs nothing, and a run that finds nothing to deliver charges nothing. Set maxTotalChargeUsd on the run and it is a hard ceiling: the run stops cleanly under it, delivers everything already in hand, and the last row says what is left.

What is never charged

Every run closes its own books, and each of these ships as a labelled, uncharged row rather than as silence:

  • page_not_found — Facebook says there is no such page. A definitive answer about your link.
  • page_private — the page is real and Facebook shows its posts to logged-in users only.
  • page_no_posts — the page is real and its own feed ended the walk short of what you asked for: it ran out of posts, or your own newerThan / olderThan window closed the walk. Any posts already delivered say what each was charged, so the no-fee statement covers only what did not arrive.
  • group_not_found — Facebook says there is no such group.
  • group_private — the group is real and Facebook shows its posts to members only.
  • group_no_posts — the same two endings on the group door: the group is real and public, and its own feed ran out or your window closed the walk.
  • skipped.page_cap — a bound of THIS ACTOR'S own, never a verdict about your page or group: the feed was still serving and the run had read the 40 feed pages it buys for a single input. Split fewer targets across more runs to go deeper.
  • skipped.all_repeat — the other bound of ours: two feed pages running that added no new post, so the run stopped paying to look. On a repeat run that usually means you already have everything there is.
  • feed_unavailable — the read did not go through. Temporary, says nothing about your target, and a re-run is the fix. When it lands part-way through an input, the row opens with the posts already delivered and charged, and the no-fee statement covers only the rest.
  • Posts dropped by your own newerThan / olderThan window, posts past your own resultsLimit or maxItems, posts the run clock or the cost cap did not reach, and posts this account already had.
  • Starting a run. There is no start fee, per post or per run.

The last row of every run is a receipt: delivered, asked for, skipped, what stopped it, and the charged-event totals. A cost cap or a time limit ends the collecting, never the delivering — posts already in hand are always written out.

How often the data changes

A busy page posts hourly and an active group posts about daily, so the useful pattern here is the same targets again tomorrow rather than one deep run. Reactions and comment counts keep moving for roughly 72 hours after a post goes up, which is the window worth re-reading if you are measuring engagement. And videoUrl is a signed link that Facebook expires about four days out (videoUrlExpiresAt is on the row) — download the MP4 in the same week, or re-run for fresh links.

Re-running would otherwise re-buy the posts you already have, so it does not: every run remembers what it delivered to your account, and a later run skips those — not delivered again, not charged, and the slot goes to the next new post instead, so you still get the number you asked for. That makes a daily watch on the same pages cost only what is new. The memory is a key-value store in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission (or Actor runs set to Full access); without it the run says so on its status line and cannot skip what you already have (Write is what lets the run remember what it delivered; without it every run pays again). Turn on Include posts you already have to get them back anyway, marked repeat: true and still uncharged, and use Skip posts in this dataset (resumeFromDatasetId) to carry the same memory across accounts.

Honest caps

  • Public groups only. A private group returns an uncharged row — Facebook shows its posts to members only, with or without a login, so there is no setting that opens one. A group that does not exist is a different uncharged row, so "private" and "gone" are never the same answer.
  • Newest first, and that is the only order. There is no "top posts" order here, and no input that asks for one. A sort sent under any name comes back as one uncharged note row saying newest-first is what you get, never as a silent substitution.
  • One image per post, not a carousel — the feed carries a single image per post.
  • 0 to 2 top comments, exactly as the feed serves them, and never charged. They are a taste, not a thread; the thread is a different actor's job.
  • Facebook may change how any of this is served. If it does, the rows that fail are labelled and not charged.

Paste one of those into this actor and it comes back as an uncharged row naming the actor that takes it, rather than as a failed run.

Something not right?

Open the Issues tab on this actor with your run ID and the input you used, and it gets looked at. If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.