# Facebook Comments Scraper — Posts & Reels (`data-wan/facebook-comments`) Actor

Extract public Facebook post and Reel comments with optional replies. Set separate comment and reply limits, preserve thread links, and collect text, dates, authors and reactions when available. Export JSON, CSV or Excel with Apify for your own analysis.

- **URL**: https://apify.com/data-wan/facebook-comments.md
- **Developed by:** [Data Wan](https://apify.com/data-wan) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 2 monthly users, 98.7% runs succeeded, 3 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.28 / 1,000 saved comment or replies

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 Comments Scraper for posts and Reels

Collect comments and optional replies from direct public Facebook post and Reel links. Set separate limits for conversation coverage and reply detail, then export structured data for your own analysis.

#### Why choose this Actor?

- **Choose breadth and detail separately.** Set independent limits for top-level comments and replies per parent, choosing wider coverage or more detail in each conversation.
- **Follow the discussion.** Parents and replies stay grouped, with thread and direct-parent links when known, so related responses are easier to interpret.
- **Get context for your analysis.** Author details, dates, reactions, and media links accompany the text when available; missing information stays `null`.
- **Start with public post links or post IDs.** Run through Apify without a Facebook account, login cookies, or proxy configuration.

For one post, the controls work like this:

| Top-level comments | Replies | Maximum rows |
|---|---|---|
| 20 | Off | 20 |
| 20 | Up to 3 per parent | 80 |
| 20 | Up to 10 per parent | 220 |

The reply allowance counts all selected descendants under each top-level comment across nesting levels. These are ceilings; Facebook may expose fewer results. Public group and event posts are supported; private groups and other private content are not, and the Actor never logs in.

### Use cases

- Review audience and campaign feedback, including recurring questions.
- Examine online conversations and audience engagement using observed thread links and reactions.
- Supply comment text to your own sentiment-analysis tools; this Actor collects data, while your tool performs the analysis.

For recurring collection, configure an Apify schedule with your chosen post links and limits. The Actor does not discover posts or monitor them automatically.

### Quick start

1. Add direct public post links to **Post URLs**, numeric post IDs to **Post IDs**, or both: 1–100 inputs in total.
2. Choose **Maximum comments per post** and a sorting option. Start with a small selection to inspect the results.
3. The prefilled example enables **Include replies**. Turn it off for top-level comments only, or keep it enabled and set **Maximum replies per comment**.
4. Start the run. Open **Output → Comments** to inspect the dataset and **Run summary** to check its outcome.
5. Use **Export** to download JSON, CSV, or Excel. Choose JSON to retain the nested reaction and attachment data.

Use links to individual posts, Reels, videos, or photos: `/posts/...`, `/permalink.php?story_fbid=...`, `/story.php?story_fbid=...`, `/reel/...`, `/videos/...`, `/watch?v=...`, `/photo?fbid=...`, or `/photos/...`, and public group or event post links with a numeric post ID: `/groups/<group>/posts/<id>`, `/groups/<group>/permalink/<id>`, or `/events/<event>/?post_id=<id>`. Paste the full URL, including its query parameters. Page/profile home links, group links with a `pfbid` post ID, and share/short links are not supported inputs. Put bare numeric post IDs in **Post IDs**, not in **Post URLs**.

#### Top-level comments only

This collects up to 20 top-level comments from one public Reel. Replies are excluded.

```json
{
  "postUrls": ["https://www.facebook.com/reel/1038785062517728"],
  "maxCommentsPerPost": 20,
  "sort": "all",
  "includeReplies": false
}
```

#### By post ID

This collects up to 100 top-level comments from each of two posts named by their numeric IDs.

```json
{
  "postIds": ["9000000000000001", "9000000000000002"],
  "maxCommentsPerPost": 100
}
```

#### Comments with bounded replies

This selects up to 20 top-level comments and up to 10 replies under each: **at most 220 rows per post**. The reply allowance includes nested descendants across all levels of that thread; it does not reset for each level or nested comment.

```json
{
  "postUrls": ["https://www.facebook.com/reel/1038785062517728"],
  "maxCommentsPerPost": 20,
  "sort": "all",
  "includeReplies": true,
  "maxRepliesPerComment": 10
}
```

### Input options

| Field | Default | What to set |
|---|---|---|
| `postUrls` | `[]` | Array of public post URLs. Together with `postIds`: 1–100 inputs. |
| `postIds` | `[]` | Array of numeric post IDs as strings (digits only). Together with `postUrls`: 1–100 inputs. |
| `maxCommentsPerPost` | `100` | Integer 1–10,000. Maximum top-level comments per post; replies do not count toward it. |
| `sort` | `"all"` | `"all"`, `"newest"`, or `"most_relevant"` for Facebook's top-level comment ordering. |
| `includeReplies` | `false` | Set `true` to include selected replies after each parent. |
| `maxRepliesPerComment` | `10` | Integer 1–10,000. Maximum unique descendants under each top-level comment when replies are enabled. |

Apify checks each field before the run starts, including at most 100 items per field and digits-only post IDs. A run with no inputs at all, or more than 100 inputs across both fields, starts and then fails at once with "Input is invalid; check the input fields." without collecting anything.

Limits are ceilings, not promised result counts or measured collection capacity. Sorting changes the requested selection; even `all` does not guarantee every comment counted by Facebook.

### Output data

Each dataset row represents one comment or reply. It includes string IDs and links, text, a UTC date, author information, reaction totals and types, and attachment links when available. Unavailable values stay `null`. `commentId` is the numeric comment ID, unique within its post; `graphqlId` is Facebook's comment node ID, unique across posts. For a post ID input, `postUrl` is `https://www.facebook.com/<post ID>`, a reference to the requested post that may not open in a browser.

The following three rows are **entirely synthetic**, illustrating a parent, its reply, and a reply to that reply. The example URLs and people do not represent collected data.

```json
[
  {
    "commentId": "7001",
    "graphqlId": "Y29tbWVudDo5MDAwMDAwMDAwMDAwMDAxXzcwMDE=",
    "commentUrl": "https://www.facebook.com/ExamplePage/posts/9000000000000001?comment_id=7001",
    "rootCommentId": "7001",
    "parentCommentId": null,
    "threadDepth": 0,
    "postId": "9000000000000001",
    "postUrl": "https://www.facebook.com/ExamplePage/posts/9000000000000001",
    "text": "Will there be an evening session?",
    "createdAt": "2026-10-20T10:00:00Z",
    "authorId": "8001",
    "authorName": "Example Author",
    "authorUrl": null,
    "authorAvatarUrl": null,
    "authorIdIsStable": true,
    "reactionsCount": 2,
    "reactionsDisplay": "2",
    "reactionsCapped": false,
    "repliesCount": 2,
    "reactions": [{"typeId": "1635855486666999", "typeName": "like", "count": 2}],
    "attachments": []
  },
  {
    "commentId": "7002",
    "graphqlId": "Y29tbWVudDo5MDAwMDAwMDAwMDAwMDAxXzcwMDI=",
    "commentUrl": "https://www.facebook.com/ExamplePage/posts/9000000000000001?comment_id=7002",
    "rootCommentId": "7001",
    "parentCommentId": "7001",
    "threadDepth": 1,
    "postId": "9000000000000001",
    "postUrl": "https://www.facebook.com/ExamplePage/posts/9000000000000001",
    "text": "The timetable has an evening option.",
    "createdAt": "2026-10-20T10:05:00Z",
    "authorId": "8002",
    "authorName": "Example Responder",
    "authorUrl": null,
    "authorAvatarUrl": null,
    "authorIdIsStable": true,
    "reactionsCount": null,
    "reactionsDisplay": null,
    "reactionsCapped": false,
    "repliesCount": 1,
    "reactions": [],
    "attachments": []
  },
  {
    "commentId": "7003",
    "graphqlId": "Y29tbWVudDo5MDAwMDAwMDAwMDAwMDAxXzcwMDM=",
    "commentUrl": null,
    "rootCommentId": "7001",
    "parentCommentId": "7002",
    "threadDepth": 2,
    "postId": "9000000000000001",
    "postUrl": "https://www.facebook.com/ExamplePage/posts/9000000000000001",
    "text": "Thank you!",
    "createdAt": "2026-10-20T10:06:00Z",
    "authorId": null,
    "authorName": null,
    "authorUrl": null,
    "authorAvatarUrl": null,
    "authorIdIsStable": null,
    "reactionsCount": null,
    "reactionsDisplay": null,
    "reactionsCapped": false,
    "repliesCount": null,
    "reactions": [],
    "attachments": []
  }
]
```

Rows follow each parent, then its selected direct replies, then deeper known levels, before the next parent. Within a level, replies keep the order Facebook returned them. `rootCommentId` identifies the thread; `parentCommentId` identifies the observed direct parent; `threadDepth` is the resolved level. A real reply can have unknown parent linkage or depth, or refer to a parent absent from the output. Replies with an unknown level follow those with known levels.

Author details use the flat fields `authorId`, `authorName`, `authorUrl`, `authorAvatarUrl`, and `authorIdIsStable`. All five are `null` when the author is unavailable. `authorIdIsStable` is true for numeric IDs and false for opaque IDs, which may change between sessions.

`repliesCount` is Facebook's reported count, available even when replies are excluded. It is not the number of replies saved by this run or a guarantee of accessible or direct replies; unknown counts stay `null`.

Reactions contain type IDs, names, and counts. `reactionsCount` is null when an exact total is unavailable or capped; `reactionsDisplay` preserves the source's display text. When `reactionsCapped` is true, individual reaction counts are lower bounds. Attachments contain kind, links, dimensions, and description when available; media files are not downloaded.

### Run summary

Each run also saves a **Run summary**: the key-value store record `OUTPUT`, shown under **Output → Run summary**. It is not a dataset row and is not charged. It holds:

- `status`: the run's result (see below).
- `savedCount`, split into `savedCommentsCount` and `savedRepliesCount`: unique rows saved in the run.
- `warning`: `"partial_results"` when the status is `partial` and some input ended with a warning, otherwise `null`. `warningCounts` counts those inputs per `reason`.
- `items`: one entry per input, in order (`postUrls` first, then `postIds`). Each has `inputIndex`; `inputUrl` (the accepted link, `null` for a post ID or a refused link); `inputPostId` (`null` for a link); `resolvedPostId` (the post whose comments were fetched; for a photo's ID, the photo itself or the container post its feedback identifies); `duplicateOf`; its own saved counts; an `outcome`; and a `reason`.

| `outcome` | `reason` | Meaning and what to do |
|---|---|---|
| `complete` | `null` | Every available comment within your selection was saved. |
| `limit_reached` | `null` | **Maximum comments per post** was reached. More comments may exist; raise the limit to get them. |
| `duplicate` | `null` | The same post as the earlier input in `duplicateOf`; its comments are saved once. |
| `incomplete` | `no_progress`, `page_budget`, `replies_unavailable`, `no_page_mode`, `time_budget` | Saved rows are valid, but further comments or replies could not be confirmed and may exist. |
| `incomplete` | `budget_reached` | The run's spending limit was reached; collection stopped. Raise the limit and start a new run for the rest. |
| `incomplete` | `interrupted` | The run was aborted during this input. |
| `invalid_url` | `invalid_url` | Not a supported public post link. Fix the input. |
| `failed` | `not_public`, `not_allowed` | Anonymous access was unavailable. This does not prove the post is private or has no comments. Check the input and try a fresh run later if access may have changed. |
| `failed` | `blocked` | Facebook blocked anonymous access; the run stopped. Try again later. |
| `failed` | `invalid_cursor`, `protocol_error`, `retries_exhausted`, `stale_doc_id`, `stale_session`, `source_error` | Comments could not be read. Try again later in a new run; report a failure that repeats. |
| `failed` | `storage_failure`, `internal_error` | Saving or processing failed; the run stopped. Start a new run. |
| `skipped` | `not_started`, `blocked`, `budget_reached`, `failed` | Not processed because the run stopped first; the reason names the stop. Include these inputs in a new run. |

While the run is in progress, the input being collected shows `incomplete` with `collecting`, and inputs not reached yet show `skipped` with `not_started`. After an abort or a timeout, the summary may still show that state.

The collection `status` is `completed` when every input ended `complete`, `limit_reached`, or `duplicate`; `partial` when some input ended `incomplete`, `failed`, or `invalid_url` but at least one ended `complete`, `limit_reached`, or `incomplete`; `failed` when none did, or the run itself failed; otherwise a run-wide stop: `budget_reached`, `blocked`, or `interrupted`.

A successful collection with no saved comments reports “Collection completed successfully. 0 comments saved.” A numeric photo can complete with zero rows when Facebook explicitly confirms zero for that photo's own comments. A group or numeric profile post can also complete with zero rows when Facebook supplies consistent evidence that the requested post has no comments. This does not add a dataset row or a result charge. An uncertain empty result stays incomplete. Photo URL resolution is unchanged, so a numeric photo ID can succeed where the corresponding URL cannot be resolved.

For a numeric photo ID, the Actor can confirm a successful zero-comment result from
matching anonymous photo metadata when its normal verification page redirects to login.
It does not log in or follow that redirect. Missing or inconsistent evidence stays
`incomplete/no_page_mode`, even with zero saved rows. That result does not prove there
are no comments. Photo URLs still require ordinary page resolution; a photo whose
comments belong to another post may remain incomplete on this path. Check each input's
outcome before treating empty rows as a completed collection.

A run can finish **SUCCEEDED** in Apify while its collection summary says `failed`: the Actor finished handling the inputs, but comments were unavailable. This includes the access and source failures listed above, with or without saved rows. The final message says “Finished processing inputs. N rows saved; see the run summary for per-input results.” Each failed input keeps its original reason and counts; `warningCounts` records those reasons even when `warning` is null. Invalid links may accompany a handled source failure. Check the summary on every run; success alone does not mean a post has no comments or that all its comments were collected.

When anonymous access is unavailable, the diagnostic says “This run could not access the post's comments anonymously.” It describes this attempt and does not establish the post's privacy.

A block, invalid overall input, a selection containing only invalid links, storage or processing failure, or interruption still prevents successful completion. A run can also fail while closing after its summary was saved, so check its status in Apify as well. Acknowledged rows remain available; warnings and failed-input summaries are not comment rows and are not billed as results. Starting another run can collect and bill some of the same rows again.

### Export and automate with Apify

Use the data to review campaign responses, identify recurring questions, or supply your own reporting and analysis tools. Sentiment analysis and post discovery are separate from this Actor's collection.

Apify provides [saved Tasks and schedules](https://docs.apify.com/get-started/run-actors) for reusing input and starting recurring runs. Its [API](https://docs.apify.com/api/v2) can start runs and retrieve results; the Actor's **API** tab provides usage examples. [Integrations and webhooks](https://docs.apify.com/integrations) let you configure workflows with tools such as Make, Zapier, or n8n. Configure these Apify tools to send results to your chosen destination.

### Pricing

The result unit is **one unique saved comment or reply**, with replies counted equally. Invalid links, warnings, and run summaries are not dataset rows. Check the current price and run budget displayed in Apify Console before starting.

### Common questions

#### Why are there fewer results than expected?

Facebook may expose fewer comments than its displayed counter, limit anonymous access, or refuse a post. The prefilled example enables replies; API input that omits `includeReplies` defaults to false. Your selected limits also affect the total. The Actor stops the batch on a block; it does not log in to recover access.

#### What happens if collection is partial or fails?

Check **Output → Run summary** (described under "Run summary" above) for saved totals, warnings, and each input's outcome. A partial collection or a collection with handled source failures can finish successfully while some data is unavailable. A failed run can also contain saved rows. Acknowledged rows remain in the dataset; the summary may be incomplete after abrupt interruption. Other inputs continue after an individual post fails, but a block, a reached run budget, or a storage failure stops later inputs. Before starting a large selection, check the run's time and spending limits in Apify Console; reaching those limits can stop collection.

### Support

For help, open an issue in the Actor's **Issues** tab. Include the run link, input settings, and what you expected compared with what happened.

# Actor input Schema

## `postUrls` (type: `array`):

Public Facebook post links, including public group and event post links. Each link is checked separately. Together with Post IDs: 1–100 inputs.

## `postIds` (type: `array`):

Numeric Facebook post IDs (digits only), collected like links. Together with Post URLs: 1–100 inputs.

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

Maximum number of top-level comments to save for each post.

## `sort` (type: `string`):

Choose how Facebook orders the available top-level comments.

## `includeReplies` (type: `boolean`):

Save selected replies after each top-level comment.

## `maxRepliesPerComment` (type: `integer`):

Maximum unique descendants across all levels of one top-level comment. Applies when replies are included.

## Actor input object example

```json
{
  "postUrls": [
    "https://www.facebook.com/reel/1038785062517728"
  ],
  "maxCommentsPerPost": 20,
  "sort": "all",
  "includeReplies": true,
  "maxRepliesPerComment": 10
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

# 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 = {
    "postUrls": [
        "https://www.facebook.com/reel/1038785062517728"
    ],
    "maxCommentsPerPost": 20,
    "includeReplies": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-wan/facebook-comments").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 = {
    "postUrls": ["https://www.facebook.com/reel/1038785062517728"],
    "maxCommentsPerPost": 20,
    "includeReplies": True,
}

# Run the Actor and wait for it to finish
run = client.actor("data-wan/facebook-comments").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 '{
  "postUrls": [
    "https://www.facebook.com/reel/1038785062517728"
  ],
  "maxCommentsPerPost": 20,
  "includeReplies": true
}' |
apify call data-wan/facebook-comments --silent --output-dataset

```

## MCP server setup

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

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/VfxRDGZEhdOhCx3xF/builds/LEuLflJep9Ecw6tQG/openapi.json
