# Reddit Post Comments Scraper — Full Comment Tree, Any Post URL (`scrapersdelight/reddit-post-comments-scraper`) Actor

Reddit comments scraper: paste post URLs (permalinks, redd.it, share links, comment links) and get every comment as flat rows with parent id, depth, thread path, reply and descendant counts, author, score, timestamps. Expands 'load more' stubs. No login or API key.

- **URL**: https://apify.com/scrapersdelight/reddit-post-comments-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Social media, AI, Marketing
- **Features:** Limited permissions
- **Stats:** 11 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 per comment returneds

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?

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

## Reddit Post Comments Scraper — Full Comment Tree, Any Post URL

Paste Reddit post URLs and get **every comment under them as flat rows that still carry the
tree**. One row per comment: **body, author, score, depth, parent_comment_id, thread_path,
reply_count, descendant_count, position, created_iso, edited, permalink, is_deleted** and the
post context on every row (**post_title, post_url, post_score, post_num_comments,
tree_complete**). Reddit shows a big thread in pieces — the first 25 comments, then "N more
replies", "View more comments" and "Continue this thread" links. This Actor opens every one of
them, so a big thread comes back whole. No login. No Reddit API key. No CAPTCHA solving.

```json
{
  "postUrls": ["https://www.reddit.com/r/programming/comments/luq9oz/how_i_cut_gta_online_loading_times_by_70/"],
  "commentSort": "top",
  "maxCommentsPerPost": 200
}
```

Click **Try for free** and hit **Start** — that block is the input the Actor ships with: the
first 200 comments of that thread in `top` order, for **$0.30**.

***

### What makes it different

1. **Tree fields that are computed, not copied.** Every row has `parent_comment_id` and
   `depth`, plus **`thread_path`** (root-first list of ancestor comment ids, so one `JOIN`
   rebuilds any branch), **`reply_count`** (direct replies present), **`descendant_count`**
   (whole subtree size), **`is_top_level`** and **`position`** (delivery order). "Which
   top-level comment started the biggest argument" is
   `ORDER BY descendant_count DESC WHERE is_top_level` — no recursion on your side.
2. **The hidden part of the thread, expanded.** With `expandMoreComments` on (default) every
   "more replies" loader and every "Continue this thread" cut is opened, at any depth, until the
   tree is complete or your cap is hit.
3. **`tree_complete` on every row, and `post_num_comments` to check it against.** You never
   have to guess whether a thread was cut by a cap, the time budget or a loader that would not
   open — the row says so, and the log prints `delivered of N reddit counts` per post.

Also: **any URL shape Reddit produces** is accepted — desktop permalinks, `redd.it` short
links, the mobile app's `/r/sub/s/AbCdEfG` share links (resolved live from Reddit's redirect),
profile post links (`/user/name/comments/…`), `old.` / `new.` / `np.` subdomains, bare ids, and
**comment permalinks**: paste a link to one comment and you get that comment's thread only
(`scope: "comment-thread"`, `focus_comment_id` set).

***

### Read this before you buy rows

1. **`post_num_comments` is Reddit's counter, not a promise.** It is kept separately from the
   tree and drifts both ways: deleted comments that still have replies stay in the tree as
   `[deleted]` rows, while deleted comments with no replies are not shown at all.
   `tree_complete: true` means every loader Reddit offered was opened.
2. **The per-post cap cuts after the tree math.** With `maxCommentsPerPost: 200` you get the
   first 200 rows in the order Reddit's page serves them (the top-level comments of the first
   page and their replies first), and each row still carries the `reply_count` /
   `descendant_count` of everything fetched. `thread_path` can name an ancestor that is not in
   your rows — it is an id, not a join failure.
3. **`[deleted]` and `[removed]` rows are real rows.** Reddit keeps them so the tree stays
   connected. They come with `is_deleted: true`, `body` `"[deleted]"` / `"[removed]"`, and
   usually no score; they are billed like any other row. Set `includeDeleted: false` to drop
   them unbilled.
4. **Score is a snapshot.** Re-run later if you need settled scores.
5. **Big trees take calls.** Reddit's page opens about 10 comments per "more replies" loader,
   so the whole tree of a ~1,000-comment thread is several hundred Reddit calls and a few
   minutes; the shipped 200-comment cap is about 25 calls.

***

### What you get

One row per unique comment, deduplicated on Reddit's fullname (`t1_…`) across the whole run.
Timestamps are ISO-8601 UTC plus the raw `created_utc` epoch. `edited` is `false`, or the ISO
time of the edit.

