# 📘 Facebook Groups & Comments Scraper (`simpleapi/facebook-groups-scraper`) Actor

Gather high-quality data from public Facebook groups—posts, comments, reactions, images, and contextual metadata. Designed for researchers, brands, and developers needing accurate, scalable group insights for analytics or automation.

- **URL**: https://apify.com/simpleapi/facebook-groups-scraper.md
- **Developed by:** [SimpleAPI](https://apify.com/simpleapi) (community)
- **Categories:** Automation, Lead generation, Social media
- **Stats:** 110 total users, 7 monthly users, 91.0% runs succeeded, 1 bookmarks
- **User rating**: 1.00 out of 5 stars

## Pricing

$19.99/month + usage

To use this Actor, you pay a monthly rental fee to the developer. The rent is subtracted from your prepaid usage every month after the free trial period. You also pay for the Apify platform usage, which gets cheaper the higher Apify subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### Facebook Groups Scraper — Posts, Comments and Contact Data

Extract posts from any public Facebook group as clean, structured JSON — full text, author profile, a per-reaction breakdown (like, love, haha, wow, sad, angry, care), photo/video/link attachments, absolute timestamps, and optional per-post comments. The scraper also derives contact-style signals from post text — `hashtags`, `mentions`, `links`, `emails`, `phones`, and an `engagementScore` — so social listening teams, lead-gen agencies, and researchers can go straight from a group URL to a filterable dataset. Pricing is pay-per-event: you're charged per post (`row_result`) and, only if you opt in, per comment (`comment_result`).

### What is Facebook Groups Posts & Comments Scraper?

Facebook Groups Posts & Comments Scraper (Actor name `facebook-groups-posts-comments-scraper`) reads the public feed of one or more Facebook groups and returns each post as a structured JSON row, with an optional child row per comment. It works against Facebook's own internal GraphQL feed endpoint (`GroupsCometFeedRegularStoriesPaginationQuery`) rather than a login-gated browser session, so **no Facebook account is required for public groups**. For closed or private groups you belong to, supplying `cookieString` (a logged-in session's `c_user` + `xs` cookies) unlocks access — this is optional, not required.

Key capabilities, all confirmed in the source:

- **Group discovery by URL only** — paste one or more group URLs (or numeric group IDs) into `startUrls`; there is no keyword-based group search.
- **Four feed orderings** via `viewOption` — chronological, recent-activity, top-posts, and a dedicated Buy/Sell listings order for marketplace-style groups.
- **A full 7-way reaction breakdown** per post (`reactionLikeCount` … `reactionCareCount`) plus a computed total, not just an aggregate like count.
- **Derived lead-generation fields** computed from post text with no AI required — `hashtags`, `mentions`, `links`, `emails`, `phones`, and a weighted `engagementScore`.
- **Six real filters** applied before any post is charged — keyword include/exclude, minimum likes/comments/shares, and a date range.
- **Optional per-post comments and optional LLM enrichment**, both off by default so the base run stays cheap.
- **JSON output** via the Apify dataset, exportable to CSV, Excel, XML, or RSS from the Apify Console like any Apify dataset.

### What data can I extract with Facebook Groups Posts & Comments Scraper?

Every post is pushed as one dataset row of `type: "post"`. The Actor's row-building function (`normalize_post` in `src/extractor.py`) writes 31 keys, `derive_text_fields` in `src/enrich.py` adds 7 more, and — only when `aiEnhancement` is on — `src/ai.py` adds up to 5 AI keys, for **38–43 fields per post row** depending on configuration. The Apify Store's default dataset view shows only 34 of these; see the Output sample section below for the 9 fields it hides.

