# Reddit Comments Scraper (`mlg14/reddit-comments-scraper`) Actor

Collect archived public Reddit post comments and nested replies by post URL, with timestamps, scores, and reply-tree links. Export the dataset as JSON or CSV.

- **URL**: https://apify.com/mlg14/reddit-comments-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.20 / 1,000 results

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

Scrape public Reddit post comments and nested replies from a post URL, then export the resulting Reddit discussion data to JSON, CSV, or a spreadsheet. This Reddit API alternative reads a public archive of posts and comments, so it works without an account or authentication, while clearly showing when each record was archived.

The output begins with one parent post item and continues with comment items from the same thread. Each comment carries its parent identifier and reply depth, allowing a discussion to be reconstructed as a tree. You can provide several post URLs in one run and set both a per-post comment limit and an overall item limit. Public archive coverage varies by post and date; see [Limits](#limits) before using the output as a complete or current record of a conversation.

### What data can you extract from Reddit?

The dataset contains two item types. `dataType` is `post` for the parent post and `comment` for a reply. Post context appears on every comment so a file containing several threads remains easy to filter. Fields that the archive does not provide are returned as `null`, with their names retained for compatibility with common comment exports.

| Field | Description | Example |
| --- | --- | --- |
| `dataType` | Item kind | `comment` |
| `id` | Full post or comment identifier | `t1_p93c1nf` |
| `parsedId` | Identifier without kind prefix | `p93c1nf` |
| `url` | Direct item permalink | `https://www.reddit.com/r/AskReddit/comments/1wd5nqd/.../p93c1nf/` |
| `postId` | Full parent post identifier | `t3_1wd5nqd` |
| `parsedPostId` | Parent post identifier without prefix | `1wd5nqd` |
| `postTitle` | Parent post title | `What’s the worst video game that you have ever bought?` |
| `postUrl` | Parent post permalink | `https://www.reddit.com/r/AskReddit/comments/1wd5nqd/.../` |
| `postUpVotes` | Archived post upvotes | `19` |
| `postCommentsCount` | Comment total in the archived post record | `230` |
| `subredditName` | Community without the `r/` prefix | `AskReddit` |
| `communityName` | Community with the `r/` prefix | `r/AskReddit` |
| `parsedCommunityName` | Community without the prefix on comment items | `AskReddit` |
| `title` | Post title on post items | `What’s the worst video game that you have ever bought?` |
| `body` | Archived post self text or comment text | `Battlefield 2042` |
| `bodyHtml` | Rendered comment text, if the source provides it | `null` |
| `authorName` | Public username or deletion marker | `[deleted]` |
| `authorId` | Full author identifier, if available | `null` |
| `parsedAuthorId` | Author identifier without prefix, if available | `null` |
| `authorFullname` | Full author identifier, if available | `null` |
| `authorFlairText` | Public author flair on a comment | `null` |
| `authorPremium` | Archived premium status, if available | `null` |
| `score` | Archived net score | `9` |
| `upvoteRatio` | Post upvote ratio on post items | `0.82` |
| `commentsCount` | Post comment total on post items | `230` |
| `parentId` | Full identifier of the parent post or comment | `t3_1wd5nqd` |
| `parsedParentId` | Parent identifier without prefix | `1wd5nqd` |
| `parentKind` | Whether the parent is a post or comment | `post` |
| `depth` | Reply level, starting at zero | `0` |
| `isSubmitter` | Whether the commenter created the post | `false` |
| `commentUpVotes` | Archived comment upvotes | `9` |
| `controversiality` | Archived controversiality value | `0` |
| `totalAwardsReceived` | Archived number of awards | `0` |
| `gilded` | Archived gilding count | `0` |
| `distinguished` | Public distinction, when present | `null` |
| `stickied` | Whether the comment is pinned | `false` |
| `scoreHidden` | Whether its score is hidden | `false` |
| `collapsed` | Whether the comment was collapsed | `true` |
| `collapsedReason` | Reason for collapse, when supplied | `null` |
| `edited` | Whether an edit was recorded | `false` |
| `editedAt` | Edit time when available | `null` |
| `bodyLength` | Character count of archived comment text | `16` |
| `wordCount` | Whitespace-separated word count | `2` |
| `ageHours` | Hours from creation to archive retrieval | `36.0` |
| `scorePerHour` | Archived score divided by that age | `0.25` |
| `commentCreatedAt` | Comment creation time in UTC | `2026-09-11T04:11:29Z` |
| `postCreatedAt` | Post creation time in UTC | `2026-09-11T04:10:46Z` |
| `isNsfw` | Whether a post carries an 18+ flag | `false` |
| `isRemoved` | Whether the stored body is a removal marker | `false` |
| `archivedAt` | Archive retrieval time for this record | `2026-09-12T16:11:46Z` |

A post item has post-level fields such as `title`, `upvoteRatio`, `commentsCount`, and `isNsfw`. Comment items carry reply fields such as `parentId`, `depth`, `edited`, and `commentCreatedAt`. The dataset schema describes each field and its possible null value. Dates use UTC and an ISO 8601 string.

### How to scrape Reddit comments

1. Copy a public Reddit post URL. A normal `/r/community/comments/post-id/.../` URL works, as does a permalink to one of that post’s comments. A short `redd.it/post-id` link also works.
2. Add the URL to **Post URLs**. Add more URLs if you want several discussions in the same dataset. Duplicate references to the same post are processed once.
3. Set **Maximum comments per post** to control the size of each thread. The default is 1,000. Set **Maximum total items** if the whole run needs a hard cap across all threads.
4. Start the run. Each available post is written first, followed by archived comments in oldest-first order. Nested replies appear in the same dataset and can be connected with `parentId`.
5. Download the dataset as JSON or CSV. For spreadsheet work, import the CSV and filter `dataType` to `comment` if you only need replies.

A comment permalink identifies its parent thread; it does not restrict the output to that one comment. The scraper retrieves the archived discussion for the entire post. For a single comment, filter the resulting dataset by `id` or `parsedId`.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `postUrls` | URL list | Empty; required | Post URLs, comment permalinks, or short post links. |
| `maxCommentsPerPost` | Integer | `1000` | Comment cap for each post, from 1 to 10,000. The parent post item is additional. |
| `maxItems` | Integer | `0` | Hard cap across all post and comment items. Zero removes the overall cap. |
| `includeNSFW` | Boolean | `false` | Include archived posts flagged as 18+ and their comments. |
| `proxyConfiguration` | Object | Platform proxy | Connection settings for retrieving public archive records. |

Example input for one discussion:

```json
{
  "postUrls": [
    {"url": "https://www.reddit.com/r/AskReddit/comments/1wd5nqd/whats_the_worst_video_game_that_you_have_ever/"}
  ],
  "maxCommentsPerPost": 150,
  "maxItems": 151,
  "includeNSFW": false
}
```

With these limits, the verified run produced 151 dataset items: one post and 150 comments. The post item counts toward `maxItems`, so set that value to at least one more than the number of comments you want from a single post. If you supply several URLs, `maxCommentsPerPost` resets for each post, while `maxItems` remains a run-wide cap.

### Output example

This shortened item comes from the verified run above. The archive stored this comment as a top-level reply. The `parentId` therefore begins with `t3_`, and `depth` is zero. Additional columns described in the table are present in the full dataset item.

```json
{
  "dataType": "comment",
  "id": "t1_p93c1nf",
  "parsedId": "p93c1nf",
  "body": "Battlefield 2042",
  "postId": "t3_1wd5nqd",
  "postTitle": "What’s the worst video game that you have ever bought?",
  "subredditName": "AskReddit",
  "parentId": "t3_1wd5nqd",
  "parentKind": "post",
  "depth": 0,
  "score": 9,
  "commentCreatedAt": "2026-09-11T04:11:29Z",
  "archivedAt": "2026-09-12T16:11:46Z",
  "isRemoved": false,
  "url": "https://www.reddit.com/r/AskReddit/comments/1wd5nqd/whats_the_worst_video_game_that_you_have_ever/p93c1nf/"
}
```

The parent post record is a separate item with `dataType: "post"`. Its `id` begins with `t3_`. In a multi-post run, group by `postId`; then use `parentId` to connect a reply to its parent. A missing parent can occur when the archive lacks that ancestor, so `depth` may be null for the affected branch. The original `parentId` remains available even then.

### Use cases

- **Discussion research:** Collect the text, timestamps, and reply structure of a known public thread. Separate top-level responses from follow-up conversations with `depth` and `parentKind`.
- **Product feedback review:** Export replies to a product announcement or question. Group comments by parent to see which observations sparked longer exchanges.
- **Community reporting:** Capture a dated snapshot of a public discussion, including archived scores and moderation markers. Retain `archivedAt` alongside the observations so reports do not imply a live reading.
- **Conversation mapping:** Rebuild a reply graph using `id` and `parentId`. The two prefixes distinguish posts from comments without needing to parse URLs.
- **Editorial research:** Collect direct comment links and creation times for later manual review. Filter removed bodies and hidden scores before quoting or summarizing a thread.
- **Spreadsheet analysis:** Compare several known threads in one CSV by filtering on `postId`, `subredditName`, `commentCreatedAt`, or `isSubmitter`.

Use only public discussion data for these workflows. Public usernames can still be personal data in context. Handle exports according to the rules that apply to your project and location.

### How much does it cost to scrape Reddit comments?

The configured result price is **$1.20 per 1,000 dataset items**. Each written post or comment is one item. The run’s 151 items therefore represent **$0.1812** in result charges. A 1,001-item dataset, such as one post plus 1,000 comments, represents **$1.2012**. A 5,005-item dataset, such as five posts plus 5,000 comments, represents **$6.006**. These examples use the item price only; check the run details for the final account charge and any account-specific limits.

The per-post limit is a maximum, not a promise of that many comments. A small thread, an excluded 18+ post, or an incomplete archive can yield fewer items. A post with no archived comments can still produce its parent post item if the post record is available. Setting `maxItems` limits how many items can be written across the run and is useful when the number of comments is uncertain.

If a dataset is used for periodic monitoring, a new run is a new snapshot and writes new items. Charges are based on the items produced by that run. The scraper does not compare its results with prior datasets or charge only for changes. Keep a stable `id` key in your destination if you intend to merge repeated exports.

### Tips for best results

Use a full post URL when possible. The important part is the post ID after `/comments/`; the title slug is only for readability. URLs copied from a comment within that thread also contain the post ID and work for the full discussion. Short post URLs in the `redd.it/post-id` form work as well. Links in the `/s/` share format do not expose the post ID in the path and are not supported.

Start with a limit of 100 to inspect the available fields and archive coverage. Raise `maxCommentsPerPost` when the thread has more relevant material. The archive returns up to 100 comments per request, and the scraper moves through successive pages by creation time. Large threads require more requests and take longer. The `postCommentsCount` field comes from the archived post record, which may have been retrieved at a different time from the comments. It is context, not a guaranteed count of rows available to this scraper.

For several threads, keep a run-wide `maxItems` if you have a fixed dataset size in mind. Posts are processed in input order. Since a parent post is written before its comments, an overall cap can stop in the middle of a later thread. Increase the cap or run that thread separately if you need more of it.

When comparing score values, use `archivedAt`. The archive may have stored a comment shortly after publication, before voting settled. `ageHours` and `scorePerHour` are computed from the comment’s creation time and the archive retrieval time, not the current clock. This makes their meaning stable in a saved export but does not turn an old score into a live score.

### Limits

This scraper uses a public archive because the public Reddit page, JSON listing, comment expansion route, alternate hosts, and feed routes were blocked or redirected to login during development. Archive records are historical snapshots. New posts may take time to appear; some comments, edits, removals, or whole threads may never be captured. The archive may be temporarily unavailable or rate limited. A successful run means the returned records were processed; it does not prove the entire live thread was captured.

The maximum input setting is 10,000 comments per post, but actual coverage depends on what the archive holds. Pagination uses a second-resolution creation timestamp. Extremely busy threads with more than 100 comments created within one second could leave a gap at a page boundary. The actor removes duplicate IDs within each post; it cannot recover records that are absent from the source.

The archive does not reliably provide full author identifiers, premium status, or rendered comment HTML. Those compatibility fields remain null. Replies whose parent is absent retain `parentId`, but their `depth` is null because the missing ancestor prevents an accurate depth calculation. Removed or deleted bodies may appear as `[removed]` or `[deleted]`; the scraper does not restore their original text. Post counts, scores, flags, and flair reflect the stored record and may differ from the live site.

Only public post and comment URLs are accepted. Subreddit listing pages, profile pages, search URLs, and `/s/` share links are outside this input format. No account-required content is retrieved. An 18+ post is skipped unless `includeNSFW` is enabled; that flag does not override an unavailable archive record. Validate coverage against your intended use before drawing conclusions from comment counts or sentiment.

### Automated use

The input and output are regular JSON, so a scheduled workflow can provide post URLs and read the resulting dataset. For example, a task could request: “Collect up to 200 archived comments from this public post and return the oldest ten top-level replies with their permalinks.” Another could request: “Run these three post URLs, then group the exported comments by `postId` and count replies with `parentKind` equal to `comment`.” Both tasks should carry `archivedAt` into any report that describes the discussion.

For repeat runs, store the original input and the run time beside your exported data. A comment’s `id` is a practical deduplication key for that thread across runs. The content or score for the same identifier may change if a later archive retrieval replaces an earlier snapshot. Decide whether your destination keeps the earliest version, latest version, or both before scheduling frequent runs.

### FAQ

#### Is it legal to collect Reddit comments?

This actor reads public records, but you are responsible for using the data lawfully. Respect applicable terms, privacy rules, and the rights of people whose public comments you process. Do not use public usernames for personal-data misuse. If your project republishes text, consider the context and any removal or deletion markers.

#### Do I need an account or a proxy?

No account is required. The default connection settings use the platform proxy and were sufficient for the verified archive run. You can supply a different proxy configuration if your environment requires one. Proxy settings cannot make a missing archive record appear.

#### How fast is a run?

Time depends on the number of posts, the comment limit, archive response time, and rate limits. The verified 151-item run completed successfully, including a second comment page. A 10,000-comment request needs many more pages and should be expected to take longer. Judge speed from your own run details for your typical thread sizes.

#### Can I schedule and monitor it?

Yes. Use the platform’s schedule and run status features, then inspect item counts and logs. A completed run with very few items may reflect source coverage rather than a parsing error. Track `archivedAt` and the difference between `postCommentsCount` and exported comments when monitoring important threads.

#### Can I export to a spreadsheet?

Yes. Download CSV and open or import it in a spreadsheet. Filter `dataType` to separate posts from comments. `postId`, `parentId`, and ISO dates make it straightforward to group threads, build reply tables, and sort chronologically.

#### Why is a field empty?

Some properties do not exist in the public archive, and others are optional on Reddit. Author IDs, premium status, and rendered body HTML are currently null. Flair, edit times, collapse reasons, and distinctions are often null even for otherwise complete comments. A null `depth` means the archive did not provide enough parent records to calculate it safely.

#### Does a comment permalink return only that comment?

No. The permalink contains the parent post ID, and the actor collects that post and its archived discussion. Filter by comment `id` after export when one reply is needed. This behavior also means several comment permalinks from the same post are deduplicated into one thread during a run.

### Integrations

The resulting dataset can be read through the platform’s data endpoint, downloaded as JSON or CSV, passed to a webhook, or imported into a spreadsheet or database. Scheduled runs can refresh a discussion export, while stable `id` and `postId` values support merging and grouping downstream. Keep the archive retrieval time with each row so downstream displays explain when the record was observed.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request. Include a public post URL, the run link, and a short description of the missing or incorrect data so the report can be checked against the archived source.

# Actor input Schema

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

Public Reddit post URLs, comment permalinks, or redd.it short post URLs. Each URL collects its parent post and archived comment tree. Share links using /s/ are not supported.

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

Maximum archived comments to collect for each post, including replies. The post item is additional.

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

Hard cap across posts and comments. Zero means no overall cap.

## `includeNSFW` (type: `boolean`):

Include archived posts flagged as 18+ and their comments.

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

Connection settings for retrieving public archive records. The default uses the platform proxy.

## Actor input object example

```json
{
  "postUrls": [
    {
      "url": "https://www.reddit.com/r/AskReddit/comments/1wd5nqd/whats_the_worst_video_game_that_you_have_ever/"
    }
  ],
  "maxCommentsPerPost": 1000,
  "maxItems": 0,
  "includeNSFW": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Parent post items and archived comments in the default dataset.

# 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": [
        {
            "url": "https://www.reddit.com/r/AskReddit/comments/1wd5nqd/whats_the_worst_video_game_that_you_have_ever/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mlg14/reddit-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": [{ "url": "https://www.reddit.com/r/AskReddit/comments/1wd5nqd/whats_the_worst_video_game_that_you_have_ever/" }] }

# Run the Actor and wait for it to finish
run = client.actor("mlg14/reddit-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": [
    {
      "url": "https://www.reddit.com/r/AskReddit/comments/1wd5nqd/whats_the_worst_video_game_that_you_have_ever/"
    }
  ]
}' |
apify call mlg14/reddit-comments-scraper --silent --output-dataset

```

## MCP server setup

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