| Group | Fields | Example |
|---|---|---|
| **Identity** | `id`, `name`, `post_id`, `permalink`, `subreddit`, `subreddit_id` | `gp8beoa` · `t1_gp8beoa` · `luq9oz` |
| **Tree** | `parent_id`, `parent_comment_id`, `is_top_level`, `depth`, `position`, `reply_count`, `descendant_count`, `thread_path[]` | depth `2` · 7 replies · 53 descendants |
| **Author** | `author`, `author_fullname`, `author_flair_text`, `author_is_op` | `deruke` |
| **Content** | `body`, `body_length`, `is_deleted`, `edited`, `distinguished`, `stickied`, `locked`, `collapsed`, `collapsed_reason` | 525 chars |
| **Votes** | `score`, `ups`, `score_hidden`, `total_awards_received` | `3034` |
| **Time** | `created_utc`, `created_iso` | `2021-02-28T21:35:00.233Z` |
| **Post context** | `post_title`, `post_url`, `post_author`, `post_subreddit`, `post_score`, `post_upvote_ratio`, `post_num_comments`, `post_created_iso`, `post_link_flair_text`, `post_is_self`, `post_external_url`, `post_selftext_preview`, `post_over_18`, `post_locked`, `post_archived` | `992` comments · ratio `0.97` |
| **Provenance** | `source` (`listing` / `more-children` / `continue-thread`), `scope`, `focus_comment_id`, `comment_sort`, `tree_complete`, `comments_delivered_for_post`, `comments_absent_at_source`, `input_url`, `scraped_at` | |

`body` is the comment as plain text: paragraphs on their own lines, list items as `- item`,
quotes as `> line`, code blocks verbatim, links as their text.

**Fields Reddit does not show logged-out visitors** (measured on 1,006 rows of the
thread above, 2026-10-27): `author_fullname` 0.8% (only where a subreddit renders user
flair), `author_flair_text` 0% on r/programming, `collapsed_reason` always null, `stickied` / `locked` null unless set,
`comments_absent_at_source` always 0. Every other field is filled on every row it applies to:
`score` 96.4% (deleted rows carry none), `edited` holds the edit time on the 3.9% of rows that were edited, `distinguished` on moderator comments.

**Every run prints a measured fill line in the log** for the 12 fields most likely to vary by
subreddit, so you can check your own subreddit.

The dataset ships with a **Comments** table view (post, depth, author, score, body, replies,
thread size, posted, tree complete, link).

***

### How to run it

#### 1. One or many post URLs (the usual choice)

```json
{
  "postUrls": [
    "https://www.reddit.com/r/programming/comments/luq9oz/how_i_cut_gta_online_loading_times_by_70/",
    "https://redd.it/1xgauuz",
    "https://www.reddit.com/r/AskReddit/s/AbCdEfGhIj",
    "t3_luq9oz"
  ],
  "commentSort": "top",
  "maxCommentsPerPost": 0
}
```

`maxCommentsPerPost: 0` = the whole tree. Share links are resolved to the canonical permalink
from Reddit's own redirect and the log prints the resolution.

#### 2. One comment's thread only

```json
{ "postUrls": ["https://www.reddit.com/r/programming/comments/luq9oz/comment/gp8beoa/"] }
```

A comment permalink scopes the scrape to that comment and everything under it. Rows carry
`scope: "comment-thread"` and `focus_comment_id: "gp8beoa"`; the linked comment is depth 0.

#### 3. Top-level comments and their direct replies

```json
{ "postUrls": ["https://redd.it/luq9oz"], "maxDepth": 1, "maxCommentsPerPost": 0 }
```

`maxDepth` filters on the 0-based `depth` field, so `maxDepth: 1` returns depths 0 **and** 1.
It filters after the tree math, so each row still carries `reply_count` and
`descendant_count` for the branch you did not buy. Filtered rows are never billed.

#### 4. First page only, fastest and cheapest

```json
{ "postUrls": ["https://redd.it/luq9oz"], "expandMoreComments": false }
```

One page per post (25 comments). `tree_complete` is honest about what you skipped.

#### Scheduling and integrations

