# Facebook Comments Scraper - $0.25 per 1,000 (`dami_studio/facebook-comments-scraper`) Actor

Get comments from any public Facebook post or reel. Paste the URLs. Each row has the author, comment text, date, like count and reply count. Nested replies are included if you want them. You never log in, and no browser is used. $0.25 per 1,000 comments, the same rate on every plan.

- **URL**: https://apify.com/dami\_studio/facebook-comments-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 15 total users, 8 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.25 / 1,000 comment scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## 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 Comments Scraper

Paste public Facebook post URLs, get the comments back as rows: who wrote it, what they wrote, when,
how many likes it got, how many replies it has, and the replies themselves if you want them.

Nothing to set up. You don't supply a login, a cookie or a token, and no browser is started.

### Price

**$0.25 per 1,000 comments.**

That $0.25 is the rate on every plan, free or paid. No volume tiers, no minimum spend, no add-on
fees. What you read here is what you pay on day one.

#### What is actually charged

- **One `comment-scraped` event per comment row written to the dataset.** Nothing else is metered
  per row.
- Replies count as comments. Each reply is its own row, so each one is one charge.
- **Free:** the sample row an empty run returns, every diagnostic row (blocked post, private post,
  post with comments turned off, bad URL), and every comment dropped by the `onlyCommentsNewerThan`
  filter. They all carry `"charged": false`.
- Rows never leave the dataset without a charge, and are never charged without a row. The primary
  event is a named one, so there's no price quietly attached to `apify-default-dataset-item`.

### Input

```json
{
  "startUrls": [
    { "url": "https://www.facebook.com/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl" },
    { "url": "https://www.facebook.com/NASAJSC/posts/1011509817757722" }
  ],
  "resultsLimit": 50,
  "includeNestedComments": true,
  "viewOption": "RANKED_UNFILTERED"
}
```

| Field | What it does |
|---|---|
| `startUrls` | Public Facebook post, video, reel, photo or group-post permalinks. Up to 100 per run. |
| `resultsLimit` | Maximum comments **per post URL**. Default 50, hard ceiling 5,000. |
| `includeNestedComments` | Also pull the replies under each comment. Each reply is its own row and counts towards the limit. |
| `viewOption` | `RANKED_UNFILTERED` (all comments, the default), `RANKED_THREADED` (most relevant), `RECENT_ACTIVITY` (newest first). |
| `onlyCommentsNewerThan` | Optional cut-off. `2026-01-31`, a full ISO timestamp, or relative like `3 days` / `2 months`. Dropped comments aren't charged. |
| `sessionCookies` | Optional, and most runs never touch it. See **About logins** below. |
| `proxyUrls` | Leave empty. Fill it in only if you want traffic to leave through proxy servers you already pay for. |

Run it with **empty input** and you get one labelled sample row, free, so you can see the output
shape before you spend anything.

#### URL shapes that work

```
https://www.facebook.com/<page>/posts/<pfbid...>
https://www.facebook.com/<page>/posts/<numeric id>
https://www.facebook.com/<page>/videos/<numeric id>
https://www.facebook.com/reel/<numeric id>
https://www.facebook.com/groups/<group id>/posts/<numeric id>
https://www.facebook.com/permalink.php?story_fbid=<id>&id=<page id>
https://www.facebook.com/photo.php?fbid=<id>
```

A bare Page URL like `https://www.facebook.com/natgeo` is rejected with an explanation rather than
scraped. A Page isn't a post and has no single comment thread.

### Output

One row per comment. Real row from a National Geographic post:

