# Reddit Subreddit Post & Comment Harvester (`vexaloom-labs/reddit-subreddit-post-and-comment-harvester`) Actor

Pull posts from any subreddit with score, media, flairs, awards and moderation flags, plus optional nested comment threads. Filter by date, score, keyword, author, post type and more. No API key or login.

- **URL**: https://apify.com/vexaloom-labs/reddit-subreddit-post-and-comment-harvester.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Social media, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Subreddit Post & Comment Harvester

Harvest posts from any public subreddit — hot, new, top, rising, controversial or best — with score, author, flair, selftext, images, galleries, video, polls and crossposts. Add full nested comment threads when you need the discussion too. Rich date, score, comment-count, upvote-ratio, keyword and author filters; no API key, no login, no proxy setup.

### What this actor does

- **Any public subreddit:** bare name (`python`), `r/`-prefixed name (`r/python`) or a full Reddit URL; multiple subreddits in one run
- **Six listing modes:** `hot`, `new`, `top`, `rising`, `controversial`, `best`; `top` and `controversial` support Reddit's six time windows
- **Deep history:** follows Reddit's listing pages to the practical ceiling (roughly 1,000 posts for hot/new, about 500 for top/controversial)
- **Posts with media:** score, upvote ratio, comment count, author, flair, selftext, images, gallery images (full resolution), video (HLS + MP4), poll options and votes, crosspost parent
- **Optional comment threads:** full nested trees with 6 comment sorts, depth, parent links, OP flag, edited/deleted flags and awards
- **Rich filtering:** date range, score, comment count, upvote ratio, post type, flair, title/body keywords, authors, domain, awards, content length and moderation flags
- **Empty fields are omitted** from the output — no need to defend against `null`/empty values downstream

### Output per post

- `dataType` — always `post`
- `subreddit`, `subreddit_prefixed`, `subreddit_id`
- `post_id`, `post_name`, `title`
- `author`, `author_id`
- `url`, `permalink`, `content_url`, `domain`
- `is_self`, `post_type` (`self`/`link`/`image`/`video`/`gallery`/`poll`), `post_type_raw`
- `score`, `upvote_ratio`, `num_comments`
- `link_flair`, `link_flair_background_color`, `flair_url`
- `content` (self-post text), `content_html`
- `thumbnail_url`
- `created_utc`, `created_at` (ISO-8601 UTC)
- `is_stickied`, `is_nsfw`, `is_spoiler`, `is_crosspost`
- `is_archived` — when the comment mode loads the post page
- `total_awards_received`, `top_award_id`, `top_award_icon_url`
- `media_type`, `has_media`
- `images[]`, `gallery_images[]`, `gallery_count`
- `video_url` (direct MP4), `video_hls_url` (HLS)
- `poll_data` — options, option ids, total votes, closing time
- `crosspost_parent_id`, `crosspost_parent_subreddit`, `crosspost_parent_url`
- `language`, `crawled_at`, `source`

For crossposts, `content` is the parent text Reddit renders in the listing, while `post_type_raw` is `crosspost` and the `crosspost_parent_*` fields identify the source post.

### Output per comment

Returned when `includeComments` is on and `maxCommentsPerPost` is above 0. The parent post is emitted first, then its comments.

- `dataType` — always `comment`
- `subreddit`, `subreddit_prefixed`, `subreddit_id`
- `post_id`, `post_url`, `post_title`, `link_id`
- `comment_id`, `comment_name`, `parent_id`, `parent_kind`
- `author`, `avatarUrl`
- `body`, `body_html`
- `score`, `total_awards_received`
- `depth` (0 = top-level), `position`, `parentPositions`
- `is_op`, `collapsed`, `contentType`
- `isCommentDeleted`, `isAuthorDeleted`, `isHighlighted`, `hasPaidAward` — present only when true
- `created_utc`, `created_at`, `edited`, `edited_at`
- `permalink`, `crawled_at`, `source`

### Fields not available anonymously