| Field | Example value | Use case |
|---|---|---|
| `type` | `"post"` | Distinguish post rows from comment rows |
| `isChild` | `false` | `true` on comment rows, linking them to a parent post |
| `parentId` | `null` | Populated on comment rows only, points at the parent post `id` |
| `facebookUrl` | `"https://www.facebook.com/groups/cheapmealideas/"` | The group URL this row came from |
| `inputUrl` | `"https://www.facebook.com/groups/cheapmealideas/"` | The exact URL supplied in `startUrls` |
| `url` | `"https://www.facebook.com/groups/cheapmealideas/permalink/..."` | Canonical post permalink |
| `id` | `"1234567890123456"` | Facebook's internal story node ID |
| `postId` | `"1204545088344463"` | Facebook's post ID |
| `legacyId` | `"1204545088344463"` | Legacy post ID (mirrors `postId`) |
| `feedbackId` | `"ZmVlZGJhY2s6MTIwNDU0NTA4ODM0NDQ2Mw=="` | Facebook's UFI feedback object ID |
| `timestamp` | `1774704000` | Post creation time, Unix epoch seconds |
| `date` | `"2026-03-28T14:00:00.000Z"` | Post creation time, ISO 8601 |
| `scrapedAt` | `"2026-07-26T09:12:03.000Z"` | When this row was extracted |
| `user` | `{"id": "...", "name": "Jane Doe", "profileUrl": "...", "profilePicture": "...", "__typename": "User"}` | Author identity |
| `text` | `"Anyone have a good vegan chili recipe?"` | Full post body |
| `postType` | `"photo"` | One of `text`, `photo`, `video`, `link`, `album`, `reshare` |
| `reactionLikeCount` … `reactionCareCount` | `42`, `11`, `3`, `0`, `0`, `0`, `2` | Per-type reaction counts |
| `topReactionsCount` | `58` | Sum of the reaction breakdown above |
| `likesCount` | `61` | Facebook's own total-reaction counter (may exceed `topReactionsCount` when Facebook doesn't surface every reaction type in the breakdown) |
| `commentsCount` | `14` | Total comments on the post, as reported by Facebook |
| `sharesCount` | `2` | Total shares |
| `attachments` | `[{"type": "photo", "image": {"uri": "...", "height": 960, "width": 720}}]` | Photos, videos, shared links, or albums |
| `topComments` | `[{"id": "...", "text": "...", "date": "...", ...}]` | Up to 2 preview comments, always present regardless of `includeComments` |
| `facebookId` | `"106823902845"` | Numeric ID of the target group |
| `groupTitle` | `"Cheap Meal Ideas"` | Group display name |
| `hashtags` | `["mealprep", "budget"]` | Hashtags parsed from `text` |
| `mentions` | `["johnsmith"]` | `@mentions` parsed from `text` |
| `links` | `["https://example.com/recipe"]` | URLs parsed from `text` |
| `emails` | `["chef@example.com"]` | Email addresses parsed from `text` |
| `phones` | `["+1 555-201-4488"]` | Phone-like strings parsed from `text` (7–15 digits) |
| `engagementScore` | `109` | `likes + 3×comments + 5×shares` — a simple weighted score, see below |
| `postAgeDays` | `4` | Days between `date` and scrape time |
| `aiSentiment` | `"positive"` | Only when `aiEnhancement` is on and enrichment succeeds |
| `aiTopics` | `["recipes", "budget cooking"]` | Up to 3 tags, AI-only |
| `aiLeadSignal` | `"medium"` | AI-only, see AI enrichment note below |

#### Engagement and reaction data

Every post carries Facebook's full reaction breakdown, not just a total: `reactionLikeCount`, `reactionLoveCount`, `reactionHahaCount`, `reactionWowCount`, `reactionSadCount`, `reactionAngryCount`, and `reactionCareCount`, summed into `topReactionsCount`. Facebook also reports a separate `likesCount` (its own total-reaction counter) and `commentsCount` / `sharesCount`. `engagementScore` is a deterministic weighted score computed in the source (`compute_engagement_score` in `src/enrich.py`) as `likes + 3×comments + 5×shares` — useful for ranking a batch of posts by engagement without writing your own formula. A market researcher tracking a niche community can sort the dataset by `engagementScore` or `topReactionsCount` to surface the posts driving the most reaction in a given week.

#### Targeting and filtering data

`resultsLimit`, `viewOption`, `keywords` / `excludeKeywords`, `minLikes` / `minComments` / `minShares`, and `onlyPostsNewerThan` / `onlyPostsOlderThan` are the fields you filter and segment on before a single credit is spent — all six are enforced in `passes_filters()` (`src/enrich.py`) before `Actor.push_data()` is called, so filtered-out posts are never charged. `searchGroupKeyword` and `searchGroupYear` add a second, client-side narrowing pass inside the feed fetch itself.

### Why not build this yourself?