```json
{
  "ok": true,
  "charged": true,
  "recordType": "comment",
  "inputUrl": "https://www.facebook.com/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl",
  "postId": "1634927484671342",
  "postTitle": "National Geographic",
  "postDescription": "A punishing heat wave has left much of Europe in drought, causing rivers to run at record-low levels.",
  "pageName": "National Geographic",
  "commentUrl": "https://www.facebook.com/natgeo/posts/pfbid02r7oYKajerb...?comment_id=27860828630277533",
  "id": "Y29tbWVudDoxNjM0OTI3NDg0NjcxMzQyXzI3ODYwODI4NjMwMjc3NTMz",
  "commentId": "27860828630277533",
  "feedbackId": "ZmVlZGJhY2s6MTYzNDkyNzQ4NDY3MTM0Ml8yNzg2MDgyODYzMDI3NzUzMw==",
  "date": "2026-08-15T06:07:03.000Z",
  "timestamp": 1786774023,
  "text": "Perfect opportunity to clean our waters!!! Bring this history to the musuem for our kids.",
  "likesCount": 106,
  "likesCountText": "106",
  "commentsCount": 4,
  "threadingDepth": 0,
  "replyToCommentId": null,
  "profileName": "Maggie Dalkowska",
  "profileId": "pfbid0gZTGg4botETTKbXHiLzt72U5Wig8Gg3mJgF18vBVbiKRWof4nKXS26GVDFy9siuyl",
  "profileUrl": "https://www.facebook.com/maggie.dalkowska",
  "profilePicture": "https://scontent-fra3-2.xx.fbcdn.net/v/t39.30808-1/465583957_...jpg",
  "author": { "id": "pfbid0gZTGg4botETTKbXHiLzt72U5Wig...", "name": "Maggie Dalkowska", "url": "https://www.facebook.com/maggie.dalkowska" },
  "attachments": [],
  "scrapedAt": "2026-08-20T19:52:23.770Z"
}
```

A reply row looks the same but with `"threadingDepth": 1`, a `replyToCommentId` pointing at its
parent, and a `commentUrl` carrying both `comment_id` and `reply_comment_id`.

#### Field notes

- `likesCount` is a **number**. Facebook only publishes a display string for reaction counts
  (`"28"`, `"1.2K"`, `"3.4M"`), so it's parsed back into an integer here. `likesCountText` keeps the
  original string. Above about a thousand reactions Facebook rounds the display string, so the
  number is rounded too: `1.2K` becomes `1200`, not the exact count. Nobody can give you the exact
  count from the public page, because the public page doesn't contain it.
- `commentsCount` is the reply count on that comment, not the post's comment count.
- `profileId` is sometimes a numeric id and sometimes an opaque `pfbid…` string, depending on the
  account. The `pfbid` form rotates over time, so `commentId` is the safer key.
- `attachments` is an array of `{type, url}` for photo, video and sticker comments, empty for the
  usual text-only ones.
- `profileUrl` is null for commenters whose profile Facebook renders without a public link.
- `postDescription` is the **post's** text, not the comment's. It's read from the post's own content
  section, which matters more than it sounds: taking the longest text block on the page instead
  returns whichever reader wrote the longest comment.

### Speed

Measured on the platform at the default 1 GB. One post, 60 comments: **13 seconds** from a cold
container. Two posts, 8 comments each: **7 seconds**, since up to three posts are worked on at once.

Facebook hands out comments in pages of ten and silently ignores any request for a larger page, so a
hundred comments is ten round trips no matter how it's asked for. That's the floor for this target,
not a limitation of this Actor.

### About logins

Worth spelling out, because "no login" gets used loosely.

Runs start logged out, and for a public post that is usually all it takes. When Facebook hands a
logged-out reader nothing back, the run falls back to an account session that ships with the Actor
so you don't have to supply one. There is a session involved. It just isn't yours and you never see
it.

That session is shared across everyone using the Actor, and Facebook meters per account. If you're
running heavily and start seeing `BLOCKED` diagnostics, `sessionCookies` lets you paste your own
`c_user=…; xs=…` pair and get a rate limit nobody else is using. Treat those values like a password.
Anyone holding them can act as that account, and Facebook may sign the session out or restrict the
account for automated use.

### What this does not do

An honest list, because you'll hit these otherwise.