Reddit no longer exposes these to logged-out readers of the public pages, so they are omitted rather than guessed: author flair, raw upvotes/downvotes, post `distinguished` (mod/admin badge), `removed_by_category`, post `locked`, original-content marker, `gilded`, the award-type breakdown, video duration/bitrate/dimensions, and oEmbed title/author/HTML for external links. Link posts still carry their target URL and preview image.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `subreddits` | array | – | **Required.** Subreddit names, `r/`-prefixed names or full URLs |
| `maxPosts` | int | `25` | Max posts per subreddit after filters (1–5,000) |
| `maxItems` | int | `10,000` | Global safety cap on total records (posts + comments) |
| `sort` | enum | `new` | `hot`, `new`, `top`, `rising`, `controversial`, `best` |
| `timeFilter` | enum | `day` | `hour`, `day`, `week`, `month`, `year`, `all` (top/controversial) |
| `includeComments` | bool | `false` | Collect comments for every harvested post |
| `maxCommentsPerPost` | int | `0` | Max comments per post (0–100,000); needs `includeComments` |
| `commentSort` | enum | `confidence` | `confidence`, `top`, `new`, `controversial`, `old`, `qa` |
| `postedAfter` | date | – | Only posts created on/after this date (UTC); switches the listing to `new` |
| `postedBefore` | date | – | Only posts created on/before this date (UTC) |
| `includeNSFW` | bool | `false` | Include posts explicitly flagged NSFW |
| `onlyWithFlair` | bool | `false` | Drop posts without a link flair |
| `postType` | enum | – | `text`, `link`, `image`, `video`, `gallery`, `poll`, `crosspost`, `multi_media` |
| `minScore` | int | – | Minimum post score |
| `maxScore` | int | – | Maximum post score |
| `minComments` | int | – | Minimum comment count |
| `maxComments` | int | – | Maximum comment count |
| `minUpvoteRatio` | number | – | Minimum upvote ratio (0–1) |
| `excludeStickied` | bool | `false` | Drop pinned/stickied posts |
| `excludeCrossposts` | bool | `false` | Drop crossposts |
| `excludeSpoilers` | bool | `false` | Drop spoiler-tagged posts |
| `titleContains` | string | – | Keep posts whose title contains this text |
| `contentContains` | string | – | Keep self posts whose body contains this text |
| `flairContains` | string | – | Keep posts whose flair contains this text |
| `authors` | array | – | Keep posts by these usernames |
| `excludeAuthors` | array | – | Drop posts by these usernames |
| `excludeKeywords` | array | – | Drop posts whose title/body/flair contains any keyword |
| `domain` | string | – | Keep posts whose link domain contains this text |
| `minAwards` | int | – | Minimum total awards |
| `excludeDeletedAuthor` | bool | `false` | Drop posts with a deleted/removed author |
| `minContentLength` | int | – | Minimum self-post body length |
| `maxContentLength` | int | – | Maximum self-post body length |
| `fullSubreddit` | bool | `false` | Walk the `new` listing as deep as Reddit allows |

#### Example: 25 new posts from r/Python

```json
{
  "subreddits": ["python"],
  "maxPosts": 25,
  "sort": "new"
}
```

#### Example: top image posts with media

```json
{
  "subreddits": ["pics"],
  "maxPosts": 50,
  "sort": "top",
  "timeFilter": "week",
  "postType": "gallery"
}
```

#### Example: posts plus full comment threads

```json
{
  "subreddits": ["technology"],
  "maxPosts": 20,
  "sort": "hot",
  "includeComments": true,
  "maxCommentsPerPost": 100,
  "commentSort": "top"
}
```

#### Example: brand monitoring with filters

```json
{
  "subreddits": ["startups", "Entrepreneur", "SaaS"],
  "maxPosts": 100,
  "sort": "new",
  "postedAfter": "2026-10-01",
  "minScore": 5,
  "excludeAuthors": ["AutoModerator"],
  "excludeKeywords": ["megathread", "giveaway"]
}
```

### Use cases

- **Market research** — collect authentic community opinions and pain points
- **Brand monitoring** — track product mentions across subreddits on a schedule
- **Content ideation** — find trending questions and high-engagement topics
- **Training data** — build clean Reddit corpora with full comment context for NLP/LLM work
- **Competitive intelligence** — monitor launches, sentiment and competitor discussions
- **Academic and social research** — export a subreddit's reachable history for discourse analysis

### FAQ

**Do I need a Reddit API key or login?**
No. The actor reads Reddit's public web data — no API key, no OAuth, no cookies, no account.

**Do I need to configure a proxy?**
No. The actor chooses a working connection to Reddit automatically. Just fill in the input and run it.

**What happens with private, banned or misspelled subreddits?**
They return no records and the run continues with the remaining subreddits. If nothing is found, the run finishes with a status message suggesting you check the names or loosen filters.

**How many posts can I get from one subreddit?**
Up to `maxPosts` (5,000), but Reddit serves roughly 1,000 posts per hot/new listing and about 500 for top/controversial. `fullSubreddit` walks the `new` listing to that ceiling. Filters are applied after posts are loaded, so strict filters can return fewer rows than `maxPosts`.

**Why did I get no comments?**
`maxCommentsPerPost` defaults to `0`, which collects none. Set it (for example `100`) and enable `includeComments`. If `maxItems` is tight, the actor keeps every requested post and trims comments first.