Save the input as a **Task**, attach a **Schedule** to re-pull a thread as the discussion
grows, and read the dataset over the REST API or the standard integrations (Zapier, Make, n8n,
webhooks, MCP). Starting a run from the API:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~reddit-post-comments-scraper/runs?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"postUrls":["https://redd.it/luq9oz"],"maxCommentsPerPost":0}'
```

***

### Input

Fields in the order the Console shows them.

| Field | Type | Default | What it does |
|---|---|---|---|
| `postUrls` | string list | 1 sample post | Permalinks, `redd.it`, `/s/` share links, profile-post links, comment permalinks, bare ids. One post pasted twice is fetched once. |
| `commentSort` | select | `top` | `top`, `best`, `new`, `controversial`, `old`, `qa` — decides which rows come first under a cap. |
| **💷 Limits & cost** | | | |
| `maxCommentsPerPost` | integer | `200` | Per post. **0 = the whole tree.** |
| `maxTotalComments` | integer | `0` | Hard cap for the run. 0 = none. The run's *Max total charge* is honoured to the whole row as well. |
| **🌳 Tree depth & filters** | | | |
| `expandMoreComments` | boolean | `true` | Open "more replies" / "Continue this thread". Off = the first page only. |
| `maxDepth` | integer | `0` | Keep depth ≤ N (0-based). 0 = unlimited. Filtered rows are never billed. |
| `includeDeleted` | boolean | `true` | Off drops `[deleted]` / `[removed]` placeholders, unbilled. |
| **⚙️ Proxy & performance** | | | |
| `proxyConfiguration` | proxy | Apify RESIDENTIAL, country US | Recommended as is — Reddit turns most datacenter IPs away. |
| `postsPerSession` | integer | `40` | Posts served per session before a fresh one is opened (a session is also replaced when Reddit blocks it or its allowance is used up). |
| `requestDelayMs` | integer | `300` | Pacing between the expansion calls. |

***

### Pricing

**$0.0015 per comment returned — $1.50 per 1,000.** Charged on the `comment-scraped` event.
No monthly fee from this Actor.

| Run | Comments | Cost |
|---|---|---|
| The shipped default (1 post, 200 cap) | 200 | **$0.30** |
| The whole tree of the sample thread | 1,006 | **$1.51** |
| 5 posts × ~260 comments (a typical VoC pull) | 1,300 | **$1.95** |
| 10,000 comments of a mega-thread | 10,000 | **$15.00** |

- **You are charged for rows delivered.** Duplicates (same post pasted twice, overlapping
  comment threads), rows removed by `maxDepth` / `includeDeleted`, and posts that failed or
  were not found are never billed.
- **Rows are charged as they are pushed**, one post at a time, so a budget cap or time stop
  leaves you with whole posts' worth of rows, every one of them billed, none unbilled.
- **`maxCommentsPerPost` × number of posts is your cost ceiling**; `maxTotalComments` and the
  run's *Max total charge* are hard caps on top. A charge cap too low for even one comment
  fetches nothing and bills nothing.

***

### Honest limits

- **No user profiles, no subreddit listings, no search.** This Actor does one thing: post
  URL → comment tree. Our [Reddit Subreddit Scraper](https://apify.com/scrapersdelight/reddit-subreddit-scraper)
  lists a subreddit's posts (and can attach comments).
- **Plain text, no media.** Images/GIFs in comments appear as their link text inside `body`.
- **Private, quarantined and banned subreddits, and deleted posts, return nothing.** The run
  names each such URL in its status message and moves on; if every post is like that the run
  ends SUCCEEDED with 0 rows and nothing charged.

***

### Duplicates

Within a post, Reddit can show the same comment on more than one of its pages; the Actor keeps
a per-post set and a **run-wide set keyed on the `t1_` fullname**, drops repeats **before**
billing, and **asserts uniqueness across everything it pushed** before the run is allowed to
succeed. Duplicates from input — the same post pasted as a permalink and a `redd.it` link, or a
comment permalink inside a post you also pasted whole — are dropped and logged, never billed.

***

### When a run ends without rows

- **Input that matches nothing** (posts not found, private subreddits, share links that lead
  nowhere, lines that are not Reddit links) → SUCCEEDED, 0 rows, nothing charged, and the status
  message names each one.
- **Reddit refuses every request** (no session clears its check, or every post is refused on
  fresh exits) → FAILED with that reason in the status message; nothing is charged. Re-run later.
- **Some posts fail, others deliver** → SUCCEEDED with a `WARNING:` status message counting
  what was skipped and why.
- **Time budget** → stops cleanly with every fetched post pushed and a status message;
  never TIMED-OUT.

***

### FAQ

**Does this need an account, a login or cookies?**
No. It opens a logged-out session the way a browser does and reads the public pages. No
credentials, no account cookies, no CAPTCHA service.

**Can I get the whole thread, however big?**
Yes — `maxCommentsPerPost: 0`. The sample thread came back as 1,006 rows in
542 Reddit calls; a 10,000-comment thread is thousands of calls across several
sessions (the Actor rotates them for you) — set the run timeout accordingly.

**Why does the row count not match `post_num_comments`?**
Reddit's counter is kept separately from the tree — see *Read this before you buy rows*.
`tree_complete: true` tells you every loader was opened.

**Do I need a residential proxy?**
It is the default and the recommended setting: Reddit turns most datacenter IPs away.

**Will two runs give me duplicates?**
Within a run, never — asserted. Across runs, the `name` field is Reddit's permanent id, so
dedupe on it.

***

### Feedback

Found a URL shape that does not parse or a field you need? Open an issue on the **Issues**
tab, and if the Actor earns it, a review on the **Reviews** tab helps other buyers find it.

# Actor input Schema

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

One or more Reddit posts. Accepts full permalinks (`https://www.reddit.com/r/programming/comments/luq9oz/…/`), `redd.it/luq9oz` short links, mobile share links (`/r/sub/s/AbCdEfG`), profile-post links (`/user/name/comments/…`), bare ids (`luq9oz` or `t3_luq9oz`), and **comment permalinks** — paste a link to one comment and you get just that comment's thread. Old/new/np subdomains all work.

