# Facebook Groups Scraper - Facebook Group Posts, No Login (`hyperbach/facebook-groups-scraper`) Actor

Facebook groups scraper, no login: public group links, ids or names in, one row per post out. Text, time, reactions by type, comments, shares, the author's public name and link, media, the post link and the group's exact member count. Free date window. Find groups by keyword. Misses free.

- **URL**: https://apify.com/hyperbach/facebook-groups-scraper.md
- **Developed by:** [Hyperbach](https://apify.com/hyperbach) (community)
- **Categories:** Social media, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 4 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 posts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Facebook Groups Scraper - Facebook Group Posts, No Login

**Facebook group posts as data, logged out.** Give public group links (or a group's numeric id or name), or a single group post's link, and get **one row per post**: the whole text, the time to the second, the **exact reactions by type**, the comment and share counts, the **author's public name and link**, the photos and videos, the shared post, the post's link and its feedback id, and the group's **exact member count** on every row. Four orders (new posts, recent activity, top posts, listings), a **free date window**, filters by words and author, and up to 10 comments a post. Or give words and **find public groups** with their member count and privacy. Every input ends with a free row that says what was read and why it stopped. No login, no cookies, no browser, proxies handled for you.

**From $5.00 per 1,000 rows (`post`) on the Free plan, $4.00 on Starter; every row and add-on is priced under Pricing below.** A run start is $0.0045. The default input returns about 21 rows for about $0.1045. A private, missing or empty group is a free row that names its state, with the group's facts: you never pay for a group that gave no post.

### Tested head-to-head on 2026-11-05

The job: the newest 20 posts of one public Facebook group by its link (Self-Taught Programmers, the schema's example input), new posts first. Every Actor ran on it the same day from the same harness; a rival's cost is what a buyer account paid, this Actor's is its list price on the Free plan. Fields counts the filled fields of a typical row. The lane's #2 by users/30d (datahamster/facebook-groups-search) finds groups by keyword and reads no group's posts: left out; the next group-posts seller by users/30d is curious_coder/facebook-post-scraper. Re-measured monthly.

| Actor | Rows | First row | Run time | Cost | Fields | Failures |
|---|---|---|---|---|---|---|
| **this Actor** | 20 | 11 s | 18 s | $0.10 | 42 | none |
| `apify/facebook-groups-scraper` | 20 | 30 s | 39 s | $0.081 | 19 | none |
| `curious_coder/facebook-post-scraper` | 20 | 5.4 s | 9.8 s | $0.021 | 12 | none |

### Why this Facebook groups scraper

- **No charge for an empty, private or missing group.** A private group, a group id that does not exist, an empty group or a link that names no group gets one free row with its `status` (`private`, `not_found`, `invalid_input`) and the reason, checked on a second connection before it says `not_found`. The lane's buyers report being charged for empty items; here only a delivered post or a found group is charged.
- **The date window is free.** `onlyPostsNewerThan` and `onlyPostsOlderThan` cost nothing extra (the lane leader prices its date filter as an add-on), and every row is checked against the window before it is delivered: a post outside it is never charged. In the new-posts order the walk stops at the window's older end.
- **The group's facts on every row.** One About page a group gives the **exact member count** ("373,967 total members", never the rounded "374K"), the privacy, the posts of the last day and the last month, the new-members line, the created date, the description and the rules on the group's row, and the member count and privacy on every post row.
- **No silent short lists.** The walk goes to Facebook's own end of the list and asks the end again on a second connection before it calls the group COMPLETE; every group's row says COMPLETE, CAPPED, DATE_LIMIT or PARTIAL with the reason, how many posts were read, and the cursor where it stopped.
- **The author's link and the post's feedback id.** The author's public profile link where Facebook links it in the group, and the post's `feedback_id`, which our Facebook Comments Scraper takes to read every comment and reply of the post.
- **Find groups by keyword.** `searchQueries` lists public groups for your words, each with its exact member count, privacy and activity, in a web index's order (Facebook's own group search is closed to a logged-out reader); filter by privacy and member range for free.
- **Speed, measured.** On 16 runs of version 0.1.7 on Apify (2026-11-05, 512 MB: the deploy gate, five cost runs, a 300-post walk and the head-to-head run below) the first post came 2.9 to 9.7 seconds after the Actor started (each group's About page is read first, for its exact member count); a group's newest 20 posts were out by 10.7 to 18.5 seconds, 50 posts by 22.5, and one group's 300 posts by 169 seconds. The head-to-head card counts from the run's start request, the container's start included: 11 seconds to the first row on its run, 7.3 by the Actor's own clock.

### Who it's for

- **Community and market researchers** — the posts of the groups where a topic is discussed, with their reactions, comments and the authors' public names, inside a date window, for a corpus or a trend report.
- **Brand, product and support teams** — the newest posts of a list of groups each morning (`onlyPostsNewerThan` set to `1 day`), filtered by a product or brand name with `textContains`.
- **Lead and partnership teams** — groups about a keyword with their exact member counts and activity (`searchQueries`, `minMembers`), then their newest posts.

### Quick start

**A group's newest 20 posts**

```json
{
  "startUrls": [
    "https://www.facebook.com/groups/selftaughtprogrammers/"
  ],
  "resultsLimit": 20
}
```

**Two groups, the last week's posts, with comments**

```json
{
  "startUrls": [
    "https://www.facebook.com/groups/selftaughtprogrammers/",
    "https://www.facebook.com/groups/874728723021553/"
  ],
  "resultsLimit": 100,
  "onlyPostsNewerThan": "7 days",
  "maxCommentsPerPost": 5
}
```

**Find groups by keyword**

```json
{
  "searchQueries": [
    "maths formulas"
  ],
  "maxGroupsPerQuery": 10,
  "groupPrivacy": "public"
}
```

### Switching from `apify/facebook-groups-scraper`?

Your input works as it is. Each name in the left column is read as this Actor's own field and the run log says so (`read <theirs> as <ours>`); a name marked *not read* is ignored with a note. A value in the other Actor's shape, an object list or its own date form, is read too.

| apify/facebook-groups-scraper | this Actor | what to know |
|---|---|---|
| `startUrls` | `startUrls` | The same name and shape: group links as strings or as `{"url": …}` items; a group's numeric id or name works too. |
| `resultsLimit` | `resultsLimit` | The same name: posts per group. Unset here means 20; 0 means every post. |
| `viewOption` | `viewOption` | The same name and the same four values: CHRONOLOGICAL, RECENT_ACTIVITY, TOP_POSTS, CHRONOLOGICAL_LISTINGS. |
| `onlyPostsNewerThan` | `onlyPostsNewerThan` | The same name and forms: a date, a time or a span such as `7 days`. |

**What differs:**

- `resultsLimit` is 20 when you do not set it; 0 reads every post the group's feed serves.
- The date window (`onlyPostsNewerThan`, and `onlyPostsOlderThan`, which the leader does not have) is free here; the leader prices its date filter as an add-on.
- The output columns are snake_case (`likes`, `comments`, `shares`, `group_name`, `author_name`, `media`) where the leader's are camelCase (`likesCount`, `commentsCount`, `sharesCount`, `groupTitle`, `user.name`, `attachments`); each field's description names the leader's key.
- Each input gets a free summary row (`record_type` = `target`) beside its post rows; filter on `record_type` = `post` for the posts alone.
- A private, missing or empty group is a free row here, never a charged empty item.

### Output

One row per post (and one free summary row for every input):

| field | meaning |
|---|---|
| `record_type` | `post` on a post row (charged); `group` on a group a search found (charged); `target` on an input's free summary row (a group, a post link or a search you gave). |
| `status` | `ok`, or why an input gave no row: `not_found` (no group or post by that link, id or name: Facebook answers an empty record or "This content isn't available" on two connections), `private` (a private group: only its members can see its posts; the row still carries the group's facts), `invalid_input` (a value that is not a Facebook group, group post or search, an empty value), `duplicate` (the same group as another value of the run, such as a group's name and its numeric id: its posts are on that value's rows), `failed` (Facebook refused every connection tried; run it again later), `not_read` (maxItems, your charge limit or the time limit came first). |
| `input` | The value you gave that this row answers (the leader's `inputUrl`). |
| `post_id` | Facebook's id of the post (the leader's `legacyId`). |
| `url` | The post's own link, `https://www.facebook.com/groups/<group>/posts/<id>/` (the leader's `url`). |
| `feedback_id` | Facebook's id of the post's reactions and comments (the leader's `feedbackId`). Give it to our Facebook Comments Scraper for every comment and reply of the post. |
| `post_type` | `photo`, `album`, `video`, `reel`, `link` (a link with its preview), `shared` (a shared post), `listing` (a buy-and-sell listing, `listing` has its fields), `text`, `event` or `other`, read from the post's attachment, never guessed from the text. |
| `is_video` | True when the post carries a video or a reel. |
| `is_share` | True when the post shares another post (`shared_post` has it). |
| `time` | When the post was published, UTC, to the second (the leader's `time`). |
| `timestamp` | The same time as Unix seconds. |
| `video_publish_time` | When the video of a video post was uploaded, UTC (usually a minute before `time`). Null on other posts. |
| `text` | The post's whole text, never cut (the leader's `text`); empty when the post has none (a photo alone, a share without words). |
| `text_length` | The text's length in characters. |
| `hashtags` | The hashtags of the text, without `#`, in order, each once. |
| `mentions` | The people, pages, groups and apps the text tags, as Facebook publishes them with the post: `type`, `id`, `name`, `url` (the leader's `textReferences`). |
| `links_in_text` | The links written in the text, unwrapped from Facebook's redirect. |
| `author_id` | The id of the member or page that posted (the leader's `user.id`). |
| `author_name` | The author's public name as Facebook shows it (the leader's `user.name`). |
| `author_url` | The author's public profile link where Facebook links it in the group (63 % of posts on ordinary groups, stage 2), never built from the id; null otherwise. |
| `author_type` | Facebook's own type for the author: `user` (a member; also a page in Facebook's new pages experience), `page`, `group` (a post by the group itself) or `anonymous` (a member who posted anonymously: the name is the one Facebook shows). |
| `reactions` | All reactions, exact (the leader's `likesCount`). |
| `likes` | The same as `reactions` (all reaction types, not only Like). |
| `reaction_types_count` | How many reaction types the post has (the leader's `topReactionsCount`). |
| `reactions_like` | Like reactions, exact; 0 when none (the leader's `reactionLikeCount`). |
| `reactions_love` | Love reactions; 0 when none. |
| `reactions_care` | Care reactions; 0 when none. |
| `reactions_haha` | Haha reactions; 0 when none. |
| `reactions_wow` | Wow reactions; 0 when none. |
| `reactions_sad` | Sad reactions; 0 when none. |
| `reactions_angry` | Angry reactions; 0 when none. |
| `comments` | The post's comment count as Facebook gives it, replies included (the leader's `commentsCount`). |
| `shares` | The post's share count (the leader's `sharesCount`). |
| `video_views` | Video views (Facebook's `video_view_count`), exact. Null on a post with no video. |
| `video_plays` | Video plays (Facebook's `play_count`), exact, when Facebook gives it. Null otherwise. |
| `video_post_views` | Views of the video in this post (Facebook's `video_post_view_count`), when given. |
| `media` | The post's photos and videos, in order (the leader's `attachments`): `type` (photo or video), `id`, `url` (the full-size photo or the video's page), `width`, `height`, `alt_text` (Facebook's description of a photo, the leader's `ocrText`), `video_hd_url`, `video_sd_url`, `thumbnail_url`, `duration_s`. The file links are Facebook's own and stop working after some days. |
| `media_count` | How many photos or videos the post holds (an album's or a listing's own count, which can be more than `media` lists). |
| `media_truncated` | True when the post holds more photos than `media` lists: Facebook sends a logged-out reader at most 5 photos of an album or a listing. |
| `video_captions_url` | The video's captions file (`.srt`) when Facebook has one; null otherwise. |
| `link` | A link post's preview: `url` (the target, unwrapped), `title`, `description`, `domain`, `image_url`. Null on other posts. |
| `event` | An event post's event: `id`, `name`, `url`, `when`, `place`. Null on other posts. |
| `shared_post` | The post this post shares: `post_id`, `url`, `author_name`, `author_id`, `author_url`, `text`, `time`, `media`, `media_count`, `media_truncated`. Its own counts are not given by Facebook in the sharing post. Null on other posts. |
| `listing` | A buy-and-sell listing's own fields: `id`, `title`, `price` (as Facebook shows it, such as "Contact seller" or "$40"), `location`, `is_sold`, and `marketplace_url` for a Marketplace item posted to the group. Null on an ordinary post: a price is never read from a post's text. |
| `top_comment` | The first top comment Facebook shows with the post: `id`, `text`, `time`, `likes`, `replies`, `author_name`, `author_id`, `author_url`, `url`. Null when the post shows none. |
| `top_comments` | With `maxCommentsPerPost` 1-10: up to that many comments of the post from one more call, each as in `top_comment` (the leader's `topComments`); else the top comment alone. |
| `comments_status` | `served` (the comments call answered), `none` (the post has no comment: no call), `refused` (Facebook refused the call; the row is delivered without them), `not_asked` (`maxCommentsPerPost` is 0, or the post came already). |
| `group_id` | The group's numeric id, on every row. |
| `group_name` | The group's name (the leader's `groupTitle`), on every row. |
| `group_url` | The group's link (the leader's `facebookUrl`). |
| `group_members` | On a post row: the group's exact member count, from its About page once a run (`includeGroupInfo`). Null with `includeGroupInfo` off, or on a row read from a post link. |
| `group_privacy` | On a post row: `public` (the posts this Actor reads come from public groups). |
| `privacy` | On a group or input row: `public` or `private`, from the group's About page. |
| `privacy_text` | The About page's own line: "Anyone can see who's in the group and what they post." or "Only members can see …". |
| `visibility` | `visible` (anyone can find the group) or `hidden`, from the About page. |
| `members` | The group's exact member count ("373,967 total members" on its About page); it moves by the hour. |
| `members_text` | The About page's member line as served. |
| `new_members_text` | The About page's new-members line as Facebook serves it to a logged-out reader ("No new members in the last week" on every group read in testing, a busy one too: the count is for members). |
| `posts_last_day` | Posts in the group in the last day, as the About page counts them. |
| `posts_last_month` | Posts in the group in the last month, as the About page counts them. |
| `created_at` | When the group was created, UTC. |
| `description` | The group's own description. |
| `rules` | The titles of the group's rules, in order. |
| `rules_count` | How many rules the group lists. |
| `cover_url` | The group's cover photo (a Facebook link that expires). |
| `view` | On a group's input row: the order read (`CHRONOLOGICAL` new posts, `RECENT_ACTIVITY`, `TOP_POSTS`, `CHRONOLOGICAL_LISTINGS`). |
| `verdict` | On an input row: `COMPLETE` (read to the list's own end, read twice), `CAPPED` (resultsLimit, maxGroupsPerQuery, maxItems or your charge limit reached; `next_cursor` says where), `DATE_LIMIT` (the date window's older end reached) or `PARTIAL` (stopped early: `note` says why). |
| `rows_delivered` | On an input row: the post rows (or, for a search, the group rows) it delivered and charged. |
| `posts_read` | On a group's input row: the posts read, the ones the filters left out included. |
| `newest_time` | On a group's input row: the newest post time read. |
| `oldest_time` | On a group's input row: the oldest post time read; give it as `onlyPostsOlderThan` to read on from there. |
| `calls` | On a group's input row: the feed calls the walk made (3 posts a call). |
| `next_cursor` | On a group's input row: Facebook's cursor where the walk stopped (CAPPED, PARTIAL), null at the list's end. |
| `search_query` | On a found group's row and its search's input row: the words searched. |
| `search_engine` | The web index that listed the groups (`yahoo`, else `bing`): Facebook's own group search is closed to a logged-out reader. |
| `search_rank` | On a found group's row: its place in the web index's order (1 = first). |
| `note` | Why an input row has the status or the verdict it has. |
| `scraped_at` | When this row was read (UTC). |
| `story_id` | Facebook's own id of the post's feed story (the leader's `id`, the `UzpfS…` form). |
| `author_picture_url` | The author's profile picture as Facebook shows it beside the post (a Facebook link that expires). |
| `author_is_verified` | True when Facebook shows the verified badge beside the author's name on the post, false when it shows none; null when the post has no name line to read. |
| `locations` | The places the group's admins set on its About page (such as "Barcelona, Spain"); empty when they set none. |
| `topics` | The topics the group's admins set on its About page (such as "Mathematics"); empty when they set none. |

Example record:

```json
{
  "record_type": "post",
  "status": "ok",
  "input": "https://www.facebook.com/groups/306490195164828/",
  "post_id": "1150908894056283",
  "url": "https://www.facebook.com/groups/306490195164828/posts/1150908894056283/",
  "feedback_id": "ZmVlZGJhY2s6cGZiaWQwMk45eTNFV21xU0g4clFrd0xWYWtqUlYxVXN5d3lIelpBbUdXckVEeFBtVDVNQzNuN3VWaVBvQWhFRWt0ZkNvN1hs",
  "post_type": "text",
  "is_video": false,
  "is_share": false,
  "time": "2026-11-05T09:17:21Z",
  "timestamp": 1793870241,
  "video_publish_time": null,
  "text": "Should we cut back hydrangeas please",
  "text_length": 36,
  "hashtags": [],
  "mentions": [],
  "links_in_text": [],
  "author_id": "2292218444903000",
  "author_name": "Anonymous participant",
  "author_url": null,
  "author_type": "anonymous",
  "reactions": 1,
  "likes": 1,
  "reaction_types_count": 1,
  "reactions_like": 1,
  "reactions_love": 0,
  "reactions_care": 0,
  "reactions_haha": 0,
  "reactions_wow": 0,
  "reactions_sad": 0,
  "reactions_angry": 0,
  "comments": 16,
  "shares": 0,
  "video_views": null,
  "video_plays": null,
  "video_post_views": null,
  "media": [],
  "media_count": 0,
  "media_truncated": false,
  "video_captions_url": null,
  "link": null,
  "event": null,
  "shared_post": null,
  "listing": null,
  "top_comment": {
    "id": "1150949240718915",
    "text": "(a member's comment)",
    "time": "2026-11-05T09:18:55Z",
    "likes": 8,
    "replies": 0,
    "author_name": "A member",
    "author_id": "100000000000001",
    "author_url": null,
    "url": "https://www.facebook.com/groups/306490195164828/posts/1150908894056283/?comment_id=1150949240718915"
  },
  "top_comments": [
    {
      "id": "1150949240718915",
      "text": "(a member's comment)",
      "time": "2026-11-05T09:18:55Z",
      "likes": 8,
      "replies": 0,
      "author_name": "A member",
      "author_id": "100000000000001",
      "author_url": null,
      "url": "https://www.facebook.com/groups/306490195164828/posts/1150908894056283/?comment_id=1150949240718915"
    }
  ],
  "comments_status": "not_asked",
  "group_id": "306490195164828",
  "group_name": "Gardening tips and tricks",
  "group_url": "https://www.facebook.com/groups/306490195164828/",
  "group_members": 99470,
  "group_privacy": "public",
  "privacy": null,
  "privacy_text": null,
  "visibility": null,
  "members": null,
  "members_text": null,
  "new_members_text": null,
  "posts_last_day": null,
  "posts_last_month": null,
  "created_at": null,
  "description": null,
  "rules": null,
  "rules_count": null,
  "cover_url": null,
  "view": null,
  "verdict": null,
  "rows_delivered": null,
  "posts_read": null,
  "newest_time": null,
  "oldest_time": null,
  "calls": null,
  "next_cursor": null,
  "search_query": null,
  "search_engine": null,
  "search_rank": null,
  "note": null,
  "scraped_at": "2026-11-05T09:44:40Z",
  "story_id": "UzpfSTIyOTIyMTg0NDQ5MDMwMDA6Vks6MTE1MDkwODg5NDA1NjI4Mw==",
  "author_picture_url": null,
  "author_is_verified": false,
  "locations": null,
  "topics": null
}
```

### Pricing

**Pay per post delivered and per group a search finds**, plus one run start; no platform usage and no proxy traffic is billed to you.

| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Post (`post`) | per 1,000 | $5.00 | $4.00 | $3.30 | $2.50 |
| Group found (`group`) | per 1,000 | $3.00 | $2.55 | $2.10 | $1.50 |
| Run start (`start`) | per run | $0.0045 | $0.0045 | $0.0045 | $0.0045 |

A post is **$5.00 per 1,000** on the Free plan ($0.005 a post) and $4.00 on Starter. Everything the row carries is inside that price: the text, the time, the reactions by type, the comment and share counts, the author's public name and link, the media, the shared post, the post's link and feedback id, the group's member count and privacy, and the comments of `maxCommentsPerPost`. **The date window is free**: no add-on for `onlyPostsNewerThan` / `onlyPostsOlderThan`, and none for `textContains` or `authorContains`. A group a keyword search finds and delivers is **$3.00 per 1,000** on the Free plan and $2.55 on Starter; a group you give as a link is never charged. Each run that delivers a charged row also pays the **run start, $0.0045 once**; a run that delivers none pays nothing at all. So one post costs $0.0095 on the Free plan and $0.0085 on Starter; a group's newest 20 posts $0.1045 and $0.0845; 1,000 posts $5.0045 and $4.0045; a search that finds 10 groups $0.0345 and $0.03. Free: every input's summary row, a private, missing or empty group, a value that is not a group link, a post given twice, a group a search found outside your privacy or member range, a post a filter or the date window leaves out, and an input left unread at `maxItems`, your charge limit or the time limit. A run that stops early (a timeout, an abort) is charged for the rows it delivered. `maxItems` counts the charged rows across inputs. Before the first request the log names the price of a post (and of a group found) at your plan, the run start and the most the run can cost when `resultsLimit`, `maxGroupsPerQuery` or `maxItems` bounds it (under your charge limit). With `resultsLimit` 0 and no `maxItems` there is no such bound: Facebook gives no count of a group's posts ahead, and a list's end is far apart from group to group (a small group of our tests ended at 62 posts, read to its end twice; a group of 710 members still had posts after 300, back six months), so set `resultsLimit`, `maxItems` or a charge limit for a deep walk. Enterprise plans have their own rates — the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Deep walks, the orders and the date window** — `resultsLimit` (20 unless you set it) counts the posts delivered from each group after the filters; 0 reads every post the group's feed serves, 3 posts a call, to Facebook's own end. `viewOption` takes the group's own orders: `CHRONOLOGICAL` (new posts, the default), `RECENT_ACTIVITY`, `TOP_POSTS`, `CHRONOLOGICAL_LISTINGS` (buy-and-sell listings). `onlyPostsNewerThan` / `onlyPostsOlderThan` take a date (`2026-09-01`), a time (`2026-09-01T12:00:00Z`) or a span back from now (`7 days`, `2 months`). `maxItems` caps the whole run's charged rows. A group's walk is one page after another (Facebook's cursor), so several groups in one run read at the same time and one deep group takes about 2 seconds a call.
- **Filters and comments** — `textContains` keeps the posts whose text or shared text holds any of your words, `authorContains` the posts whose author's name holds any of yours (both case-insensitive); a filtered walk reads 20 calls, then 7 a delivered post, and its row says how many matched. A post a filter leaves out is free. `maxCommentsPerPost` 1-10 adds up to that many comments a post from one more call (free); every comment and reply is our Facebook Comments Scraper's job, by the row's `feedback_id`.
- **What a row is** — `record_type` = `post`: one post, charged. `record_type` = `group`: one group a search found, charged. `record_type` = `target`: one free row per input with its `status`, `verdict`, `rows_delivered`, `posts_read`, the newest and oldest post time read, the group's facts and a `note` saying why it stopped. The photo and video links are Facebook's own and stop working after some days: download what you keep.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `startUrls` | `array` | `[]` | One per line. A public group's link (https://www.facebook.com/groups/selftaughtprogrammers/, /groups/<numeric id>/, any page of the group such as /about/ or /members/, m.facebook.com links) or a bare group name or numeric id: the group's posts. A group post's link (/groups/<group>/posts/<id>/, /permalink/<id>/): that one post. A private, missing or other link gets a free row that says so. Empty, with no searchQueries either: the example input is read. |
| `resultsLimit` | `integer` | `20` | The posts to deliver from each group, after the filters below. 0 = every post the group's feed serves, to the list's own end (3 posts a call: a deep walk takes time). Empty = 20. |
| `viewOption` | `CHRONOLOGICAL` / `RECENT_ACTIVITY` / `TOP_POSTS` / `CHRONOLOGICAL_LISTINGS` | `"CHRONOLOGICAL"` | The group's own sort: new posts first (the default), recent activity, top posts, or buy-and-sell listings (newest first). The date window stops a new-posts walk at its older end; in the other orders it is a filter. |
| `onlyPostsNewerThan` | `string` |  | Only posts from this time on: a date (2026-10-01, 00:00 UTC), a time (2026-10-01T12:00:00Z) or a span back from now (7 days, 2 months). Free (no add-on price): a new-posts walk stops at the first older post. |
| `onlyPostsOlderThan` | `string` |  | Only posts up to this time: a date (2026-10-01 = up to 00:00 UTC that day), a time or a span back from now. The newer posts are read on the way (at most 100 calls) and not delivered. |
| `textContains` | `array` | `[]` | Deliver only the posts whose text (or the shared post's text) contains any of these words, case-insensitive. A filtered walk reads 20 calls, then 7 a delivered post, and its group row says how many matched. |
| `authorContains` | `array` | `[]` | Deliver only the posts whose author's public name contains any of these words, case-insensitive. |
| `includeGroupInfo` | `boolean` | `true` | Read each group's About page once (one call a group): the exact member count, the privacy, posts in the last day and month, the created date, the description and the rules, on the group's row, and the member count and privacy on every post row. Off: a numeric id's posts are read with no page call (a group name still needs it). |
| `maxCommentsPerPost` | `integer` | `0` | 0 = the first top comment the post itself carries (no extra call); 1-10 = up to that many comments, from one more call a post that has comments (slower). Free either way. Every comment and reply of a post: give its feedback_id to our Facebook Comments Scraper. |
| `searchQueries` | `array` | `[]` | One search a line (maths formulas). Each gives up to maxGroupsPerQuery public groups in a web index's order (Facebook's own group search is closed to a logged-out reader), one row a group with its exact member count and privacy. The groups' posts are not read: put the links you want in Facebook groups above. |
| `maxGroupsPerQuery` | `integer` | `10` | The most groups to deliver for each search (1-50). |
| `groupPrivacy` | `any` / `public` / `private` | `"any"` | Deliver only the found groups of this privacy. |
| `minMembers` | `integer` | `0` | Deliver only the found groups with at least this many members (the exact count); 0 = no bound. |
| `maxMembers` | `integer` | `0` | Deliver only the found groups with at most this many members; 0 = no bound. |
| `maxItems` | `integer` | `0` | The most charged rows of the whole run (posts and found groups), across all inputs; 0 = no cap beyond your charge limit. |
| `proxyUrl` | `string` |  | Your own proxy (scheme://user:pass@host:port) for every call instead of ours. Leave empty: the Actor brings its own connections at no cost to you. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

A private group, a group that does not exist, an empty group, or a value that is not a Facebook group link is not an error: it gets one free row with its `status` and the reason (with the group's member count and privacy when Facebook shows them), and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Which status and verdict is which?**

A post or group row is `ok`. An input row: `ok` with a verdict (`COMPLETE`: read to Facebook's own end, read twice; `CAPPED`: resultsLimit, maxGroupsPerQuery, maxItems or your charge limit reached; `DATE_LIMIT`: the window's older end reached; `PARTIAL`: stopped early, the note says why), or free: `private` (a private group: only members see its posts), `not_found` (no such group or post, on two connections), `invalid_input` (not a group link, an empty value), `duplicate` (the same group as another value of the run), `failed` (Facebook refused every connection tried; run it again later), `not_read` (maxItems, your charge limit or the time limit came first).

**Can it read private groups?**

No. A private group's posts are for its members, and this Actor uses no account. A private group gets a free row with its name, member count and privacy, so you know what it is.

**Does it read every post a group has?**

It reads what the group's logged-out feed serves, in the order you choose, to Facebook's own end flag. A group whose feed is mostly one admin's posts (welcome posts, for one) is served as Facebook lists it. An album gives its first 5 photos with the album's real count beside them.

**Why does the group search not find my group by its exact name?**

Facebook's own group search is closed to a logged-out reader, so the search reads a web index (`site:facebook.com/groups <words>`) and gives the groups in its order. A large group the index does not list for your words can be missing; give its link in `startUrls` instead.

**Does it need a Facebook account, cookies or an API key?**

No. Everything is read from Facebook's public, logged-out pages: no login, no cookie, no token of yours. The connections are included in the price.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/facebook-groups-scraper').call({"startUrls": ["https://www.facebook.com/groups/selftaughtprogrammers/"], "resultsLimit": 20});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/facebook-groups-scraper').call(run_input={'startUrls': ['https://www.facebook.com/groups/selftaughtprogrammers/'], 'resultsLimit': 20})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/facebook-groups-scraper --input '{"startUrls": ["https://www.facebook.com/groups/selftaughtprogrammers/"], "resultsLimit": 20}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~facebook-groups-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"startUrls": ["https://www.facebook.com/groups/selftaughtprogrammers/"], "resultsLimit": 20}'
```

#### AI agents and MCP

Apify's MCP server offers this Actor as a tool to Claude, Cursor, VS Code and any other MCP client. Connect `https://mcp.apify.com?tools=hyperbach/facebook-groups-scraper` and ask, for example: "Get the newest 50 posts of the public Facebook group selftaughtprogrammers with their reactions, comments and authors". The agent fills the input from the table above and reads the rows from the run's dataset. A field under the name another Actor gives it is read as this Actor's own: `urls`, `url`, `links`, `groupUrls` as `startUrls`; `search`, `searchQuery`, `queries`, `query` as `searchQueries`; `maxPosts`, `postsLimit`, `maxPostsPerGroup`, `postsPerGroup` as `resultsLimit`; `sortBy`, `sort`, `sorting`, `sortingSetting` as `viewOption` (and more).

#### n8n, Make and Zapier

The Apify node in n8n, the Apify app in Make and the Apify integration in Zapier run this Actor on a schedule or from a trigger and read its dataset. A webhook on the run's end (Integrations on the Actor page) sends the rows on to your own endpoint. Nothing to install on our side.

### Support

**Disagree with a charge?** Write to apify@hyperbach.com with the run ID. We will ask Apify to compensate you, and the charge comes back to your Apify account as platform credit.

**Did it do the job?** A short [review on the Store](https://apify.com/hyperbach/facebook-groups-scraper/reviews), at any rating, helps the next buyer choose.

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

## `startUrls` (type: `array`):

One per line. A public group's link (https://www.facebook.com/groups/selftaughtprogrammers/, /groups/<numeric id>/, any page of the group such as /about/ or /members/, m.facebook.com links) or a bare group name or numeric id: the group's posts. A group post's link (/groups/<group>/posts/<id>/, /permalink/<id>/): that one post. A private, missing or other link gets a free row that says so. Empty, with no searchQueries either: the example input is read.

## `resultsLimit` (type: `integer`):

The posts to deliver from each group, after the filters below. 0 = every post the group's feed serves, to the list's own end (3 posts a call: a deep walk takes time). Empty = 20.

## `viewOption` (type: `string`):

The group's own sort: new posts first (the default), recent activity, top posts, or buy-and-sell listings (newest first). The date window stops a new-posts walk at its older end; in the other orders it is a filter.

## `onlyPostsNewerThan` (type: `string`):

Only posts from this time on: a date (2026-10-01, 00:00 UTC), a time (2026-10-01T12:00:00Z) or a span back from now (7 days, 2 months). Free (no add-on price): a new-posts walk stops at the first older post.

## `onlyPostsOlderThan` (type: `string`):

Only posts up to this time: a date (2026-10-01 = up to 00:00 UTC that day), a time or a span back from now. The newer posts are read on the way (at most 100 calls) and not delivered.

## `textContains` (type: `array`):

Deliver only the posts whose text (or the shared post's text) contains any of these words, case-insensitive. A filtered walk reads 20 calls, then 7 a delivered post, and its group row says how many matched.

## `authorContains` (type: `array`):

Deliver only the posts whose author's public name contains any of these words, case-insensitive.

## `includeGroupInfo` (type: `boolean`):

Read each group's About page once (one call a group): the exact member count, the privacy, posts in the last day and month, the created date, the description and the rules, on the group's row, and the member count and privacy on every post row. Off: a numeric id's posts are read with no page call (a group name still needs it).

## `maxCommentsPerPost` (type: `integer`):

0 = the first top comment the post itself carries (no extra call); 1-10 = up to that many comments, from one more call a post that has comments (slower). Free either way. Every comment and reply of a post: give its feedback_id to our Facebook Comments Scraper.

## `searchQueries` (type: `array`):

One search a line (maths formulas). Each gives up to maxGroupsPerQuery public groups in a web index's order (Facebook's own group search is closed to a logged-out reader), one row a group with its exact member count and privacy. The groups' posts are not read: put the links you want in Facebook groups above.

## `maxGroupsPerQuery` (type: `integer`):

The most groups to deliver for each search (1-50).

## `groupPrivacy` (type: `string`):

Deliver only the found groups of this privacy.

## `minMembers` (type: `integer`):

Deliver only the found groups with at least this many members (the exact count); 0 = no bound.

## `maxMembers` (type: `integer`):

Deliver only the found groups with at most this many members; 0 = no bound.

## `maxItems` (type: `integer`):

The most charged rows of the whole run (posts and found groups), across all inputs; 0 = no cap beyond your charge limit.

## `proxyUrl` (type: `string`):

Your own proxy (scheme://user:pass@host:port) for every call instead of ours. Leave empty: the Actor brings its own connections at no cost to you.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.facebook.com/groups/selftaughtprogrammers/"
  ],
  "resultsLimit": 20,
  "viewOption": "CHRONOLOGICAL",
  "textContains": [],
  "authorContains": [],
  "includeGroupInfo": true,
  "maxCommentsPerPost": 0,
  "searchQueries": [],
  "maxGroupsPerQuery": 10,
  "groupPrivacy": "any",
  "minMembers": 0,
  "maxMembers": 0,
  "maxItems": 50
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped records in the default dataset. One row per post (and one free summary row for every input):

## `runSummary` (type: `string`):

How the run went: posts and groups charged, free rows by status, each input's verdict, posts read and feed calls, the end reads, the comments calls, the source counters (requests and seconds by call, connections opened and refused by country, bytes), the first and last row's seconds and the memory watch.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.facebook.com/groups/selftaughtprogrammers/"
    ],
    "resultsLimit": 20,
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/facebook-groups-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": ["https://www.facebook.com/groups/selftaughtprogrammers/"],
    "resultsLimit": 20,
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/facebook-groups-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.facebook.com/groups/selftaughtprogrammers/"
  ],
  "resultsLimit": 20,
  "maxItems": 50
}' |
apify call hyperbach/facebook-groups-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hyperbach/facebook-groups-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/HhDJB4MJm73Ddhixq/builds/zIBBByB0dvPtYzMeI/openapi.json