**Why are some fields missing from a record?**
The actor omits values Reddit does not expose publicly (author flair, raw upvotes/downvotes, post distinguished/removed flags, award breakdown, video duration). Empty values are omitted rather than emitted as `null`, so records stay clean.

**Why is the video URL a low-resolution MP4?**
Reddit serves a direct low-resolution MP4 plus an HLS playlist. The actor provides both: `video_url` for durable downloads and `video_hls_url` for the adaptive stream when Reddit's signed playlist is available. HLS links can expire; `video_url` is the stable choice.

**Why are some NSFW or spoiler posts still included?**
`includeNSFW` filters posts Reddit explicitly flags. Posts in 18+ communities are served to the actor's adult-content session and cannot be distinguished at community level; use `excludeSpoilers` or keyword filters for tighter control.

**Can I schedule recurring runs?**
Yes — create a schedule in Apify Console for daily or weekly monitoring. Use `postedAfter` or `sort: "new"` to collect only recent posts.

**How do I export or integrate the data?**
Download JSON, CSV, Excel or HTML from the run, call the dataset API, or connect to n8n, Make, Zapier or the Apify MCP server.

# Actor input Schema

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

List of subreddits to scrape. Accepts bare names (python), r/ prefixed (r/python) or full URLs (https://www.reddit.com/r/python/).

## `maxPosts` (type: `integer`):

Maximum number of posts to keep from each subreddit after filters. Reddit serves roughly 1,000 posts per hot/new listing and about 500 for top/controversial.

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

Global safety cap on the total number of records pushed for the whole run (posts and comments combined).

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

How to sort posts in the subreddit. Reddit serves roughly 1,000 posts per hot/new listing and about 500 for top/controversial.

## `timeFilter` (type: `string`):

Time filter when sorting by 'top' or 'controversial'.

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

When enabled, the full comment thread (including nested replies) is collected for every scraped post. Significantly increases run time and output.

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

Maximum number of comments to scrape per post (only used when 'Include comments' is enabled).

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

How to sort the comment threads.

## `postedAfter` (type: `string`):

Only keep posts created on or after this date (UTC). When set, collection runs newest-first (sort=new) and the sort option above is ignored. Reddit caps each listing at roughly 1,000 posts, so a distant date window on a very active subreddit may not be fully reachable.

## `postedBefore` (type: `string`):

Only keep posts created on or before this date (interpreted as the end of that day, UTC).

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

Include posts Reddit explicitly flags as NSFW. Posts in 18+ communities are served by the actor's adult-content cookie and are not distinguishable at community level.

## `onlyWithFlair` (type: `boolean`):

When enabled, posts without a flair tag are dropped from the results.

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

Keep only posts of the selected content type (based on Reddit's listing type). Leave unset to keep all types. Legacy 'self' is treated as 'text'.

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

Only keep posts with a score at or above this value. Leave unset to keep all scores.

## `maxScore` (type: `integer`):

Only keep posts with a score at or below this value. Useful for finding overlooked/low-visibility posts. Leave unset to keep all scores.

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

Only keep posts with at least this many comments. Leave unset to keep all posts.

## `maxComments` (type: `integer`):

Only keep posts with at most this many comments. Useful for finding under-discussed posts. Leave unset to keep all posts.

## `excludeStickied` (type: `boolean`):

When enabled, posts pinned/stickied by moderators (e.g. rules, weekly threads) are dropped from the results.

## `minUpvoteRatio` (type: `number`):

Only keep posts whose upvote ratio (fraction of votes that are upvotes, 0-1) is at or above this value. Useful for filtering out controversial/divisive posts. Leave unset to keep all ratios.

## `excludeCrossposts` (type: `boolean`):

When enabled, posts that are crossposts from another subreddit are dropped from the results, keeping only original submissions to the scraped subreddit.

## `excludeSpoilers` (type: `boolean`):

When enabled, posts marked as a spoiler by their author are dropped from the results.

## `titleContains` (type: `string`):

Only keep posts whose title contains this text (case-insensitive substring match). Leave empty to keep all titles.

## `contentContains` (type: `string`):

Only keep self (text) posts whose body contains this text (case-insensitive substring match). Link/image/video posts have no body text and are excluded when this is set. Leave empty to keep all posts.

## `flairContains` (type: `string`):

Only keep posts whose flair text contains this text (case-insensitive substring match). Leave empty to keep all flairs.

## `authors` (type: `array`):

Only keep posts submitted by one of these usernames (bare name or u/-prefixed, case-insensitive). Leave empty to keep posts from all authors.

## `domain` (type: `string`):

Only keep posts whose link domain contains this text (case-insensitive, e.g. 'youtube.com' or 'self.python'). Leave empty to keep all domains.

## `minAwards` (type: `integer`):

Only keep posts with at least this many total awards. Leave unset to keep all posts.

## `excludeDeletedAuthor` (type: `boolean`):

When enabled, posts whose author account was deleted (shown as \[deleted]/\[removed]) are dropped from the results.

## `excludeAuthors` (type: `array`):

Drop posts submitted by any of these usernames (bare name or u/-prefixed, case-insensitive) — useful for filtering out bots (e.g. AutoModerator) or known spam accounts. Leave empty to keep posts from all authors.

## `excludeKeywords` (type: `array`):

Drop posts whose title, self-text body or flair contains any of these keywords or phrases (case-insensitive substring match) — useful for filtering out megathreads, giveaways or spam boilerplate. Leave empty to keep all posts.

## `minContentLength` (type: `integer`):

Only keep self (text) posts whose body is at least this many characters long. Link/image/video posts have no body and are excluded when this is set. Useful for dropping one-line/low-effort text posts when building NLP datasets. Leave unset to keep all lengths.

## `maxContentLength` (type: `integer`):

Only keep self (text) posts whose body is at most this many characters long. Leave unset to keep all lengths.

## `fullSubreddit` (type: `boolean`):

When enabled, walk Reddit's 'new' listing as deep as the source allows (roughly 1,000 posts per listing) instead of stopping at the first page. Combine with a higher 'Maximum posts per subreddit'.

## Actor input object example

```json
{
  "subreddits": [
    "python",
    "r/programming",
    "https://www.reddit.com/r/webdev/"
  ],
  "maxPosts": 25,
  "maxItems": 5000,
  "sort": "new",
  "timeFilter": "day",
  "includeComments": false,
  "maxCommentsPerPost": 100,
  "commentSort": "confidence",
  "postedAfter": "2025-01-01",
  "postedBefore": "2025-12-31",
  "includeNSFW": false,
  "onlyWithFlair": false,
  "minScore": 10,
  "maxScore": 1000,
  "minComments": 5,
  "maxComments": 500,
  "excludeStickied": false,
  "minUpvoteRatio": 0.8,
  "excludeCrossposts": false,
  "excludeSpoilers": false,
  "titleContains": "launch",
  "contentContains": "tutorial",
  "flairContains": "Discussion",
  "authors": [
    "spez"
  ],
  "domain": "youtube.com",
  "minAwards": 1,
  "excludeDeletedAuthor": false,
  "excludeAuthors": [
    "AutoModerator"
  ],
  "excludeKeywords": [
    "megathread"
  ],
  "minContentLength": 100,
  "maxContentLength": 5000,
  "fullSubreddit": false
}
```

# Actor output Schema

## `postAndCommentRecords` (type: `string`):

Dataset containing one record per post, plus one record per comment when 'Include comments' is on. Records are distinguished by the `dataType` field (`post` or `comment`).

# 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": [
        "python"
    ],
    "maxPosts": 25,
    "maxItems": 10000,
    "sort": "new",
    "timeFilter": "day",
    "includeComments": false,
    "maxCommentsPerPost": 0,
    "commentSort": "confidence",
    "includeNSFW": false,
    "onlyWithFlair": false,
    "excludeStickied": false,
    "excludeCrossposts": false,
    "excludeSpoilers": false,
    "excludeDeletedAuthor": false,
    "fullSubreddit": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/reddit-subreddit-post-and-comment-harvester").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": ["python"],
    "maxPosts": 25,
    "maxItems": 10000,
    "sort": "new",
    "timeFilter": "day",
    "includeComments": False,
    "maxCommentsPerPost": 0,
    "commentSort": "confidence",
    "includeNSFW": False,
    "onlyWithFlair": False,
    "excludeStickied": False,
    "excludeCrossposts": False,
    "excludeSpoilers": False,
    "excludeDeletedAuthor": False,
    "fullSubreddit": False,
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/reddit-subreddit-post-and-comment-harvester").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": [
    "python"
  ],
  "maxPosts": 25,
  "maxItems": 10000,
  "sort": "new",
  "timeFilter": "day",
  "includeComments": false,
  "maxCommentsPerPost": 0,
  "commentSort": "confidence",
  "includeNSFW": false,
  "onlyWithFlair": false,
  "excludeStickied": false,
  "excludeCrossposts": false,
  "excludeSpoilers": false,
  "excludeDeletedAuthor": false,
  "fullSubreddit": false
}' |
apify call vexaloom-labs/reddit-subreddit-post-and-comment-harvester --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/reddit-subreddit-post-and-comment-harvester"
        }
    }
}
```

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/BKePs1sWHACaSpzyh/builds/qdSuwLoNAqkhDc3y4/openapi.json
