# Reddit Posts & Comments Scraper - Subreddits & Search (`eiv/reddit-posts-comments-scraper`) Actor

Reddit scraper for subreddit feeds, keyword search, user submissions and full comment threads. Returns score, upvote ratio, comment count, flair, awards and media URLs per post, with nested comment trees. No login, no API key, no rate-limit quota.

- **URL**: https://apify.com/eiv/reddit-posts-comments-scraper.md
- **Developed by:** [Eimantas V](https://apify.com/eiv) (community)
- **Features:** Limited permissions
- **Stats:** 22 total users, 14 monthly users, 70.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 post scrapes

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

## Reddit Posts & Comments Scraper

Scrape Reddit posts and their comment threads from **subreddits, keyword searches, user profiles and individual post links**. No Reddit account, no API key, and none of the rate-limit quota the official API imposes.

One row per post, one row per comment, and a summary row per source that tells you exactly what was and was not collected.

***

### What you get on every post

| | |
|---|---|
| **Engagement** | score, upvote ratio, comment count, awards |
| **Content** | title, full self-text, post kind (text / link / image / gallery / video / poll) |
| **Links & media** | destination URL, domain, and direct media URLs: the original image files and the highest-resolution video Reddit has, never a resized preview |
| **Context** | subreddit, flair, NSFW, spoiler, pinned, locked, archived |
| **People** | author username plus Reddit's stable account id, which survives a rename |
| **Timing** | UTC timestamp of posting |
| **Moderation** | whether the author posted with a moderator or admin badge |

**Upvote ratio** is the field most Reddit exports leave out, and it is the one that separates a post everyone agreed with from a post that was fought over. Both can score 500.

Self-text comes as clean plain text: paragraphs, list items and code blocks are kept, and every link is written as `[text](url)`, so no URL in a post is lost.

### What you get on every comment

The comment text, author, score, nesting depth, its parent comment id, a direct permalink, and whether it was written by the person who made the post. Every comment row also carries the **post id and post title**, so it reads on its own without joining anything back together.

Comments come back in **thread order**: each top-level comment followed by its replies, so a parent always appears before its replies and the thread reconstructs as it reads on Reddit.

***

### Four ways to point it at Reddit (mix them freely in one run)

**Subreddits.** `programming`, `r/programming`, or the full URL. Sort by hot, new, top, rising or controversial, over any timeframe from the past hour to all time.

**Keyword search.** Across all of Reddit, or restricted to subreddits you name. Reddit's own search operators work, including quoted phrases, `OR`, `author:name` and `flair:"name"`.

**Users.** The posts on a person's profile, sorted by hot, new, top or controversial.

**Individual posts.** Paste any post permalink or `redd.it` short link. Turn on **Also scrape comments** to get its thread as well.

Give it a subreddit twice, once as a URL and once as a bare name, and it is scraped once, not twice.

***

### Filters that cut the bill, not just the spreadsheet

Filtered posts are **never written and never charged**. They are not rows you pay for and then delete.

- **Minimum score** and **minimum comment count**: reach versus discussion
- **Post kind**: only text posts, only outbound links, only images and galleries, or only videos
- **Skip pinned posts**: drops the moderator announcements that sit on top of every page
- **Exclude NSFW**

`minComments` is the one to reach for before turning comments on: it spends the extra requests only where there is a discussion worth reading.

***

### Search results, in full

Reddit's search page shows only a title, a score, a comment count and a date for each result. With **Full post details for search results** on (the default), every result is opened, one small request each, and gains its text, post kind, destination link and media. Turn it off for faster, lighter rows.

Search rows never carry an upvote ratio or flair, because Reddit does not show those outside a subreddit or profile feed. For those fields, scrape the subreddit instead.

***

### An empty result always tells you why

Here is a thing about Reddit that quietly breaks scrapers. Ask it for a subreddit that **does not exist** and it does not return an error: it returns an empty feed, exactly like a real but quiet subreddit. A typo in your input list and a genuinely dead community look the same.

So when a source returns no posts, this Actor checks that source against its own record on Reddit, and tells you which it was:

| `zeroReason` | What it means |
|---|---|
| `listing-empty-confirmed` | The subreddit or user really does exist, and really has nothing matching |
| `search-matched-nothing` | The search ran and matched nothing (for a scoped search, the subreddit was checked too) |
| `all-posts-filtered-out` | Posts were found; **your filters** removed all of them |
| `run-timeout-reached` | The run stopped itself before the platform could kill it |

If the check says the subreddit or user is not there, you get a typed `not-found` error row naming it, not a summary row implying it was empty. That check costs nothing on a normal run, because it only happens for a source that produced nothing.

Every source also gets a **summary row** carrying how many posts were examined, how many the filters dropped, how many pages were read, and why it stopped.

Sources that could not be read at all get a typed **error row** instead, and error rows are never charged:

| `errorClass` | What actually happened |
|---|---|
| `not-found` | No such post or user, or a subreddit that does not exist or is banned, private or quarantined |
| `blocked` / `rate-limited` | A transport problem, retried on a fresh IP before it was reported |
| `bad-input` | A value in your input that is not a subreddit, user, post or search |

***

### Honest about limits, because Reddit has them

- **About 1,000 posts per listing.** Reddit stops paginating any single subreddit feed, search or user profile there, whatever is requested. More than that has to come from more sources (a narrower timeframe, a tighter search, a different sort), and this Actor will not pretend otherwise.
- **Banned, private and quarantined subreddits look alike.** Reddit shows logged-out visitors the same empty page for all of them, and for one that does not exist, so they are reported together as `not-found` rather than guessed apart.
- **Profiles show what their owners choose.** Reddit lets people hide posts from their profile, and a profile source returns the profile as Reddit shows it. To find every post by someone, search for `author:name` as well.
- **Very large comment threads are partial.** Comments are read the way Reddit's own "load more" buttons read them, about ten at a time, up to your per-post limit. When more remain, the post row is marked `commentsTruncated: true`, so you can see it rather than discover it later.
- **Scores are approximate by design.** Reddit fuzzes vote counts to frustrate manipulation. Treat a score threshold as a guide.
- **No subscriber counts, edit times or OC marks.** Reddit no longer shows these to logged-out visitors. The fields `subredditSubscribers` and `editedAt` stay in the dataset as null and `isOriginalContent` as false, so existing integrations keep working.
- **Sorts differ per source.** Reddit accepts `relevance` on a subreddit feed, ignores it, and quietly answers with `hot`. Rather than pass that through, an unsupported sort is swapped for the closest supported one and the change is written to the log.
- **The clock is real.** If a run starts running out of time, it **stops itself cleanly** and marks every summary row `runTimeout` rather than being killed halfway with nothing saved. Posts and their comments are written as each post finishes, so a run you stop early keeps everything it had already collected.

***

### Pricing

Pay per result. You are charged for rows that were actually written.

| Event | Price |
|---|---|
| **Post scraped** | **$1.50 per 1,000 posts** |
| Comment scraped | $0.50 per 1,000 comments |
| Comment thread opened | $2.50 per 1,000 posts, only when comments are on |
| Actor start | $0.00005 per run |

Prices fall on every paid Apify plan, to **$0.90 per 1,000 posts** at the top tier.

Summary rows are free. Error rows are free. Filtered posts are free. Posts Reddit repeated across pages are deduplicated by id and charged once. A request that came back blocked and was retried is not charged.

**A posts-only run is the cheap one.** Reddit serves about 25 posts per page, in about a second, so 1,000 posts from a subreddit take a minute or so and cost $1.50. Turning comments on adds requests for every post, which is why opening a thread is priced separately, including for a post that turns out to have no comments at all, since that answer cost a real request too.

***

### Getting started

Put a subreddit in **Subreddits**, press **Start**, and look at the results. The defaults collect 100 posts per source with comments off, in a few seconds.

Then, when you know what you want:

- Turn on **Also scrape comments** and set **Minimum comment count** so the extra requests go where the discussion is
- Set **Sort** to `top` and **Timeframe** to `year` for a subreddit's greatest hits
- Use **Search keywords** with **Search inside these subreddits** to track a brand, product or competitor across the communities that matter
- Raise **Max posts in the run** once you have seen the shape of the data

Export to JSON, CSV, Excel or XML, or pull the dataset straight from the Apify API.

# Actor input Schema

## `subreddits` (type: `array`):

Subreddits to read, one per line. Accepts a bare name (programming), the r/ shorthand (r/programming), or a full URL. Reddit serves at most about 1,000 posts per subreddit feed, so to go deeper use a narrower sort or timeframe rather than a bigger limit.

## `searchQueries` (type: `array`):

Keyword searches to run, one per line. Reddit's own search operators work here, including quoted phrases, OR, and flair:"name". Leave Search inside subreddits empty to search all of Reddit. Reddit's search shows about 7 results per page, so a search is slower per post than a subreddit feed.

## `searchInSubreddits` (type: `array`):

Restrict every search keyword to these subreddits, one per line. Accepts a bare name, the r/ shorthand, or a full URL. Reddit cannot restrict one search to several subreddits at once, so each keyword and subreddit pair is run as its own search. Ignored when Search keywords is empty. If you fill this in and none of the names can be read, the searches are NOT run against all of Reddit instead - you get a bad-input row per unreadable name, because widening a scoped search silently would return the wrong data at full price.

## `usernames` (type: `array`):

Users whose submitted posts you want, one per line. Accepts spez, u/spez or a profile URL. Returns posts only; a user's comments are not part of this listing.

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

Any mix of Reddit URLs: subreddit pages, user profiles, search result pages with a ?q= term, and individual post permalinks (including redd.it short links). A post URL returns that post; turn on Also scrape comments to get its comment thread too.

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

How Reddit should rank the feed. Reddit supports a different set per source type and silently ignores the rest, so an unsupported choice is swapped for the closest one and reported in the log: subreddits accept hot, new, top, rising and controversial; users accept hot, new, top and controversial; searches accept relevance, hot, top, new and most comments.

## `timeframe` (type: `string`):

The window Top and Controversial rank over, and the window every search is limited to. Ignored by Hot, New and Rising, which have no time window.

## `postType` (type: `string`):

Keep only text posts, only outbound links, only images, or only videos. Image also keeps multi-image galleries. Filtered posts are never written and never charged.

## `minScore` (type: `integer`):

Skip posts scoring below this. Reddit fuzzes scores slightly to frustrate vote manipulation, so treat a threshold as approximate. Negative values are allowed.

## `minComments` (type: `integer`):

Skip posts with fewer comments than this. Useful when you want discussion rather than reach, and it cuts the cost of a comments run sharply.

## `skipStickied` (type: `boolean`):

Drop the moderator posts pinned to the top of a subreddit. They appear on every page of a feed and are almost never the data you want.

## `includeNsfw` (type: `boolean`):

Keep posts Reddit marks over-18. Turn this off to drop them.

## `searchDetails` (type: `boolean`):

Reddit's search page shows only each result's title, score, comment count and date. With this on, every result is opened (one small extra request each) to add the post text, post kind, destination link and media. Needed for Only this kind of post to work on searches. Turn it off for faster, lighter search rows.

## `includeComments` (type: `boolean`):

Fetch the comment thread for every post that passes the filters, and for every post URL. A thread costs one request for its first ~25 comments and one more for each further ~10, so a run with comments takes longer than the same run without them. Use Minimum comment count to spend that time only where there is a discussion to read.

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

Upper bound on comment rows per post. Comments are read the way Reddit's own 'load more' buttons read them, about ten at a time, top-level comments first, and written in thread order so a parent always comes before its replies. When a thread has more than this, or Reddit still offers more, the post row is marked commentsTruncated true rather than pretending the thread was complete.

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

How Reddit should order the comment tree. Best is Reddit's own default ranking.

## `maxPostsPerSource` (type: `integer`):

Output cap per subreddit, search or user. Reddit stops paginating a single listing at about 1,000 posts however many pages are requested, so 1,000 is the ceiling here too.

## `maxTotalPosts` (type: `integer`):

Hard cap across every source, so a long list of subreddits cannot run away with the bill. Reached first, it stops the run and every remaining source gets a summary row saying so.

## `maxPagesPerSource` (type: `integer`):

Work cap per source, counted in units of 100 posts read (Reddit serves them about 25 at a time, so one unit is about four requests). Ten covers the whole reachable feed. The extra headroom is for a shifting listing that repeats posts across pages, which Hot constantly does, since repeats are discarded and do not count toward the post cap.

## `maxConcurrency` (type: `integer`):

How many requests may be in flight at once, across the whole run rather than per source.

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

Minimum gap between the start of one request and the next, applied globally rather than per worker, so this really is the run's request rate. Raise it if the log reports HTTP 429.

## `proxyConfig` (type: `object`):

Leave this alone unless you know otherwise. Reddit refuses datacenter IPs, so the Actor uses Apify's residential proxies, which get through in about a second per page. An old saved setting of UNBLOCKER is switched to residential automatically, because Unblocker no longer reaches Reddit. Your own proxy URLs work too, if they are residential.

## Actor input object example

```json
{
  "subreddits": [
    "programming"
  ],
  "sort": "hot",
  "timeframe": "all",
  "postType": "any",
  "minScore": 0,
  "minComments": 0,
  "skipStickied": false,
  "includeNsfw": true,
  "searchDetails": true,
  "includeComments": false,
  "maxCommentsPerPost": 50,
  "commentSort": "confidence",
  "maxPostsPerSource": 100,
  "maxTotalPosts": 1000,
  "maxPagesPerSource": 10,
  "maxConcurrency": 5,
  "requestDelayMs": 250,
  "proxyConfig": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per post with score, upvote ratio, comment count, flair, awards and media URLs, plus one row per comment with its author, score and position in the thread. Every source also gets a summary row saying how many posts were read, how many were filtered out, and why the run stopped.

# 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 = {
    "subreddits": [
        "programming"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eiv/reddit-posts-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 = { "subreddits": ["programming"] }

# Run the Actor and wait for it to finish
run = client.actor("eiv/reddit-posts-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 '{
  "subreddits": [
    "programming"
  ]
}' |
apify call eiv/reddit-posts-comments-scraper --silent --output-dataset

```

## MCP server setup

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