Facebook does not expose a public API for reading a group's post feed to ordinary third-party developers — Meta's Graph API groups permissions are restricted to a small set of approved partner use cases, not general-purpose scraping. Building this yourself against Facebook's actual feed means reverse-engineering the same things this Actor's source code does: discovering the group's internal `node_id`, extracting the current GraphQL `doc_id` for `GroupsCometFeedRegularStoriesPaginationQuery` (which changes whenever Facebook ships new frontend code — see `extract_doc_id_from_content()` in `src/app.py`, which tries nine regex patterns before falling back to scanning bundled JS files), pulling CSRF tokens (`lsd`, `fb_dtsg`, `jazoest`) out of the bootstrap HTML, handling Facebook's login-wall redirect for unauthenticated or flagged requests, paginating with an `end_cursor`, deduplicating overlapping post edges across pages, and rotating IPs through a proxy when Facebook rate-limits or blocks a request.

None of that is exposed through documented, stable Facebook infrastructure — it is Facebook's internal Comet frontend contract, which this Actor already tracks with fallback patterns for when Facebook changes it. Building and then maintaining that pipeline yourself — including the residential-proxy cost, since Facebook blocks most datacenter and guest IPs — is the ongoing engineering cost this Actor removes. If you already have a compliant, licensed data source for Facebook group content (for example, an approved Meta platform partnership), use that instead; if you need ad-hoc or recurring access to public group posts without maintaining scraping infrastructure, this Actor is built for that.

### How to use data extracted from Facebook groups?

#### Lead generation and sales teams

`emails`, `phones`, `mentions`, and `links` are parsed out of every post's `text` automatically, and — with `aiEnhancement` on — `aiBuyIntent` and `aiLeadSignal` classify whether a post reads like someone wanting to buy, sell, or hire. Set `keywords` to your product category, `minLikes` to filter out low-engagement noise, and export the rows where `aiLeadSignal` is `"medium"` or `"high"` straight into a CRM import.

#### Agencies monitoring groups for clients

Point `startUrls` at the niche groups a client's audience lives in, schedule the Actor to run daily or weekly through Apify Scheduler, and use `onlyPostsNewerThan` (e.g. `"1 days"`) so each run only returns posts since the last one. `groupTitle` on every row makes it trivial to split a multi-group run back out per client in a spreadsheet or BI tool.

#### Market research and intelligence

Run the same group over time with `viewOption: "CHRONOLOGICAL"` to build a time-ordered archive of discussion, then use `engagementScore`, `postAgeDays`, and the reaction breakdown to chart which topics and post formats get the most traction in a community — useful for category research before a product launch or for benchmarking a competitor's community engagement.

#### AI agents and automated pipelines

Because the Actor is a standard Apify Actor, any agent framework that can call the Apify API can run it, poll the run status, and pull `dataset items` as structured JSON — no custom parser needed. Feed `aiSentiment`, `aiTopics`, and `aiLeadSignal` (when `aiEnhancement` is on) directly into a downstream classification or alerting pipeline, or use the raw `text` field as retrieval context for a RAG system built on community discussion data.

### 🔼 Input sample

All 20 input parameters, read directly from `.actor/actor.json`:

| Parameter | Required | Type | Constraints | Description |
|---|---|---|---|---|
| `startUrls` | **Yes** | array | — | Public Facebook group URLs. Group numeric IDs also work. |
| `resultsLimit` | No | integer | default `20`, min `1` | Maximum number of top-level POSTS. Comments are additional and NOT counted against this limit. |
| `viewOption` | No | string (enum) | default `CHRONOLOGICAL` | Feed order: `CHRONOLOGICAL` (oldest→newest), `RECENT_ACTIVITY`, `TOP_POSTS`, `CHRONOLOGICAL_LISTINGS` (Buy/Sell). |
| `keywords` | No | array | default `[]` | Keep only posts whose text contains at least one of these words (case-insensitive). |
| `searchGroupKeyword` | No | string | default `""` | Client-side search by letter/word inside the group. Without login, single/double letters work best. |
| `searchGroupYear` | No | string | default `""` | Keep only posts from a specific year, e.g. `"2024"`. Best combined with `searchGroupKeyword`. |
| `excludeKeywords` | No | array | default `[]` | Drop posts whose text contains any of these words (case-insensitive). |
| `minLikes` | No | integer | default `0`, min `0` | Drop posts with fewer likes/reactions than this. |
| `minComments` | No | integer | default `0`, min `0` | Drop posts with fewer comments than this. |
| `minShares` | No | integer | default `0`, min `0` | Drop posts with fewer shares than this. |
| `onlyPostsNewerThan` | No | string (date) | — | Keep posts on/after this date. Absolute (`2024-01-15`) or relative (`"2 weeks"`, `"30 days"`, `"1 month"`). |
| `onlyPostsOlderThan` | No | string (date) | — | Keep posts on/before this date. Absolute or relative. Combine with `onlyPostsNewerThan` for a range. |
| `includeComments` | No | boolean | default `false` | Collect available comments per post, interleaved after the post row. |
| `maxComments` | No | integer | default `10`, min `0` | Cap comments collected per post. `0` = no per-post cap. |
| `aiEnhancement` | No | boolean | default `false` | Classify each post's text (sentiment, topics, buy-intent, lead signal) when on AND a key is provided. |
| `aiModel` | No | string (enum) | default `claude-haiku-4-5` | LLM model; provider auto-detected from the name prefix (`claude-*`=Anthropic, `gpt-*`/`o1`/`o3`=OpenAI, `gemini-*`=Google, `grok-*`=xAI, `deepseek-*`=DeepSeek, `sonar*`=Perplexity, `mistral-*`=Mistral). 22 models total. |
| `aiApiKey` | No | string (secret) | — | Your LLM provider API key. Falls back to `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GEMINI_API_KEY` / `XAI_API_KEY` / `DEEPSEEK_API_KEY` / `PERPLEXITY_API_KEY` / `MISTRAL_API_KEY` env vars per provider. |
| `maxRequestRetries` | No | integer | default `3`, min `0` | Retries per GraphQL request before giving up. |
| `proxyConfiguration` | No | object (proxy) | prefill: Apify residential | Proxy used for requests. If left unset, Apify residential proxy is used automatically — Facebook blocks most datacenter IPs. |
| `cookieString` | No | string (secret) | — | Logged-in session cookies (must include `c_user` + `xs`) for reliability or closed/private groups. Format: `c_user=...; xs=...; datr=...; fr=...`. |

#### Example input

```json
{
  "startUrls": ["https://www.facebook.com/groups/cheapmealideas/"],
  "resultsLimit": 25,
  "viewOption": "RECENT_ACTIVITY",
  "excludeKeywords": ["giveaway", "spam"],
  "minLikes": 5,
  "onlyPostsNewerThan": "30 days",
  "includeComments": true,
  "maxComments": 10
}
```

#### Common pitfall

`resultsLimit` counts **top-level posts only** — comments pushed when `includeComments` is `true` are billed and returned separately and do not reduce your post count. A second, less obvious one: `excludeKeywords`, `minLikes`, `minComments`, `minShares`, `onlyPostsNewerThan`, and `onlyPostsOlderThan` are all applied **before** a post is charged, so a run with strict filters and a small target group may exhaust the feed (the Actor stops after 10 fetch attempts, `MAX_ATTEMPTS` in `src/main.py`) before reaching `resultsLimit` — this is expected behavior, not a bug, when the group simply doesn't have that many matching posts.

### 🔽 Output sample

Output is typed, normalized JSON pushed to the Apify dataset — export as JSON, CSV, Excel, or XML from the Apify Console or API like any Apify dataset. Two row shapes exist: post rows (`type: "post"`) and, only when `includeComments` is on, comment rows (`type: "comment"`) interleaved directly after their parent.

**The Apify Store's default dataset view shows 34 fields; the actual post row carries up to 43.** Fields present on every row but excluded from the default view: `facebookUrl`, `inputUrl`, `postId`, `legacyId`, `timestamp`, `topComments`, and `facebookId`. Two AI-only fields are also written to every row but missing from the view: `aiLanguage` and `aiBuyIntent` — both are computed and charged for like the other AI fields when `aiEnhancement` is on, they simply aren't columns in the default table. Switch the dataset view or pull raw items via the API to see them.

Full post row example (post-type fields, values illustrative):

