# Facebook Post & Group Comment Thread Extractor (`vexaloom-labs/facebook-post-and-group-comment-thread-extractor`) Actor

Get comments and nested reply threads from public Facebook posts, Watch videos, photos, and group posts, with per-reaction-type counts, author details, and date filtering. No login or cookies required.

- **URL**: https://apify.com/vexaloom-labs/facebook-post-and-group-comment-thread-extractor.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Social media, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Post & Group Comment Thread Extractor

Get public comments and full reply threads from Facebook posts, photos, and public group posts. Returns comment text, author details, per-reaction-type counts, timestamps, attachment and mention data, plus the post's own comment totals — no login or cookies required.

### What this actor does

- Scrapes comments from public Facebook posts, photos, reels, videos, and public group posts, with full pagination (thousands of comments per post when Facebook serves them)
- Reels and videos are followed through Facebook's canonical video permalink, with a strict check that the returned thread belongs to the requested video
- Follows reply threads and includes nested replies (direct replies plus deeper nesting when Facebook returns it)
- Three comment orderings: Facebook's ranked view, all comments, or newest first
- Captures author details (name, profile picture, profile URL, ID), reaction totals and a per-reaction-type breakdown
- Surfaces the parent post's context on every row: page name, post text, Facebook's total comment count, and the count visible to anonymous visitors
- Filters results to comments posted after a given date
- `reactionBreakdown`, `attachmentType`, `hashtags`, `mentions`, `groupCommentInfo` and similar optional fields are only added when Facebook's data carries them

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array | – | Required. Facebook post, photo, or public group post URLs. Comment permalinks (`?comment_id=`) are resolved to their post; legacy `story.php`, `permalink.php` and share links are normalized automatically |
| `maxItems` | integer | `100` | Maximum number of comment rows (including replies) per URL; 1–10,000 |
| `onlyCommentsNewerThan` | string | – | Date filter: `YYYY-MM-DD`, full ISO 8601, or relative (`1 day`, `2 weeks`, `3 months`, `1 year`). Comments are fetched newest-first so pagination stops early |
| `commentsMode` | string | `ALL` | Comment ordering: `ALL` (Facebook's unfiltered view), `NEWEST` (newest first), or `MOST_RELEVANT` (Facebook's ranked view) |
| `includeNestedComments` | boolean | `true` | Also collect reply rows under top-level comments (with parent/reply metadata). Turn off for faster, top-level-only runs |
| `maxReplyDepth` | integer | `1` | Reply nesting depth to collect: `1` = direct replies only, `2`+ also includes deeper nested replies; 1–5 |

At least one URL in `startUrls` is required. Provide URLs as plain strings, `{ "url": "..." }` objects, or several URLs separated by spaces/newlines in one string.

#### Supported URL formats