## `commentSort` (type: `string`):

The order Reddit ranks the tree in — this decides which comments come first when a cap applies. `top` = highest score first; `best` = Reddit's default "best" ranking; `new`, `old`, `controversial`, `qa` as on the site.

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

Stop after this many comments for each post (nested replies count). **0 = the whole tree**, however deep — the sample thread (992 comments by Reddit's count) came back as 1,006 rows in 542 Reddit calls / ~6 min; a 10,000-comment thread is thousands of calls, so give the run time. The shipped default of 200 keeps a first click under $0.40.

## `maxTotalComments` (type: `integer`):

Hard cost cap across all posts. 0 = no cap (the per-post cap still applies). The run's *Max total charge* setting works as well and is honoured to the whole row.

## `expandMoreComments` (type: `boolean`):

Reddit's page shows the first 25 comments of a thread and hides the rest behind "N more replies", "View more comments" and "Continue this thread" links. On, the Actor opens every one of them, at any depth, until the tree is complete or your cap is hit. Off = the first page only (fast, cheap, `tree_complete` will be false on big threads).

## `maxDepth` (type: `integer`):

Keep only comments at this nesting depth or shallower (0-based: top-level comments are depth 0). **0 = unlimited.** Set 1 to keep top-level comments and their direct replies (depths 0 and 1) (their `reply_count` / `descendant_count` are still computed from the full tree). Filtered rows are never billed.

## `includeDeleted` (type: `boolean`):

Reddit keeps a placeholder row for deleted and moderator-removed comments so the tree stays intact. On (default) they are delivered with `is_deleted: true`; off, they are dropped and not billed (their replies are still delivered, with `parent_comment_id` pointing at the missing row).

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

**Residential proxy (country US) is recommended.** Reddit turns most of Apify's datacenter IPs away; the default below is already correct.

## `postsPerSession` (type: `integer`):

How many posts one challenge-cleared session serves before a fresh session (new exit IP) is minted. A mint takes a few seconds. A failed post, a blocked exit IP, or a session that has used up Reddit's per-session rate limit also triggers a re-mint.

## `requestDelayMs` (type: `integer`):

Pacing between the calls that expand a tree. Lower is faster but more likely to be throttled. Measured: the 1,006-row sample tree = 542 calls, ~6 minutes.

## Actor input object example

```json
{
  "postUrls": [
    "https://www.reddit.com/r/programming/comments/luq9oz/how_i_cut_gta_online_loading_times_by_70/"
  ],
  "commentSort": "top",
  "maxCommentsPerPost": 200,
  "maxTotalComments": 0,
  "expandMoreComments": true,
  "maxDepth": 0,
  "includeDeleted": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "postsPerSession": 40,
  "requestDelayMs": 300
}
```

# Actor output Schema

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

Flat comment rows with tree fields, author, score, body, timestamps and post context.

# 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.reddit.com/r/programming/comments/luq9oz/how_i_cut_gta_online_loading_times_by_70/"
    ],
    "commentSort": "top",
    "maxCommentsPerPost": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/reddit-post-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 = {
    "postUrls": ["https://www.reddit.com/r/programming/comments/luq9oz/how_i_cut_gta_online_loading_times_by_70/"],
    "commentSort": "top",
    "maxCommentsPerPost": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/reddit-post-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 '{
  "postUrls": [
    "https://www.reddit.com/r/programming/comments/luq9oz/how_i_cut_gta_online_loading_times_by_70/"
  ],
  "commentSort": "top",
  "maxCommentsPerPost": 200
}' |
apify call scrapersdelight/reddit-post-comments-scraper --silent --output-dataset

```

## MCP server setup

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