```json
{
  "type": "post",
  "isChild": false,
  "parentId": null,
  "facebookUrl": "https://www.facebook.com/groups/cheapmealideas/",
  "inputUrl": "https://www.facebook.com/groups/cheapmealideas/",
  "url": "https://www.facebook.com/groups/cheapmealideas/permalink/1204545088344463/",
  "id": "Uzpmc3RvcnkuUzoxOjEyMDQ1NDUwODgzNDQ0NjM",
  "postId": "1204545088344463",
  "legacyId": "1204545088344463",
  "feedbackId": "ZmVlZGJhY2s6MTIwNDU0NTA4ODM0NDQ2Mw==",
  "timestamp": 1774702800,
  "date": "2026-03-28T14:00:00.000Z",
  "scrapedAt": "2026-07-26T09:12:03.000Z",
  "user": {
    "id": "100063669491743",
    "name": "Jane Doe",
    "profileUrl": "https://www.facebook.com/jane.doe",
    "profilePicture": "https://scontent.fbcdn.net/...",
    "__typename": "User"
  },
  "text": "Anyone have a good vegan chili recipe? Email me at chef@example.com if you want to swap notes! #mealprep",
  "postType": "photo",
  "reactionLikeCount": 42,
  "reactionLoveCount": 11,
  "reactionHahaCount": 3,
  "reactionWowCount": 0,
  "reactionSadCount": 0,
  "reactionAngryCount": 0,
  "reactionCareCount": 2,
  "topReactionsCount": 58,
  "likesCount": 61,
  "commentsCount": 14,
  "sharesCount": 2,
  "attachments": [
    {"type": "photo", "__typename": "Photo", "id": "...", "thumbnail": "...", "image": {"uri": "...", "height": 960, "width": 720}, "ocrText": null}
  ],
  "topComments": [
    {"id": "...", "legacyId": "...", "commentUrl": "...", "date": "2026-03-28T14:20:00.000Z", "text": "This looks amazing!", "profileId": "...", "profileName": "...", "profileUrl": "...", "profilePicture": "...", "likesCount": 3, "threadingDepth": 0}
  ],
  "facebookId": "106823902845",
  "groupTitle": "Cheap Meal Ideas",
  "hashtags": ["mealprep"],
  "mentions": [],
  "links": [],
  "emails": ["chef@example.com"],
  "phones": [],
  "engagementScore": 109,
  "postAgeDays": 120,
  "aiSentiment": "positive",
  "aiLanguage": "en",
  "aiTopics": ["recipes", "meal prep"],
  "aiBuyIntent": false,
  "aiLeadSignal": "low"
}
```

Comment row example (only present when `includeComments` is `true`, and only carries these 14 fields — separate shape from the post row):

```json
{
  "type": "comment",
  "isChild": true,
  "isReply": false,
  "parentId": "1204545088344463",
  "parentUrl": "https://www.facebook.com/groups/cheapmealideas/permalink/1204545088344463/",
  "facebookUrl": "https://www.facebook.com/groups/cheapmealideas/",
  "id": "Y29tbWVudDoxMjA0NTQ1MDg4MzQ0NDYzXzk4NzY1NDMy",
  "legacyId": "9876543210",
  "url": "https://www.facebook.com/groups/cheapmealideas/permalink/1204545088344463/?comment_id=9876543210",
  "date": "2026-03-28T14:22:00.000Z",
  "text": "Try adding smoked paprika, game changer.",
  "user": {"id": "...", "name": "John Smith", "profileUrl": "...", "profilePicture": "..."},
  "likesCount": 5,
  "threadingDepth": 0
}
```

Comment rows are also mirrored to a per-run child dataset named `comments-<runId>` for teams who want posts and comments in separate datasets.

Note on group metadata: `groupTitle` on every row is the group's display name only. Aggregate group metadata collected once per run — member count, privacy setting, description — is written to the run's key-value store under the `RUN_SUMMARY` key, not as a per-row dataset field.

### How do you filter and target specific posts?

This is the Actor's most useful skill: narrowing a group's entire feed down to the handful of posts you actually need, without paying for the rest. Four axes matter.

**Free-text keywords vs. structural search.** `keywords` and `excludeKeywords` are case-insensitive substring matches against post `text`, applied client-side after extraction — accurate but literal (no stemming or fuzzy match). `searchGroupKeyword` runs a second, earlier client-side pass inside the feed fetch itself; per the schema, when scraping without `cookieString`, single or double-letter search terms return more complete results than full words, because Facebook's own unauthenticated search behaves the same way.