- **Posts**: `https://www.facebook.com/PageName/posts/1234567890`
- **Posts (pfbid)**: `https://www.facebook.com/PageName/posts/pfbid0...`
- **Legacy permalinks**: `story.php?story_fbid=...&id=...`, `permalink.php?...` (normalized automatically)
- **Photos**: `https://www.facebook.com/photo?fbid=1234567890`, `photo.php?fbid=...`, `PageName/photos/...`
- **Reels**: `https://www.facebook.com/reel/1234567890` (resolved through Facebook's video permalink)
- **Videos**: `https://www.facebook.com/PageName/videos/1234567890`, `https://www.facebook.com/watch?v=1234567890`
- **Public group posts**: `https://www.facebook.com/groups/groupname/permalink/1234567890/`, `groups/<id>/posts/<id>`
- **Comment permalinks**: any post URL with `?comment_id=...` — the parent post's thread is returned
- **Share links**: `https://www.facebook.com/share/p/...` (resolved through Facebook's redirect)
- **Mobile URLs**: `m.facebook.com/...` links are normalized to `www`

Some individual videos are restricted by Facebook and do not expose their comment thread to anonymous visitors; the actor reports that per video instead of guessing (see Limitations).

#### Example input

```json
{
  "startUrls": [
    { "url": "https://www.facebook.com/NASA/posts/1234567890" },
    { "url": "https://www.facebook.com/groups/pythondevelopers/posts/28449935087979081/" }
  ],
  "maxItems": 500,
  "commentsMode": "NEWEST",
  "includeNestedComments": true,
  "maxReplyDepth": 2,
  "onlyCommentsNewerThan": "3 months"
}
```

### Output

Each dataset row is one comment or reply.

| Field | Type | Description |
| --- | --- | --- |
| `facebookUrl` | string | URL of the post the comment belongs to |
| `commentUrl` | string | Direct permalink to the comment |
| `commentId` | string | Numeric (legacy) comment ID |
| `id` | string | Internal relay ID |
| `feedbackId` | string | Feedback ID for the comment |
| `date` | string | Comment timestamp (ISO 8601, UTC) |
| `text` | string | Full comment text; `[Attachment]` for media-only comments |
| `profilePicture` | string | Author's profile picture URL |
| `profileId` | string | Author's numeric or pfbid profile ID |
| `profileName` | string | Author's display name |
| `profileUrl` | string | Author's profile URL (when Facebook exposes one) |
| `likesCount` | integer | Total reactions on the comment |
| `reactionBreakdown` | object | Per-reaction-type counts (`like`, `love`, `care`, `haha`, `wow`, `sad`, `angry`, `other`); only reaction types with a non-zero count are included |
| `threadingDepth` | integer | `0` = top-level comment, `1`+ = reply depth |
| `commentsCount` | integer | Number of replies (0 for replies themselves) |
| `replyToCommentId` | string | Parent comment's ID; for deep replies where Facebook withholds the direct parent, the thread's top-level comment ID |
| `directParentCommentId` | string | Facebook's direct parent pointer, when the response includes it |
| `facebookId` | string | Numeric story/post ID |
| `inputUrl` | string | The URL exactly as provided in the input |
| `postTitle` | string | Page name that published the post (kept for integrations) |
| `pageName` | string | Page name that published the post |
| `postText` | string | The post's own text (when available) |
| `postCommentsCount` | integer | Facebook's total comment count for the post |
| `postVisibleCommentsCount` | integer | Comments Facebook serves to anonymous visitors for the selected ordering |
| `gender` | string | Author's gender, when Facebook exposes it |
| `spamDisplayMode` | string | Facebook's own spam classification for the comment |
| `isDisabled` | boolean | Whether the comment was hidden/removed by moderation |
| `isHiddenByViewer` | boolean | Whether Facebook hides the comment from anonymous viewers |
| `attachmentType` | string | `gif`, `sticker`, `image`, `video` or `attachment` for media comments |
| `attachmentUrl` | string | Media URL for attachment comments — rehosted into Apify's key-value store so it does not expire |
| `attachmentUrlOriginal` | string | Original signed Facebook CDN URL of the attachment (expires after a few days) |
| `attachmentUrlExpiresAt` | string | Expiry time of the original signed attachment URL (ISO 8601) |
| `hashtags` | array | Hashtags used in the comment (without `#`) |
| `mentions` | array | Profiles mentioned in the comment (without `@`) |
| `groupCommentInfo` | object | Group posts only: `groupId`, `isAuthorAnonymous`, `isAuthorNickname`, `isAuthorWithMemberProfile` |
| `groupId` | string | Group ID for group-post comments |

#### Example output

```json
{
  "facebookUrl": "https://www.facebook.com/NASA/posts/pfbid025kwyoB...",
  "commentUrl": "https://www.facebook.com/NASA/posts/pfbid02BnSBZiZ...?comment_id=809453025548580",
  "commentId": "809453025548580",
  "id": "Y29tbWVudDoxNDg3MDE3OTEyNzkzNTgwXzgwOTQ1MzAyNTU0ODU4MA==",
  "feedbackId": "ZmVlZGJhY2s6MTQ4NzAxNzkxMjc5MzU4MF84MDk0NTMwMjU1NDg1ODA=",
  "date": "2026-03-26T23:31:17.000Z",
  "text": "Ah I remember the moon landing of 1969 on the eve of my 9th birthday!",
  "profileId": "pfbid0246xYyRVsKr1v7nDryWNgA4iynY2GKcEVzuVzx2knUikMNEzUNN5RtWsiX945JRt4l",
  "profileName": "Gwen Weidell",
  "profileUrl": "https://www.facebook.com/gwen.weidell.9",
  "likesCount": 69,
  "reactionBreakdown": { "like": 54, "haha": 9, "love": 4, "care": 1, "wow": 1 },
  "threadingDepth": 0,
  "commentsCount": 31,
  "facebookId": "1487017912793580",
  "inputUrl": "https://www.facebook.com/NASA/posts/pfbid025kwyoB...",
  "postTitle": "NASA - National Aeronautics and Space Administration",
  "pageName": "NASA - National Aeronautics and Space Administration",
  "postText": "Our Artemis II crew's journey around the Moon is on the horizon...",
  "postCommentsCount": 1083,
  "postVisibleCommentsCount": 813,
  "gender": "FEMALE",
  "spamDisplayMode": "none",
  "isDisabled": false,
  "isHiddenByViewer": false
}
```

### Use cases

- **Brand monitoring** — track what people say about your brand across public posts
- **Market research** — analyze audience sentiment and topics at scale (pipe `text` into your own NLP/LLM step)
- **Community insights** — understand how audiences react to news, announcements, or campaigns
- **Competitor analysis** — monitor comment threads on competitor pages and public groups
- **Lead generation** — identify engaged users from comment activity
- **Moderation triage** — use Facebook's own spam/disabled/hidden signals plus reply structure to prioritize review

### Tips for best results

1. **Set `maxItems` per URL** — it caps runtime and dataset size; the actor also stops on its own when Facebook runs out of comments
2. **Use `NEWEST` for fresh-comment monitoring** — with `onlyCommentsNewerThan`, pagination stops as soon as an older comment appears, saving requests
3. **Keep `includeNestedComments` on** for full threads; turn it off for fast top-level-only exports
4. **Test with 1–2 URLs first**, then scale up
5. **Public content only** — posts, photos and groups must be visible without login

### Limitations

- **Some videos are restricted per video**: Facebook serves reel and video comment threads anonymously for most public videos, but a minority return no thread at all; those are reported as "not served to anonymous visitors" instead of returning unrelated content (every returned thread is verified against the requested video id).
- **Anonymous visibility ceiling**: Facebook serves only part of a post's comments to visitors without a session (for example, a post reporting 1,083 comments served 813 to anonymous viewers). The dataset reports what Facebook actually served; `postCommentsCount` vs `postVisibleCommentsCount` shows the difference, and the run status message notes it.
- **Some photo pages are login-walled from cloud networks**: photo URLs return the photo's own comment thread when it is a standalone photo. When the photo belongs to a post and Facebook blocks the anonymous photo viewer, the actor reports that a logged-in session would be required — paste the post URL that contains the photo to scrape its comments.
- Some authors have no public profile URL; those rows simply omit `profileUrl`.
- Attachment media (`attachmentUrl`) is rehosted so it does not expire; original signed URLs stay in `attachmentUrlOriginal`. Profile pictures are Facebook CDN links that expire after a few days (per-row avatars are not rehosted — see FAQ).
- Private, friends-only, and restricted content cannot be accessed.
- Occasionally Facebook changes its internal comment API; the actor discovers the current API identifiers at runtime and falls back to the last known ones, but comment ordering and reply visibility are always Facebook's decision.

### FAQ

**Q: Do I need a Facebook account or cookies?**
A: No. The actor reads public content without any login. Nothing in the input asks for credentials.

**Q: Do I need to configure a proxy?**
A: No. The actor picks its route automatically, starting with a direct connection and escalating only when Facebook blocks it. There is no proxy input.

**Q: Why are some comments missing?**
A: Facebook shows anonymous visitors a filtered view of large threads ("Most relevant" ordering may hide some comments) and withholds some replies. The actor reports what Facebook serves and never fabricates rows.

**Q: Can I scrape comments from Reels or videos?**
A: Yes for most public reels and videos — they are followed through Facebook's own canonical video permalink and the returned thread is verified to match the requested video. A minority of videos are restricted by Facebook and return no thread to anonymous visitors; those are reported clearly instead of returning unrelated comments.

**Q: I passed a photo URL and got 0 results. Why?**
A: Facebook serves some photo pages only to logged-in sessions (this depends on the network the actor runs from). When that happens the actor says so; paste the post URL that contains the photo instead — post URLs are always supported.

**Q: How many comments can I scrape per post?**
A: Up to `maxItems` (10,000 by default max), and often Facebook's own visibility limit comes first. Exploding threads of thousands of comments are paginated 10 at a time until `maxItems` or Facebook's last page.

**Q: What does `threadingDepth` mean?**
A: `0` is a top-level comment, `1` is a direct reply, `2`+ is a deeper reply. Use `maxReplyDepth` to control how deep the actor collects.

**Q: Why are profile picture URLs temporary?**
A: They are signed Facebook CDN links that expire after a few days. Rehosting every row's avatar would dominate run bandwidth, so the actor keeps the CDN link for profile pictures while attachment media (rarer) is rehosted permanently.

**Q: Is this affiliated with Facebook or Meta?**
A: No. This is an independent, third-party actor that reads publicly accessible comments; it is not affiliated with, endorsed by, or operated by Meta Platforms, Inc.

# Actor input Schema

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

Facebook post, photo, reel, video, Watch, or public group post URLs (one or more). Comment permalinks (URLs containing ?comment_id=) are accepted and resolved to their post; legacy story.php, permalink.php and share links are normalized automatically. Most public reels and videos are supported; a minority restricted by Facebook return no thread and are reported.

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

Maximum number of comment rows (including replies) to collect per URL. The run also stops when Facebook has no more comments or replies to serve.

## `onlyCommentsNewerThan` (type: `string`):

Limit results to comments posted after this date. Accepts YYYY-MM-DD, a full ISO 8601 timestamp (e.g. 2024-06-15T12:00:00Z), or a relative value like '1 day', '2 weeks', '3 months', '1 year'. When set, comments are fetched newest-first so the filter can stop as soon as an older comment is reached. Leave blank to disable date filtering.

## `commentsMode` (type: `string`):

Which comment view to scrape: MOST_RELEVANT (Facebook's ranked view), NEWEST (all comments, newest first), or ALL (Facebook's unfiltered ranked view, including comments its ranked view may hide).

## `includeNestedComments` (type: `boolean`):

If true, also scrape reply rows under top-level comments (with parent/reply metadata). If false, only top-level comments are collected (faster).

## `maxReplyDepth` (type: `integer`):

How deep into reply threads to collect: 1 = direct replies to top-level comments only, 2 or more = also include deeper nested replies. Facebook serves reply threads with up to two nesting levels to anonymous visitors.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/NASA/posts/pfbid025kwyoBjhFGUC22fC4Sies56D5xvd65ZHR5PYjHhc7XSzqGsxuWq2uDVriZNxWbeol"
    }
  ],
  "maxItems": 5,
  "commentsMode": "MOST_RELEVANT",
  "includeNestedComments": true,
  "maxReplyDepth": 1
}
```

# Actor output Schema

## `comments` (type: `string`):

Dataset containing all scraped Facebook comments

# 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": [
        {
            "url": "https://www.facebook.com/NASA/posts/pfbid025kwyoBjhFGUC22fC4Sies56D5xvd65ZHR5PYjHhc7XSzqGsxuWq2uDVriZNxWbeol"
        }
    ],
    "maxItems": 5,
    "commentsMode": "MOST_RELEVANT",
    "includeNestedComments": true,
    "maxReplyDepth": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/facebook-post-and-group-comment-thread-extractor").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": [{ "url": "https://www.facebook.com/NASA/posts/pfbid025kwyoBjhFGUC22fC4Sies56D5xvd65ZHR5PYjHhc7XSzqGsxuWq2uDVriZNxWbeol" }],
    "maxItems": 5,
    "commentsMode": "MOST_RELEVANT",
    "includeNestedComments": True,
    "maxReplyDepth": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/facebook-post-and-group-comment-thread-extractor").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": [
    {
      "url": "https://www.facebook.com/NASA/posts/pfbid025kwyoBjhFGUC22fC4Sies56D5xvd65ZHR5PYjHhc7XSzqGsxuWq2uDVriZNxWbeol"
    }
  ],
  "maxItems": 5,
  "commentsMode": "MOST_RELEVANT",
  "includeNestedComments": true,
  "maxReplyDepth": 1
}' |
apify call vexaloom-labs/facebook-post-and-group-comment-thread-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/facebook-post-and-group-comment-thread-extractor"
        }
    }
}
```

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/gCko1ssooveQ4r5cK/builds/a6Lt5hJbeDo9CracC/openapi.json