- **Public posts only.** Anything Facebook shows only to a specific audience, meaning private
  groups, friends-only posts, or a Page restricted by age or country, returns an uncharged
  diagnostic row saying so.
- **No reactions breakdown.** You get the total reaction count, not how many were love, haha or
  angry. The public page doesn't carry it.
- **No commenter email or phone.** Not published, not inferable, not returned.
- **One level of replies.** `includeNestedComments` pulls the replies under a top-level comment.
  Replies to replies are collapsed by Facebook into the same level, so that's as deep as the public
  thread goes.
- **Reply counts can exceed the rows you get.** A comment may report six replies and return four if
  some were made by accounts that later deactivated or were removed.
- **Very large threads.** The ceiling is 5,000 comments per post URL. Threads deeper than that get
  paginated by Facebook in a way that starts repeating rather than continuing, and repeated rows are
  dropped rather than charged.
- **A post with comments turned off** returns one uncharged diagnostic row, not an error.
- **Rate limiting.** Facebook meters reads per address. The Actor rotates through a large pool of
  datacenter addresses and retries, but a very large run may still see a few posts come back as
  `BLOCKED` diagnostics. They aren't charged. Re-running usually clears them.

### Diagnostics

Nothing here fails silently, and a failed run never bills you for the failure. Every problem becomes
a row with `"charged": false` and a `code`:

| `code` | Meaning |
|---|---|
| `BAD_INPUT` | That URL isn't a Facebook post URL. The row says why. |
| `NOT_FOUND` | The post is private, deleted, or the URL points at something with no comment thread. |
| `NO_COMMENTS` | The post exists and is readable but has no comments, or none newer than your date filter. |
| `BLOCKED` | Every exit address tried was throttled on this post. Re-run. |
| `RATE_LIMITED` | Facebook asked for a slower pace. Re-run. |
| `DEAD_BATCH` | Fifteen URLs in a row returned nothing, so the run stopped early rather than working through a list of dead links. The rest weren't touched and weren't charged. |
| `PROXY_INPUT_ADJUSTED` | You asked for a proxy setting this Actor doesn't use; it ran on its own rotating addresses instead. |
| `CHARGE_ERROR` | A billing event couldn't be recorded. Told to you rather than hidden. |

The run still ends **SUCCEEDED** in all of these cases, so a wrong link comes back as a labelled row
rather than a crashed run.

### FAQ

**Do I need a Facebook account, cookies or an access token?**
Not from you. Paste URLs, get comments. There's an optional `sessionCookies` field if you'd rather
the run used your own account's rate limit than the shared one.

**Does it work on reels and videos?**
Yes. Reels, videos, photos and group posts all have the same comment thread underneath them and are
read the same way.

**How do I get the replies under a comment?**
Set `includeNestedComments` to `true`. Each reply arrives as its own row with `threadingDepth: 1`
and `replyToCommentId` set, and each is charged as a comment because each is a row.

**How do I only get recent comments?**
Set `onlyCommentsNewerThan` to `7 days`, or to a date like `2026-06-01`. Anything older is dropped
before it reaches the dataset and never charged. Combine it with `viewOption: "RECENT_ACTIVITY"` and
the run also stops paging as soon as it goes past your cut-off, which is faster and cheaper than
filtering afterwards.

**How much does 10,000 comments cost?**
$2.50. There's no tier that makes it cheaper or more expensive.

**Why do I get exactly ten comments per request in the logs?**
Because that's what Facebook returns. Asking for 25, 50 or 100 returns ten. The Actor pages until it
reaches your `resultsLimit`.

**Can I use my own proxies?**
Yes, put them in `proxyUrls` as `http://user:pass@host:port`. Leave it empty and the run uses a
rotating pool of datacenter addresses, which costs you nothing extra. This Actor deliberately
doesn't offer residential proxy: on a target this data-heavy the bandwidth would cost more than the
data is worth, and you'd be paying for it.