**Scope precision via date range.** `onlyPostsNewerThan` and `onlyPostsOlderThan` accept either an absolute date (`2024-01-15`) or a relative expression (`"2 weeks"`, `"3 months"`, `"1 year"`). Combine both to bound a window; combine either with `searchGroupYear` for coarse year-level scoping before applying a tighter date filter.

**Quality thresholds.** `minLikes`, `minComments`, and `minShares` drop low-engagement posts before they're charged — useful for cutting straight to the posts an audience actually reacted to, rather than paying to review every post in a busy group.

**Volume and feed order.** `resultsLimit` caps top-level posts (not comments); `viewOption` decides which posts you see first — `CHRONOLOGICAL` for a complete time-ordered archive, `RECENT_ACTIVITY` for what's currently being discussed, `TOP_POSTS` for the highest-engagement content, or `CHRONOLOGICAL_LISTINGS` for Buy/Sell-style groups where the feed is organized around listings rather than discussion.

Three real examples:

```json
{ "startUrls": ["https://www.facebook.com/groups/cheapmealideas/"], "viewOption": "TOP_POSTS", "minLikes": 20, "resultsLimit": 15 }
```

```json
{ "startUrls": ["https://www.facebook.com/groups/localmarketplace/"], "viewOption": "CHRONOLOGICAL_LISTINGS", "resultsLimit": 50 }
```

```json
{ "startUrls": ["https://www.facebook.com/groups/cheapmealideas/"], "keywords": ["recipe"], "excludeKeywords": ["giveaway"], "onlyPostsNewerThan": "2024-01-01", "onlyPostsOlderThan": "2024-12-31" }
```

### ▶️ Want to try other Facebook scrapers?

| Scraper | What it extracts |
|---|---|
| 🔎 Facebook Groups Search Scraper Plus | Searches and filters Facebook groups (SimpleAPI account) |
| 📘 Facebook Group Posts Scraper Pro | Public Facebook group posts and comments, a separate implementation from this Actor (SimpleAPI account) |
| 📸 Facebook Photos Scraper | Photos from Facebook profiles and Pages (SimpleAPI account) |
| 👥 Instagram Followers Scraper | Follower lists and profile data from Instagram accounts (SimpleAPI account) |
| 📇 Instagram Profile Contact Enricher | Public contact details (email/phone) from Instagram profiles (SimpleAPI account) |
| 🔎 LinkedIn Profile Contact Lookup | Public contact details from LinkedIn profile URLs (SimpleAPI account) |

### How to extract Facebook group data programmatically

This Actor runs on Apify like any other Actor — start it from the Apify Console, via a schedule, or programmatically through `apify-client`, and read results back as JSON from the run's dataset.

#### Python example

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_API_TOKEN>")