**Is the comment ID stable?**
`commentId` is Facebook's own numeric comment id and is stable. `id` is the base64 node id, also
stable. Either works as a deduplication key across runs.

**What happens if I run it with no input?**
One sample row, marked `"_sample": true` and `"charged": false`, showing every field. Only the start
fee applies.

**Can it scrape all comments from a whole Page?**
Not directly. This takes post URLs. Collect the post URLs first, then feed them in here, up to 100
per run.

### Integration

Standard Apify Actor. Start it from the API, the JS or Python client, a schedule, or an MCP client,
and read the default dataset:

```bash
curl -X POST "https://api.apify.com/v2/acts/dami_studio~facebook-comments-scraper/runs?token=%3CYOUR_TOKEN%3E" \
  -H 'content-type: application/json' \
  -d '{"startUrls":[{"url":"https://www.facebook.com/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl"}],"resultsLimit":50}'
```

Output exports as JSON, CSV, Excel and XML from the dataset endpoint.

### Legal

This Actor reads public Facebook content: posts and comments that anyone can open. It doesn't bypass
a privacy setting and it doesn't touch private groups or restricted posts. Comment text and profile
names are personal data in most jurisdictions, so you are the controller of whatever you collect,
and GDPR, the CCPA and Facebook's own terms are yours to satisfy. If someone asks you to delete
their comment out of your dataset, that request lands on you, not on us.

# Actor input Schema

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

Public Facebook post, video, reel, photo or group-post permalinks. You get one row per comment for each of them.

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

Stop after this many comments for each post URL. Keep it low while you are testing - you pay per comment.

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

Also fetch the replies underneath each comment. Every reply is returned as its own row and counts towards the limit.

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

How Facebook should order the comments before they are read.

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

Optional cut-off. Absolute (2026-01-31 or a full ISO timestamp) or relative (3 days, 2 months). Comments older than this are dropped and never charged. There is no add-on fee for using it.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of datacenter addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## `sessionCookies` (type: `array`):

Leave this empty unless you need it. Runs are logged out by default and that is enough for public Facebook content. Facebook shows some things only to a signed-in account, and it limits how fast any one account may read; supplying your own cookie uses your account and your own rate limit, shared with nobody. In Chrome: open facebook.com while signed in, press F12, then Application > Cookies > https://www.facebook.com, and paste the values as "c\_user=<value>; xs=<value>". One line per account. Treat these like a password: anyone with them can act as that account, and Facebook may sign the session out or restrict the account for automated use.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl"
    }
  ],
  "resultsLimit": 50,
  "viewOption": "RANKED_UNFILTERED"
}
```

# Actor output Schema

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

Every comment row in the default dataset: profileName, text, date, likesCount, commentsCount, threadingDepth, commentUrl, facebookUrl, postId, postTitle and the rest. An empty, private or blocked run returns a single uncharged row explaining what happened instead.

# 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/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl"
        }
    ],
    "resultsLimit": 50,
    "includeNestedComments": false,
    "viewOption": "RANKED_UNFILTERED",
    "onlyCommentsNewerThan": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/facebook-comments-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": [{ "url": "https://www.facebook.com/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl" }],
    "resultsLimit": 50,
    "includeNestedComments": False,
    "viewOption": "RANKED_UNFILTERED",
    "onlyCommentsNewerThan": "",
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/facebook-comments-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": [
    {
      "url": "https://www.facebook.com/natgeo/posts/pfbid02rAK2eMyfVYDZbESKoNVuwWjcFr5BgRBfjp27nhgQToSiQD9NF3ypNdXpQuwSp7qnl"
    }
  ],
  "resultsLimit": 50,
  "includeNestedComments": false,
  "viewOption": "RANKED_UNFILTERED",
  "onlyCommentsNewerThan": ""
}' |
apify call dami_studio/facebook-comments-scraper --silent --output-dataset

```

## MCP server setup

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