run = client.actor("facebook-groups-posts-comments-scraper").call(run_input={
    "startUrls": ["https://www.facebook.com/groups/cheapmealideas/"],
    "resultsLimit": 25,
    "minLikes": 5,
    "onlyPostsNewerThan": "30 days",
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["groupTitle"], item["text"][:80], item["engagementScore"])
```

#### Export to spreadsheets or CRM

From the Apify Console, export the dataset directly as CSV or Excel — `text`, `emails`, `phones`, `engagementScore`, and `aiLeadSignal` (when AI enrichment is on) map cleanly to spreadsheet or CRM columns for lead review, with `url` as the record's canonical link back to the source post.

### Is it legal to scrape Facebook groups?

Scraping publicly accessible Facebook group posts is generally lawful — public posts are, by definition, visible to anyone without logging in, and courts in the US (notably *hiQ Labs v. LinkedIn*) have held that scraping publicly available web data does not violate the Computer Fraud and Abuse Act. That said, post rows in this dataset carry personal data — author names, profile URLs, profile pictures, and comment text from identifiable individuals — so **GDPR (if you or your data subjects are in the EU/UK) and CCPA (if they're in California) govern how you may store, process, and reuse it**, not just Facebook's own Terms of Service. You need a lawful basis for processing personal data at scale, and Facebook's Terms separately restrict automated data collection regardless of the data's legal status. Consult legal counsel for commercial applications involving bulk storage of personal data.

### ❓ FAQ

#### What happens if a post or comment is deleted after I scrape it?

The Actor returns data exactly as it existed on the group's public feed at request time. It does not track a post after scraping, so a deleted or edited post afterward will not update your dataset — re-run the Actor to get current state.

#### Can I get comments along with the posts?

Yes — set `includeComments` to `true` and comments are interleaved right after their parent post, capped by `maxComments` (default 10, `0` = no cap). One caveat worth knowing upfront: comments come from what Facebook's own feed response already includes for that post (`get_comments_from_payload` in `src/extractor.py`), not from a separate paginated comment-thread request — so `maxComments` can only return up to what Facebook bundled with the post, and threaded replies beneath a top-level comment are not fetched (every comment row is written with `isReply: false`; there is no reply-nesting logic in the source). Every post row also always carries up to 2 preview comments in its `topComments` field regardless of the `includeComments` setting.

#### How accurate is the contact data extracted from posts?

`emails`, `phones`, `mentions`, and `links` are extracted directly from each post's public `text` with regular expressions — they are exactly what the poster wrote, not verified or deduplicated against an external directory. Treat extracted phone numbers and emails as leads to validate, not confirmed contact records.

#### How many posts can I get per run?

There is no maximum enforced by the input schema — `resultsLimit` has a minimum of 1 but no stated upper bound. In practice, the run stops itself after 10 fetch attempts (`MAX_ATTEMPTS` in `src/main.py`) even if `resultsLimit` hasn't been reached, which happens when the group's feed is exhausted or strict filters (`excludeKeywords`, minimum thresholds, date range) leave nothing left to match.

#### What's the most useful filter for narrowing down a group's feed?

`viewOption: "CHRONOLOGICAL_LISTINGS"` is the one most competitors don't expose — it switches the feed to Facebook's Buy/Sell listing order for marketplace-style groups, instead of forcing you to filter a discussion-ordered feed for listing posts after the fact.

#### Does this Actor work with Claude, ChatGPT, and AI agent frameworks?

Yes, as a standard HTTP-callable Apify Actor — any agent framework that can call the Apify API (start a run, poll status, read dataset items) can drive it. The Actor also has its own optional `aiEnhancement` input that classifies post text with your choice of Anthropic, OpenAI, Google, xAI, DeepSeek, Perplexity, or Mistral models via your own API key, independent of whichever agent framework is orchestrating the run.

#### How does this compare to other Facebook group scrapers?

As observed on the Apify Store on 2026-07-26: `unseenuser/fb-groups` returns posts only (no group-search, no group metadata, no private-group access by design) at $4 per 1,000 results with a 50-result free-plan cap, and sells full comment threads and replies as separate billed add-ons. `whoareyouanas/facebook-group-scraper` covers both groups and Pages from one input and claims cookie-based access to private groups plus a full 6-reaction breakdown and group/Page metadata capture. `alien_force/facebook-posts-comments-scraper` takes individual post URLs rather than group URLs, so it scrapes one known post at a time instead of paginating a group's feed, and is priced as a flat monthly subscription rather than pay-per-event. This Actor differs by pairing full group-feed pagination and a 7-reaction breakdown with derived contact-signal fields (`emails`, `phones`, `mentions`) and optional multi-provider AI classification (sentiment, topics, buy-intent, lead signal) computed on your own key, on pay-per-event pricing.

#### Can I use this without a Facebook API key or developer account?

Yes for public groups — no Facebook account, API key, or developer app is required; the Actor works from a public group URL alone. `cookieString` (your own logged-in session cookies) is only needed to improve reliability against login walls or to access closed/private groups you already belong to.

### Conclusion

Facebook Groups Posts & Comments Scraper turns a public group URL into a structured, filterable dataset of posts and — on request — their comments, with a full reaction breakdown, derived contact signals, and optional AI classification layered on top. It's built for teams who need a repeatable, schema-stable way to pull group discussion data without maintaining Facebook's internal GraphQL contract themselves, priced per result so a small monitoring run and a large research pull scale the same way. Start it from the Apify Console with a group URL and `resultsLimit`, or drive it from `apify-client` as shown above.

# Actor input Schema

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

Public Facebook group URLs to scrape. Example: https://www.facebook.com/groups/cheapmealideas/. Group numeric IDs also work.

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

Maximum number of top-level POSTS to scrape. Comments are additional and are NOT counted against this limit. Example: 20. Default is 20.

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

How the group feed is ordered. CHRONOLOGICAL = oldest→newest, RECENT\_ACTIVITY = most active, TOP\_POSTS = most engaging, CHRONOLOGICAL\_LISTINGS = Buy/Sell listings.

## `keywords` (type: `array`):

Keep only posts whose text contains at least one of these words (case-insensitive). Leave empty to keep all posts. Example: \["recipe", "vegan"].

## `searchGroupKeyword` (type: `string`):

Client-side search by letter/word inside the group. Without login, single/double letters work best. Example: "a".

## `searchGroupYear` (type: `string`):

Keep only posts from a specific year, e.g. "2024". Best combined with the search term above.

## `excludeKeywords` (type: `array`):

Drop posts whose text contains any of these words (case-insensitive). Example: \["spam", "giveaway"].

## `minLikes` (type: `integer`):

Drop posts with fewer likes/reactions than this. Example: 10. Default 0 (no minimum).

## `minComments` (type: `integer`):

Drop posts with fewer comments than this. Default 0 (no minimum).

## `minShares` (type: `integer`):

Drop posts with fewer shares than this. Default 0 (no minimum).

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

Keep only posts on/after this date. Absolute (2024-01-15) or relative ("2 weeks", "30 days", "1 month").

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

Keep only posts on/before this date. Absolute (2024-01-15) or relative ("3 months", "1 year"). Combine with 'newer than' for a date range.

## `includeComments` (type: `boolean`):

When on, collect the available comments for each scraped post. Comments are additional to the post limit and capped by 'Max comments per post'.

## `maxComments` (type: `integer`):

Cap the number of comments collected per post. Example: 10. Set 0 for no per-post cap. Default 10.

## `aiEnhancement` (type: `boolean`):

When on AND an API key is provided, each post text is classified. Off by default.

## `aiModel` (type: `string`):

Provider auto-detected from the name: claude-*=Anthropic, gpt-*/o1/o3=OpenAI, gemini-*=Google, grok-*=xAI, deepseek-*=DeepSeek, sonar*=Perplexity, mistral-\*=Mistral. Cheaper mini/flash/haiku models are recommended for classification.

## `aiApiKey` (type: `string`):

Your LLM provider API key (secret). Used only when AI enrichment is enabled. Env-var fallbacks per provider: ANTHROPIC\_API\_KEY / OPENAI\_API\_KEY / GEMINI\_API\_KEY / XAI\_API\_KEY / DEEPSEEK\_API\_KEY / PERPLEXITY\_API\_KEY / MISTRAL\_API\_KEY.

## `maxRequestRetries` (type: `integer`):

Retries per GraphQL request before giving up. Example: 3. Default 3.

## `proxyConfiguration` (type: `object`):

Proxy used for requests. Your choice is honored. If left unset, Apify residential proxy is used by default (recommended — Facebook blocks datacenter IPs).

## `cookieString` (type: `string`):

Optional logged-in session cookies (must include c\_user + xs) to improve reliability and access closed/private groups. Format: c\_user=...; xs=...; datr=...; fr=.... Use a secondary account.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.facebook.com/groups/cheapmealideas/"
  ],
  "resultsLimit": 5,
  "viewOption": "CHRONOLOGICAL",
  "keywords": [],
  "searchGroupKeyword": "",
  "searchGroupYear": "",
  "excludeKeywords": [],
  "minLikes": 0,
  "minComments": 0,
  "minShares": 0,
  "includeComments": false,
  "maxComments": 10,
  "aiEnhancement": false,
  "aiModel": "claude-haiku-4-5",
  "maxRequestRetries": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

All scraped items in the Actor's default dataset.

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

A single JSON record written once the run finishes: parents pushed, comments pushed, group metadata (name, member count, privacy), and the per-run child comments dataset name when comments were collected.

# 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/cheapmealideas/"
    ],
    "resultsLimit": 5,
    "onlyPostsNewerThan": "",
    "onlyPostsOlderThan": "",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("simpleapi/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/cheapmealideas/"],
    "resultsLimit": 5,
    "onlyPostsNewerThan": "",
    "onlyPostsOlderThan": "",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("simpleapi/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/cheapmealideas/"
  ],
  "resultsLimit": 5,
  "onlyPostsNewerThan": "",
  "onlyPostsOlderThan": "",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call simpleapi/facebook-groups-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,simpleapi/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/d2sLO4HrqBKincPHu/builds/pS3WbtN5eawdoCRIQ/openapi.json
