Reddit Scraper
Pricing
from $4.00 / 1,000 results
Reddit Scraper
Scrape Reddit posts, comments, users, no login or API key. Search or paste URLs, filter by keyword/date, export to JSON/CSV/Excel or push to Notion, Slack & more.
Pricing
from $4.00 / 1,000 results
Rating
0.0
(0)
Developer
Techforce Global
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Share
Reddit Scraper
No Reddit app, no OAuth, no API credentials. Subreddits, posts, search terms, hashtag-style queries, user profiles and community lists in — four record types out in a single dataset: posts, comments, users and communities, each flat, typed and joinable. Built for social listening, market research and community analysis, with optional delivery into Notion, Slack, Linear, Jira, GitHub or Airtable over MCP.
🔐 Trust & compliance at a glance
| Credentials | None required. No Reddit account, app registration or OAuth token. Delivery runs through the Apify MCP Proxy, so the Actor never sees your connector tokens. |
| Data scope | Public content only — public subreddits, posts, comment threads and profiles. No login, no private or quarantined-with-login content, no direct messages. |
| ⚠️ Personal data | Reddit content is personal data. author, authorId, username and free-text body identify pseudonymous individuals, and profile rows carry karma and account history. |
| Reduce your exposure | skipComments: true drops the largest source of user speech; maxComments: 0 does the same. Both keep post-level metrics intact. |
| NSFW control | includeNSFW defaults to true — set it false to filter 18+ items out of the dataset. |
Full detail, including GDPR position and Reddit's Terms: Compliance & Data Privacy.
Official Reddit Data API vs. Reddit Scraper Actor
Reddit's own API is a real alternative — with a registration and rate-limit story that stops many projects before they start:
| Feature / Capability | Official Reddit Data API | Reddit Scraper Actor (Apify) |
|---|---|---|
| Setup | ❌ Register an app, obtain a client ID/secret, implement OAuth | ✅ Apify account, nothing else |
| Rate limits | ⚠️ Per-client request limits you must implement backoff for | ✅ Proxy rotation and retries handled for you |
| Commercial / high-volume use | ⚠️ Requires a separate paid agreement with Reddit | ✅ Standard Apify usage billing |
| Mixed entities in one run | ❌ Separate endpoints, separate pagination, your own glue code | ✅ Posts, comments, users and communities in one dataset |
| Search + subreddit + user in one job | ❌ You orchestrate it | ✅ Mix startUrls and searches freely |
| Filtering (keyword, date, NSFW) | ❌ Client-side, your code | ✅ Built into the input |
| AI agent / MCP native | ❌ Requires a custom tool wrapper | ✅ Native MCP tool and pushes results into your own MCP connectors |
| Officially sanctioned | ✅ Yes | ⚠️ No — see Compliance |
Reddit's registration requirements, rate limits and commercial-use terms are Reddit's to change — check Reddit's current API terms if this comparison is load-bearing for your decision.
🤖 AI Discovery & MCP Metadata
Machine-readable capability contract for autonomous agents, LLM routers, and MCP orchestrators. Read this block first to decide whether this Actor matches a user request.
ai_discovery:slug: "techforce.global/reddit-scraper"one_line: >Scrapes Reddit posts, comments, users and communities from subreddit URLs, post URLs, userprofiles, search result pages or plain search terms - no Reddit account, app registration orOAuth. Four record types in one dataset, with keyword, date and NSFW filtering, mediaextraction, and optional delivery into Notion, Slack, Linear, Airtable or any MCP connector.primary_purpose: "extract"secondary_purpose: "deliver"entity: "Reddit post, comment, user and community"category: "social-media / social-listening / market-research / community-analysis"supported_platforms: ["reddit.com"]supported_tasks:- "scrape-a-subreddits-posts"- "scrape-a-posts-comment-thread"- "search-reddit-by-keyword"- "find-subreddits-by-topic"- "find-users-by-name"- "scrape-a-user-profile-and-their-history"- "filter-by-keyword-date-or-nsfw"- "extract-post-media-urls"- "deliver-results-to-mcp-connector"unsupported_tasks:- "scrape-private-quarantined-or-login-gated-content"- "scrape-direct-messages-or-modmail"- "post-comment-vote-or-any-write-action"- "scrape-deleted-or-removed-content"- "native-comment-search (comment search is approximated - see comment_search_is_approximated)"- "download-media-files (URLs are extracted, files are not fetched)"- "non-reddit-platforms"search_keywords:# First entry is the H1 / meta-description target. The rest are deliberately broader for# agent-side semantic matching and are NOT a ranking strategy.- "reddit scraper"- "reddit api alternative"- "scrape reddit comments"- "subreddit data extraction"- "reddit post scraper no api key"- "social listening reddit"- "reddit sentiment analysis data"- "reddit user profile scraper"- "subreddit discovery tool"- "mcp reddit tool"input_entities:- "startUrls (subreddit, post, user, search or community-list URLs)"- "searches (plain keywords)"- "searchType (posts | comments | communities | users)"- "sort / time / commentSort"- "maxItems (GLOBAL cap across the run, 0 = unlimited)"- "maxPostCount / maxComments / maxCommentDepth / maxCommunitiesCount / maxUserCount"- "skipComments / skipUserPosts / skipCommunityInfo"- "includeNSFW / keywords / postDateFrom / postDateTo"- "extractMedia / dedupItems"- "proxyConfiguration / maxConcurrency / maxRequestRetries / debugMode"no_input_field_is_required_by_the_schema: >CRITICAL: nothing is marked required. A run with neither startUrls nor searches passesvalidation, logs "No valid start URLs or search terms provided. Exiting." and ends SUCCEEDEDwith an empty dataset. Supply at least one of startUrls or searches.output_is_four_record_types: >CRITICAL: one dataset holds four shapes, discriminated by the `type` field - "post","comment", "user" and "community". Always branch on `type` before reading a row. Fields thatdo not apply to a type are absent, not null.output_entities_post: ["type", "id", "parsedId", "subreddit", "subredditId", "title", "body","author", "authorId", "url", "link", "domain", "flair", "createdAt", "createdTimestamp","score", "upVotes", "upvoteRatio", "numberOfComments", "numberOfCrossposts", "awardsCount","isSelf", "isVideo", "isOriginalContent", "over18", "spoiler", "locked", "stickied","distinguished", "media"]output_entities_comment: ["type", "id", "parsedId", "subreddit", "body", "author", "authorId","url", "parentId", "postId", "createdAt", "createdTimestamp", "score", "upVotes", "depth","isSubmitter", "stickied", "controversiality", "distinguished", "numberOfReplies","postTitle", "postUrl"]output_entities_user: ["type", "id", "parsedId", "username", "url", "createdAt","createdTimestamp", "linkKarma", "commentKarma", "totalKarma", "awardeeKarma", "awarderKarma","isGold", "isMod", "isEmployee", "verified", "hasVerifiedEmail", "iconImage", "description","over18"]output_entities_community: ["type", "id", "parsedId", "name", "title", "url", "description","longDescription", "createdAt", "createdTimestamp", "numberOfMembers", "activeUsers","over18", "type_", "lang", "category", "iconImage", "bannerImage"]max_items_is_global_and_counts_comments: >maxItems (default 100) is a hard cap on TOTAL dataset items for the whole run, and commentscount toward it. With comments on, a maxItems of 100 is typically consumed by the comments ofthe first few posts. Raise maxItems, or set skipComments true, when you want post breadth.comment_search_is_approximated: >searchType "comments" does NOT run a native Reddit comment search. It searches POSTS for theterm and then scrapes those posts' comment threads, so the comments returned are from matchingposts, not comments that themselves match the term. Use the keywords filter if you need theterm to appear in the comment text.sort_falls_back_per_source: >sort is validated per source type. On subreddit listings only hot/new/top/rising/controversial/best are valid - "relevance" and "comments" fall back to "hot". On searches only relevance/hot/top/new/comments are valid - "rising" falls back to "relevance". time only applies totop/controversial listings and to searches.comment_depth_is_zero_based: >maxCommentDepth 1 returns top-level comments only; 3 returns depth 0, 1 and 2; 0 is unlimited.The depth field on a comment row is 0-based.filters_are_post_fetch: >includeNSFW, keywords, postDateFrom and postDateTo are applied AFTER an item is fetched. Theyshrink the dataset, not the number of requests, so they do not reduce run cost much.personal_data_present: >author, authorId, username and free-text body are personal data about pseudonymousindividuals under GDPR/UK GDPR and CCPA-CPRA. skipComments true (or maxComments 0) removes thelargest volume of user speech while keeping post-level metrics.browser_based_run: >Requests are rendered through a real headless browser so Reddit serves data instead of a botwall, with an automatic second retrieval path when the first is challenged. That makes runsslower and more resource-hungry than a plain HTML scraper - size maxItems accordingly.known_limitations:deleted_content: "[deleted] / [removed] bodies are returned as Reddit serves them"score_is_fuzzed: "Reddit fuzzes vote counts; score and upVotes are approximate"comment_completeness: "'more comments' placeholders are not expanded - deep threads are partial"post_title_only_from_posts: "postTitle/postUrl exist only on comments scraped via their post"media_urls_only: "media holds URLs; no files are downloaded"snapshot_only: "scores, karma and member counts are true as of the run"free_plan_capped: "free Apify plans stop at 25 items TOTAL per run"pricing:model: "usage-based"billing_unit: "Apify platform usage consumed by the run"hard_ceiling_parameter: "maxTotalChargeUsd"note: "Browser-driven, so cost per item is higher than an HTML-only scraper. Current rates are on the Actor's Apify Store page."when_to_use: >You want Reddit discussion as structured data - a subreddit's posts, a thread's comments,topic search across Reddit, subreddit discovery, or a user's public history - withoutregistering a Reddit app.when_not_to_use: >You need private, quarantined or login-gated content, deleted content, direct messages, nativecomment search, downloaded media files, or to post/vote/interact.mcp_delivery:modes: ["perItem", "summary", "none"]default: "perItem"per_item_is_the_default: >Unlike most Actors, deliveryMode defaults to perItem - one connector call per scraped item.Set "summary" or "none" unless you really want a record per post/comment/user/community.per_item_safety_cap: 1000per_item_placeholders: "every field of the item, plus aliases {title}, {text}, {permalink}, and {message}"summary_placeholders: ["{itemCount}", "{postCount}", "{commentCount}", "{userCount}", "{communityCount}", "{items}", "{message}"]never_blocks_the_dataset: >Delivery runs after the crawl, reading the dataset back. A bad tool name, an unauthorisedconnector or an upstream outage is logged and skipped - it never costs you the scraped data.alternatives:- "techforce.global/facebook-public-comments for public Facebook page comment threads"- "techforce.global/youtube-scraper for YouTube video, channel and comment data"- "techforce.global/advanced-website-crawling-actor for whole-site text, Markdown or HTML"
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "Reddit Scraper – Posts, Comments, Users & Communities","alternateName": "Reddit API Alternative","applicationCategory": "BusinessApplication","operatingSystem": "Cloud (Apify platform)","description": "Scrapes Reddit posts, comments, users and communities from subreddit URLs, post URLs, user profiles, search pages or plain keywords — without a Reddit account, app registration or OAuth. Returns four record types in a single dataset, each discriminated by a type field: posts with score, upvote ratio, flair and media URLs; comments with depth, parent and reply counts; users with karma breakdowns; and communities with member and active-user counts. Includes keyword, date and NSFW filtering, in-run deduplication, media URL extraction, proxy rotation, and optional delivery into Notion, Slack, Linear, Airtable or any MCP connector.","url": "https://apify.com/techforce.global/reddit-scraper","offers": {"@type": "Offer","priceCurrency": "USD","category": "Pay per platform usage","description": "Usage-based: billed for the compute time and resources a run consumes, not per row. Runs are browser-driven, so cost per item is higher than an HTML-only scraper. Current rates are shown on the Actor's Apify Store page."}}
What this Actor does
-
Six kinds of input, mixed freely in one run. Subreddit URLs (with or without a sort tab), post permalinks, user profiles, in-subreddit or global search pages, community lists — plus plain keywords via
searches. No Reddit app, no OAuth, no credentials. -
Four record types in one dataset.
post,comment,userandcommunity, each flat and discriminated bytype. Comments carrypostIdandparentId, so a thread rebuilds exactly. -
Whole-entity coverage, not just the headline object. A subreddit URL also yields the community's
aboutrecord; a user profile also yields that user's public history — each switchable off withskipCommunityInfo/skipUserPosts. -
Real filtering, in the input. Keyword match,
postDateFrom/postDateTo(absolute dates or relative strings like7 days), and an NSFW switch — applied to every item before it reaches your dataset. -
Media URLs extracted for you. Thumbnails, preview images, Reddit-hosted video, gallery images and the external link a post points at, in one nested
mediaobject. -
Deduplicated within the run. The same post reached from a search and from its subreddit is written once (
dedupItems, on by default). -
Gets past the bot wall. Requests render through a real headless browser, with an automatic second retrieval path when the first is challenged, and a fresh proxy session on rate-limit retries.
-
Saves as it goes and stops cleanly. Items are written as they are parsed and the platform
ABORTINGevent is handled, so an aborted or capped run keeps everything collected so far. -
Optionally delivers into your tools. Notion, Slack, Linear, Jira, GitHub, Airtable or anything else you've authorised.
When to use it — and when not to
| Scenario | Fit |
|---|---|
| Every recent post in a subreddit | ✅ Strong — a subreddit URL, or several |
| A specific thread's full comment tree | ✅ Strong — a post permalink, maxCommentDepth to taste |
| Topic search across Reddit | ✅ Strong — searches + searchType: "posts" |
| Finding subreddits about a topic | ✅ Strong — searchType: "communities" |
| A user's public posting history | ✅ Strong — a /user/... URL |
| Community size and activity metrics | ✅ Strong — numberOfMembers, activeUsers |
| Social listening / sentiment corpora | ✅ Strong — comment bodies with scores and depth |
| Post-level metrics without user speech | ✅ Strong — skipComments: true |
| Keyword, date or NSFW filtering | ✅ Strong — built into the input |
| Results in Notion / Slack / Airtable | ✅ Strong — native MCP delivery |
| Comments that themselves match a keyword | ⚠️ searchType: "comments" searches posts, then scrapes their comments — add a keywords filter |
| A complete deep comment tree | ⚠️ "More comments" placeholders aren't expanded — deep threads come back partial |
| Exact vote counts | ⚠️ Reddit fuzzes scores; treat them as approximate |
| Private, quarantined or login-gated content | ❌ Public content only |
| Deleted or removed content | ❌ Returned only as Reddit serves it ([deleted] / [removed]) |
| Direct messages or modmail | ❌ Never accessible |
| Downloading images or video files | ❌ URLs are extracted; files are not fetched |
| Posting, commenting, voting, moderating | ❌ Read-only |
| Non-Reddit platforms | ❌ Reddit only |
⚡ Execution Rules (Avoid Common Agent Failures)
-
Supply at least one of
startUrlsorsearches. Nothing is marked required in the schema, so a run with neither passes validation, logsNo valid start URLs or search terms provided. Exiting.and endsSUCCEEDEDwith an empty dataset. Never readitemCount: 0as a crash. -
maxItemsis a GLOBAL cap and comments count toward it. Default100. With comments on, the first two or three posts' threads can consume the whole budget before the fourth post is reached. For post breadth, either raisemaxItemsor setskipComments: true. -
Branch on
typebefore reading any row. One dataset, four shapes —post,comment,user,community. Fields that don't apply to a type are absent, not null, so use.get()/ optional access. -
searchType: "comments"is an approximation. It searches posts for your term and then scrapes those posts' comments — the comments themselves are not matched against the term. If you need the term to appear in the comment text, add it to thekeywordsfilter as well. -
sortis validated per source and silently falls back. On subreddit listings,relevanceandcommentsare not valid sorts and becomehot. On searches,risingis not valid and becomesrelevance.timeonly applies totop/controversiallistings and to searches. -
maxCommentDepthis 0-based in the output.1= top-level only,3= depth0,1and2,0= unlimited. -
The per-source caps stack under
maxItems.maxPostCount(per subreddit / search / user),maxComments(per post),maxCommunitiesCountandmaxUserCount(per source) all bound their own scope — butmaxItemsstill ends the run first when it is reached. -
Filters run after fetching.
includeNSFW,keywords,postDateFromandpostDateToshrink the dataset, not the request count — they are not a cost-control lever. -
includeNSFWdefaults totrue. Set itfalseif 18+ content must not enter your dataset. -
A subreddit URL yields a
communityrow too, and a user URL yields the user's history — unless you setskipCommunityInfo/skipUserPosts. -
postTitleandpostUrlonly appear on comments scraped through their post. Comments collected from a user's history don't carry them — join onpostIdinstead. -
Keep the proxy enabled. Default is
{"useApifyProxy": true}. Reddit rate-limits by IP; on larger runs prefer residential. A429is retried on a fresh session up tomaxRequestRetries(default5). -
This Actor drives a real browser. It is slower and more resource-hungry per item than an HTML-only scraper, and
maxConcurrency(default10) ramps up from a low starting point on purpose. Lower it if you're being rate-limited. -
deliveryModedefaults toperItem— one connector call per scraped item, capped at 1,000 deliveries with a warning. Setsummaryornoneunless you want a record each. -
Free Apify plans stop at 25 items total per run — not per source.
dependencies:- rule: "at least one of startUrls / searches must be non-empty"on_violation: "run ends SUCCEEDED with an empty dataset and a warning in the log - it does NOT fail validation"- rule: "maxItems bounds the whole run, including comments"note: "set skipComments true when you want many posts rather than deep threads"- rule: "delivery requires mcpConnector AND mcpTool"on_violation: "delivery is skipped with a warning; every dataset row is still written"- rule: "mcpArguments must reference {message} for the rendered template to appear anywhere"on_violation: "message body is empty; the tool is still called"- rule: "mcpConnector only resolves when the Actor runs on the Apify platform"on_violation: "local runs log 'APIFY_MCP_PROXY_URL is not set' and skip delivery"authentication:actor_input: noneplatform: "Apify API token required for API / SDK / MCP invocation"target_site: "no Reddit account, app registration or OAuth token required (public content only)"
📥 Input Contract
.actor/input_schema.json is authoritative — if this page disagrees with the schema files,
the schema files win.
Sources
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
startUrls | array | see note | — | Subreddit, post, user, search or community-list URLs |
searches | array<string> | see note | — | Plain keywords; each runs one search |
searchType | string (enum) | ⬜ No | posts | posts · comments · communities · users |
At least one of
startUrlsorsearchesmust be non-empty. Nothing isrequiredin the schema, so this is enforced at runtime.
Accepted startUrls shapes:
| Shape | Example | What you get |
|---|---|---|
| Subreddit | https://www.reddit.com/r/technology/ | community row + its posts (+ comments) |
| Subreddit with sort | https://www.reddit.com/r/technology/top/?t=week | The sort and time window in the URL win over sort/time |
| In-subreddit search | https://www.reddit.com/r/technology/search?q=ai | Matching posts from that subreddit |
| Post permalink | https://www.reddit.com/r/x/comments/abc123/slug/ | The post + its comment tree |
| User profile | https://www.reddit.com/user/spez/ | user row + their public history |
| User sub-page | .../user/spez/submitted · /comments · /overview | Just that slice of their history |
| Global search page | https://www.reddit.com/search?q=bitcoin | Matching posts |
| Community list | https://www.reddit.com/subreddits/search?q=ai | Matching community rows |
An unrecognised URL is skipped with Could not classify URL, skipping: <url> in the log.
Sorting
| Parameter | Type | Default | Valid where |
|---|---|---|---|
sort | string (enum) | new | relevance · hot · top · new · rising · comments. Listings accept hot/new/top/rising (others → hot); searches accept relevance/hot/top/new/comments (rising → relevance) |
time | string (enum) | all | hour · day · week · month · year · all. Applies to top/controversial listings and to searches |
commentSort | string (enum) | top | confidence (Best) · top · new · controversial · old · qa |
Limits
| Parameter | Type | Default | Scope |
|---|---|---|---|
maxItems | integer | 100 | Whole run, all types, comments included. 0 = unlimited |
maxPostCount | integer | 50 | Per subreddit / search / user. 0 = unlimited |
maxComments | integer | 20 | Per post. 0 = skip comments entirely |
maxCommentDepth | integer | 3 | 1 = top-level only, 0 = unlimited (depth is 0-based) |
maxCommunitiesCount | integer | 10 | Per source. 0 = unlimited |
maxUserCount | integer | 10 | Per source. 0 = unlimited |
Content toggles and filters
| Parameter | Type | Default | Effect |
|---|---|---|---|
skipComments | boolean | false | No comment rows — faster, cheaper, far less personal data |
skipUserPosts | boolean | false | For user URLs, return only the profile row |
skipCommunityInfo | boolean | false | For subreddit URLs, skip the community about-row |
includeNSFW | boolean | true | false drops items flagged over18 |
keywords | array<string> | [] | Keep only items whose title/body/description contains one of these (case-insensitive) |
postDateFrom | string | "" | YYYY-MM-DD, DD/MM/YYYY, an ISO datetime, or relative — 7 days, 3 months, 1 year |
postDateTo | string | "" | Same formats; a bare date includes the whole day |
extractMedia | boolean | true | Populate the media object on posts |
dedupItems | boolean | true | Drop repeats of the same type + id within the run |
Filters are applied after an item is fetched — they shrink the dataset, not the request count.
Runtime
| Parameter | Type | Default | Notes |
|---|---|---|---|
proxyConfiguration | object | {"useApifyProxy": true} | Leave enabled. Reddit rate-limits by IP; residential is best for larger runs |
maxConcurrency | integer | 10 | 1–50. Browser-driven, so it ramps from a low start. Lower it if rate-limited |
maxRequestRetries | integer | 5 | Retries a blocked or rate-limited request on a fresh session |
debugMode | boolean | false | Verbose logging |
Delivery (optional)
| Parameter | Type | Default | Description |
|---|---|---|---|
mcpConnector | string | "" | A connector you've authorised on your Apify account — Notion, Slack, Linear, Jira, GitHub, Airtable, or any MCP-compatible connector. Leave empty for dataset-only runs. |
deliveryMode | string (enum) | perItem | perItem (one call per item, capped at 1,000) · summary (one digest call) · none. |
mcpTool | string | "" | Tool to call — create_page, send_message, create_issue. Required when a connector is set; run once and the log lists the connector's tools. |
mcpArguments | object | {} | Arguments for the tool. String leaves support {placeholder} tokens. |
mcpMessageTemplate | string | "" | Template rendered and exposed to mcpArguments as {message}. |
Placeholders by mode:
| Mode | Placeholders |
|---|---|
perItem | every field of the item — {type} {id} {author} {subreddit} {score} {url} {createdAt} … — plus the cross-type aliases {title}, {text}, {permalink} and {message}. Nested values (like media) arrive JSON-encoded; nulls render empty. |
summary | {itemCount} {postCount} {commentCount} {userCount} {communityCount} {items} (a formatted digest, truncated at ~60,000 characters) {message} |
The aliases exist because the four types don't share field names:
{title}resolves to a post's title, a community's name or a username;{text}to a comment body, a post body or a description. Use them for templates that must work across every type.
📤 Output Contract & Data Structure
One dataset, four record types. Branch on type first. Fields that don't apply to a type are
absent keys, not nulls.
Shared by every type
| Field | Type | Meaning |
|---|---|---|
type | string | post · comment · user · community — read this first |
id | string | Reddit's base-36 ID for the entity. The join/dedup key |
parsedId | string | Reddit's fullname (t3_…, t1_…, t2_…, t5_…) |
url | string | Canonical reddit.com URL for the entity |
createdAt | string | ISO-8601 UTC |
createdTimestamp | integer | Epoch seconds — use this for arithmetic |
type: "post"
| Field | Type | Meaning |
|---|---|---|
subreddit / subredditId | string | Community the post is in |
title | string | Post title |
body | string | Self-text; "" for link posts |
author / authorId | string | Personal data. [deleted] when the account is gone |
link / domain | string | Where a link post points, and its domain |
flair | string | Link flair text |
score / upVotes | integer | Fuzzed by Reddit — approximate |
upvoteRatio | number | 0–1 |
numberOfComments | integer | Reddit's count — not the number of comment rows scraped |
numberOfCrossposts / awardsCount | integer | Crossposts and awards |
isSelf / isVideo / isOriginalContent | boolean | Post format flags |
over18 / spoiler / locked / stickied | boolean | Content and state flags |
distinguished | string | moderator / admin when applicable |
media | object | {thumbnail, images[], video, gallery[], externalLink} — only when extractMedia |
type: "comment"
| Field | Type | Meaning |
|---|---|---|
subreddit | string | Community the comment is in |
body | string | Comment text. Personal data. [deleted]/[removed] as Reddit serves it |
author / authorId | string | Personal data |
parentId | string | t1_… (a comment) or t3_… (the post) — rebuilds the tree |
postId | string | t3_… fullname of the post — join key |
depth | integer | 0-based nesting level |
score / upVotes | integer | Approximate |
isSubmitter | boolean | true when the commenter is the post's author |
stickied / controversiality / distinguished | mixed | Moderation and state signals |
numberOfReplies | integer | Direct replies present in the payload |
postTitle / postUrl | string | Only on comments scraped through their post — absent for comments from a user's history |
type: "user"
| Field | Type | Meaning |
|---|---|---|
username | string | Personal data — the pseudonymous identity |
linkKarma / commentKarma / totalKarma | integer | Karma breakdown |
awardeeKarma / awarderKarma | integer | Award karma |
isGold / isMod / isEmployee / verified / hasVerifiedEmail | boolean | Account flags |
iconImage | string | Avatar URL |
description | string | Profile blurb |
over18 | boolean | Profile marked 18+ |
type: "community"
| Field | Type | Meaning |
|---|---|---|
name | string | Subreddit name without r/ |
title | string | Display title |
description / longDescription | string | Short blurb and full sidebar text |
numberOfMembers | integer | Subscribers |
activeUsers | integer | Users online at capture time |
over18 | boolean | 18+ community |
type_ | string | public / restricted / private … (note the trailing underscore) |
lang / category | string | Declared language and advertiser category |
iconImage / bannerImage | string | Community imagery |
Example output — one of each type
[{"type": "post","id": "1abcxyz","parsedId": "t3_1abcxyz","subreddit": "technology","title": "Our team open-sourced the ingestion pipeline behind the launch","body": "Writing up what broke and what we'd do differently…","author": "some_builder","authorId": "t2_9q1w2","url": "https://www.reddit.com/r/technology/comments/1abcxyz/our_team_open_sourced/","link": "https://github.com/example/pipeline","domain": "github.com","flair": "Software","createdAt": "2026-08-19T14:03:11+00:00","createdTimestamp": 1787148191,"score": 3184,"upVotes": 3184,"upvoteRatio": 0.94,"numberOfComments": 412,"isSelf": false,"over18": false,"locked": false,"media": {"thumbnail": "https://b.thumbs.redditmedia.com/…jpg","images": ["https://preview.redd.it/…png"],"video": null,"gallery": [],"externalLink": "https://github.com/example/pipeline"}},{"type": "comment","id": "kx91mno","parsedId": "t1_kx91mno","subreddit": "technology","body": "The backpressure section is the part everyone skips and then regrets.","author": "queue_nerd","authorId": "t2_4d5f6","url": "https://www.reddit.com/r/technology/comments/1abcxyz/…/kx91mno/","parentId": "t3_1abcxyz","postId": "t3_1abcxyz","createdAt": "2026-08-19T15:22:40+00:00","createdTimestamp": 1787152960,"score": 214,"depth": 0,"isSubmitter": false,"numberOfReplies": 3,"postTitle": "Our team open-sourced the ingestion pipeline behind the launch","postUrl": "https://www.reddit.com/r/technology/comments/1abcxyz/our_team_open_sourced/"},{"type": "user","id": "9q1w2","parsedId": "t2_9q1w2","username": "some_builder","url": "https://www.reddit.com/user/some_builder","createdAt": "2017-03-04T09:11:02+00:00","createdTimestamp": 1488618662,"linkKarma": 12045,"commentKarma": 38210,"totalKarma": 50255,"isMod": false,"verified": true,"description": "Backend engineer. Opinions my own."},{"type": "community","id": "2qh16","parsedId": "t5_2qh16","name": "technology","title": "Technology","url": "https://www.reddit.com/r/technology/","description": "Subreddit dedicated to the news and discussions about technology.","createdAt": "2008-01-25T05:15:47+00:00","createdTimestamp": 1201238147,"numberOfMembers": 18400000,"activeUsers": 4123,"over18": false,"type_": "public","lang": "en"}]
Values illustrate shape and formatting — the posts, people and numbers are invented.
What a successful run looks like
- Status:
SUCCEEDED - Dataset: interleaved
post,comment,userandcommunityrows, in crawl order - Log:
Starting with N seed request(s). Item cap: …, then a line per listing, post and profile, thenCrawl finished. - Zero results is also a success. No sources supplied, an unclassifiable URL, a private or
banned subreddit, or a bot-block all return an empty dataset with
SUCCEEDED. Check the log. - Hitting
maxItemsends the run early and cleanly — that's the cap doing its job, not a failure.
Dataset views
| View | Shows |
|---|---|
| Overview | Type, title/body, author, subreddit, score, comments, date — everything in one table |
| Posts | Title, author, subreddit, score, upvote ratio, comments, flair, links |
| Comments | Body, author, subreddit, score, depth, submitter flag, replies, parent post |
| Communities | Name, members, active users, description, 18+ flag |
| Users | Username, karma breakdown, verified and mod flags |
| Post Media | Title, subreddit, self/video flags, the media object, link and domain |
▶️ Invocation & Integration
Authenticate every call with Authorization: Bearer <APIFY_TOKEN>.
Apify Console
Open the Actor page → Input tab → paste
subreddit/post/user URLs and/or type search terms → set maxItems and the per-source caps →
decide whether you want comments → leave the proxy on → Start.
Model Context Protocol (MCP)
claude mcp add --transport http apify \"https://mcp.apify.com?tools=techforce.global/reddit-scraper"
Then ask your agent for "what r/technology is saying about this launch".
Python (apify-client)
import osfrom collections import Counterfrom apify_client import ApifyClientclient = ApifyClient(os.getenv("APIFY_TOKEN"))run = client.actor("techforce.global/reddit-scraper").call(run_input={"startUrls": [{"url": "https://www.reddit.com/r/technology/top/?t=week"}],"maxItems": 500,"maxPostCount": 50,"maxComments": 20,"maxCommentDepth": 3,"deliveryMode": "none","proxyConfiguration": {"useApifyProxy": True},})rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())if not rows:raise SystemExit("Nothing scraped — check the run log (no sources? blocked?).")print(Counter(r["type"] for r in rows)) # e.g. Counter({'comment': 420, 'post': 50, 'community': 1})# ALWAYS branch on type — one dataset, four shapes.posts = [r for r in rows if r["type"] == "post"]comments = [r for r in rows if r["type"] == "comment"]# Rebuild each thread: comments join to posts on postId -> parsedId.by_post = {p["parsedId"]: {"post": p, "comments": []} for p in posts}for c in comments:thread = by_post.get(c.get("postId"))if thread:thread["comments"].append(c)for thread in sorted(by_post.values(), key=lambda t: t["post"].get("score") or 0, reverse=True)[:5]:p = thread["post"]# numberOfComments is Reddit's figure; len(...) is what we actually scraped.print(f'{p["score"]:>6} {len(thread["comments"])}/{p.get("numberOfComments")} {p["title"][:60]}')
JavaScript / TypeScript (apify-client)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('techforce.global/reddit-scraper').call({searches: ['vector database'],searchType: 'posts',sort: 'top',time: 'month',maxItems: 300,skipComments: false,maxComments: 25,keywords: ['pgvector', 'qdrant', 'weaviate'], // post-fetch filterdeliveryMode: 'none',proxyConfiguration: { useApifyProxy: true },});const { items } = await client.dataset(run.defaultDatasetId).listItems();// Four record types — split before you touch any field.const byType = items.reduce<Record<string, any[]>>((acc, i) => {(acc[i.type] ??= []).push(i);return acc;}, {});console.log(Object.entries(byType).map(([t, v]) => `${t}: ${v.length}`).join(', '));// Top-level comments only (depth is 0-based).for (const c of (byType.comment ?? []).filter((c) => c.depth === 0).slice(0, 10)) {console.log(`[r/${c.subreddit}] u/${c.author} (${c.score}): ${c.body.slice(0, 100)}`);}
cURL — synchronous (waits, returns the dataset)
curl -X POST \-H 'Content-Type: application/json' \-H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \-d '{"startUrls": [{ "url": "https://www.reddit.com/r/AskReddit/" }],"maxItems": 50,"skipComments": true,"deliveryMode": "none","proxyConfiguration": { "useApifyProxy": true }}' \'https://api.apify.com/v2/acts/techforce.global~reddit-scraper/run-sync-get-dataset-items'
cURL — asynchronous with a spend cap (recommended for large runs)
# 1. Start with a hard spend limitcurl -X POST -H 'Content-Type: application/json' \-H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \-d '{ "searches": ["bitcoin"], "maxItems": 2000, "maxComments": 50,"proxyConfiguration": { "useApifyProxy": true } }' \'https://api.apify.com/v2/acts/techforce.global~reddit-scraper/runs?maxTotalChargeUsd=10'# 2. Poll, then 3. fetch the dataset when SUCCEEDEDcurl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' 'https://api.apify.com/v2/actor-runs/<runId>'curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \'https://api.apify.com/v2/datasets/<DATASET_ID>/items?clean=true&format=json'
Posts only, no comments — the cheap, low-risk configuration
{"startUrls": [{ "url": "https://www.reddit.com/r/technology/" }],"maxItems": 200,"maxPostCount": 200,"skipComments": true,"extractMedia": false,"deliveryMode": "none","proxyConfiguration": { "useApifyProxy": true }}
Post-level metrics with no comment rows: far fewer items per post, a much smaller dataset, and no user speech in your output. Start here.
One thread, deep — for sentiment analysis
{"startUrls": [{ "url": "https://www.reddit.com/r/technology/comments/1abcxyz/some_thread/" }],"maxItems": 1000,"maxComments": 500,"maxCommentDepth": 0,"commentSort": "top","deliveryMode": "none","proxyConfiguration": { "useApifyProxy": true }}
maxCommentDepth: 0 follows every reply level the payload contains. Note that "more comments"
placeholders are not expanded, so very large threads still come back partial.
Topic monitoring on a schedule
{"searches": ["our product name"],"searchType": "posts","sort": "new","postDateFrom": "7 days","maxItems": 300,"maxComments": 20,"includeNSFW": false,"deliveryMode": "none","proxyConfiguration": { "useApifyProxy": true }}
Put a fixed input on a schedule, keep postDateFrom
relative so the window moves with each run, and dedupe against your store on id.
With MCP delivery — a digest into Slack
{"searches": ["our product name"],"maxItems": 200,"proxyConfiguration": { "useApifyProxy": true },"mcpConnector": "<your-authorized-slack-connector>","mcpTool": "send_message","deliveryMode": "summary","mcpMessageTemplate": "Reddit: {postCount} posts, {commentCount} comments\n\n{items}","mcpArguments": { "channel": "#social-listening", "text": "{message}" }}
Use summary deliberately — perItem is the default and would post one Slack message per
scraped row.
⚠️ Autonomous Agent Error Handling & Resolution Matrix
Read this first: almost nothing fails the run. Missing sources, an unclassifiable URL, a
blocked request and an empty subreddit all end SUCCEEDED while the log carries the reason. An
agent that checks run status alone will read an empty dataset as success-with-data — check the
row count and the log.
The CONDITION labels are documentation handles for observable states; this Actor writes no error
code into the dataset.
| Condition | What you observe | Cause | Deterministic agent action |
|---|---|---|---|
NO_SOURCES_PROVIDED | SUCCEEDED, empty dataset; log: No valid start URLs or search terms provided. Exiting. | Both startUrls and searches empty — not caught by validation | Modify Input: supply at least one source. |
URL_NOT_CLASSIFIED | That URL contributes nothing; log: Could not classify URL, skipping: <url> | Not a recognised Reddit URL shape | Modify Input: use one of the shapes in the Input Contract. |
BOT_WALL_OR_BLOCKED | Empty/thin dataset; log: Could not obtain JSON from … Reddit may be blocking this request | Proxy disabled, or the IP is blocked | Modify Input: enable Apify Proxy, prefer residential, lower maxConcurrency. |
RATE_LIMITED_BY_REDDIT | Log: Rate limited by Reddit (HTTP 429) - retrying with a new session/proxy. | Too many requests from one IP | Expected, self-healing. If it repeats, lower maxConcurrency to 2–3. |
PRIVATE_OR_BANNED_SUBREDDIT | No rows for that subreddit | Private, banned or quarantined community | Do not retry. Public content only. |
MAX_ITEMS_TRUNCATED_RUN | Fewer posts than expected; comments dominate | maxItems is global and comments count toward it | Modify Input: raise maxItems, or set skipComments: true. |
TYPE_MISMATCH_IN_CONSUMER | Consumer crashes reading title / body | One dataset, four shapes | Fix consumer: branch on type, use .get(). |
COMMENT_SEARCH_MISMATCH | searchType: "comments" returned comments that don't mention the term | It searches posts, then scrapes their comments | Modify Input: add the term to keywords as well. |
SORT_IGNORED | Results aren't in the order you asked for | relevance/comments are search-only (→ hot); rising is listing-only (→ relevance) | Fix expectation or pick a sort valid for that source. |
TIME_IGNORED | time had no effect | It only applies to top/controversial listings and searches | Modify Input: set sort: "top". |
DEPTH_OFF_BY_ONE | Fewer reply levels than expected | depth is 0-based; maxCommentDepth: 3 yields depths 0–2 | Fix expectation, or raise the value / use 0. |
PARTIAL_DEEP_THREAD | Big thread came back incomplete | "More comments" placeholders are not expanded | Expected. Raise maxComments; full expansion isn't supported. |
FILTERS_DIDNT_CUT_COST | Small dataset, unchanged run time | Filters are applied after fetching | Fix expectation: use the caps to control cost, not filters. |
NO_MEDIA_OBJECT | media missing on posts | extractMedia: false | Modify Input: enable it. |
MISSING_POST_TITLE_ON_COMMENT | postTitle/postUrl absent | That comment came from a user's history, not from its post | Fix consumer: join on postId → the post's parsedId. |
SCORES_DONT_MATCH_REDDIT | Scores differ slightly from the site | Reddit fuzzes vote counts | Expected. Treat scores as approximate. |
DUPLICATES_EXPECTED_BUT_ABSENT | Fewer rows than sources suggest | dedupItems is on by default | Expected. Set it false to keep repeats. |
FREE_PLAN_CAPPED | 25 items; log: Free plan detected - capping this run to 25 items. | Free Apify plan — the cap is total | Upgrade the plan. |
CONNECTOR_CALL_STORM | Hundreds of connector calls | deliveryMode defaults to perItem (capped at 1,000, with a warning) | Modify Input: switch to summary or none. |
CONNECTOR_TOOL_NOT_FOUND | Log: Tool 'x' is not available on this connector. Available tools: … | Wrong mcpTool name (empty is logged separately) | Modify Input: copy an exact name from that log line. Dataset unaffected. |
CONNECTOR_UNREACHABLE | Log: Could not reach the connector: … or APIFY_MCP_PROXY_URL is not set | Connector not authorised, or running locally | Authorise it, or apify push and run on the platform. Data already saved. |
AUTH_INVALID | HTTP 401 | Missing or malformed APIFY_TOKEN | Abort & prompt user for a valid token. |
RATE_LIMITED_APIFY | HTTP 429 | Apify API rate / concurrency limits — not Reddit | Retry: exponential backoff (2s, 4s, 8s). |
SYNC_TIMEOUT | HTTP 408 on the sync endpoint | Run exceeded the synchronous ceiling | Reconfigure: async POST /runs + polling. |
Provenance: every log line, status and dataset condition above is taken from this Actor's own source and verified against it. The three HTTP rows describe the Apify API transport layer, not this Actor — confirm them against the Apify API reference before wiring them into automated retry logic.
🗣️ Natural Language → Actor Mapping
| A user says… | Intent | Constructed Actor input |
|---|---|---|
| "What's on r/technology right now?" | Subreddit listing | {"startUrls":[{"url":"https://www.reddit.com/r/technology/"}],"sort":"hot"} |
| "Top posts in r/x this week" | Ranked listing | {"startUrls":[{"url":"https://www.reddit.com/r/x/top/?t=week"}]} |
| "Get every comment on this thread" | Thread extraction | {"startUrls":[{"url":"<permalink>"}],"maxComments":500,"maxCommentDepth":0,"maxItems":1000} |
| "What is Reddit saying about | Topic search | {"searches":["<topic>"],"searchType":"posts","sort":"new"} |
| "Which subreddits discuss | Community discovery | {"searches":["<topic>"],"searchType":"communities","maxCommunitiesCount":25} |
| "Find Reddit users called | User search | {"searches":["<name>"],"searchType":"users","maxUserCount":25} |
| "What has u/ | User history | {"startUrls":[{"url":"https://www.reddit.com/user/<name>/"}]} |
| "Just their profile stats" | Profile only | {"startUrls":[{"url":"https://www.reddit.com/user/<name>/"}],"skipUserPosts":true} |
| "Mentions of us in the last week" | Monitoring | {"searches":["<brand>"],"postDateFrom":"7 days","sort":"new"} |
| "Posts only, no comments" | Cheap/low-risk pull | {"startUrls":[{"url":"<subreddit>"}],"skipComments":true} |
| "No adult content please" | Safe corpus | Add {"includeNSFW":false} |
| "Only posts mentioning X or Y" | Keyword filter | Add {"keywords":["X","Y"]} |
| "How big is r/x?" | Community stats | {"startUrls":[{"url":"https://www.reddit.com/r/x/"}],"maxPostCount":1} — read the community row |
| "Which of these are Shorts…" (wrong platform) | — | ➡️ Not Reddit; see the YouTube Actors |
| "Send new mentions to Slack" | Scrape + deliver | Above, plus mcpConnector, mcpTool, deliveryMode: "summary" |
| "It returned nothing" | Empty result | Check a source was supplied, the URL shape is recognised, and the proxy is on |
| "Post this comment for me" | Write action | ❌ Read-only — out of scope |
Should NOT route here: private, quarantined or login-gated content · deleted or removed content · direct messages or modmail · posting, voting, or moderating · downloading media files (URLs only) · native comment search (comment search is approximated via posts) · non-Reddit platforms — for YouTube use the YouTube Scraper, for public Facebook page comments the Facebook Public Comment Tracker.
🧭 Agent Execution & Routing Logic
Does the user want PUBLIC REDDIT content as data?├── NO → wrong Actor.│ YouTube videos/comments → YouTube Scraper│ Facebook page comments → Facebook Public Comment Tracker│ a whole website's text → Advanced Website Crawling Actor│ private/DM/mod content → not obtainable│└── YES│├── What do they actually have? (mix freely; at least ONE is mandatory at│ runtime even though the schema marks nothing required)│ ├── a community → startUrls: r/<sub> (+ a community row)│ ├── a specific thread→ startUrls: permalink (most predictable cost)│ ├── a person → startUrls: /user/<name> (+ their history)│ ├── a topic → searches + searchType "posts"│ ├── "which subs?" → searches + searchType "communities"│ └── "which users?" → searches + searchType "users"│├── ALWAYS keep proxyConfiguration enabled (useApifyProxy: true)│├── Do they need COMMENTS?│ ├── no → skipComments: true → fewer items, lower cost, NO user speech│ └── yes → maxComments PER POST, maxCommentDepth (0-based; 0 = unlimited)│ └── remember: comments count toward the GLOBAL maxItems│├── Size the run│ ├── maxItems → GLOBAL cap, comments included (0 = unlimited)│ ├── maxPostCount → per subreddit / search / user│ └── big run? → set maxTotalChargeUsd as well│├── Narrow the output (post-fetch — these do NOT cut cost)│ ├── keywords → term must appear in title/body/description│ ├── postDateFrom → "7 days" / "2026-01-01"│ └── includeNSFW → false to drop 18+ items│├── What is the actual question?│ ├── what's popular → sort top/hot, read score + upvoteRatio│ ├── what's new → sort new + postDateFrom│ ├── how do they feel→ comment bodies, join on postId│ ├── where to engage → searchType "communities", read numberOfMembers/activeUsers│ └── who is talking → user rows + karma│└── Should results land in another app?├── yes → mcpConnector + mcpTool + {message} in mcpArguments│ digest → summary · record each → perItem (DEFAULT; capped at 1000)└── no → deliveryMode "none", or leave mcpConnector empty[Execute Actor]├──► SUCCEEDED with rows → split by `type`, then consume├──► SUCCEEDED, 0 rows → READ THE LOG: no source / unclassified URL / blocked / private└──► exactly 25 rows → free plan cap, not a data pull
💰 Cost Control & Pricing Transparency
Billing model: usage-based. This Actor bills for platform usage — the compute time and resources a run consumes — not per row. Current rates are on the Actor's Apify Store page; the Console shows the live total mid-run.
It is browser-driven, which is what gets past Reddit's bot wall — and also what makes each request heavier than a plain HTML fetch. Size runs accordingly.
| Choice | Effect on cost |
|---|---|
maxItems | The main lever — a hard stop on the whole run, comments included. |
skipComments / maxComments | The second lever. One extra request per post, and comment rows dominate item counts. |
| Number of sources | Each subreddit, search and user is its own paginated crawl. |
maxPostCount | Pagination depth per source — more pages, more requests. |
maxCommentDepth | Deeper trees mean bigger payloads, not more requests. |
maxConcurrency | Faster when Reddit tolerates it; rate-limit retries make it slower. |
Filters (keywords, dates, NSFW) | No meaningful cost saving — applied after fetching. |
| Being blocked | Costs run time and returns nothing. A disabled proxy is the most expensive setting on this page. |
Worked examples — relative run shape
| Scenario | Requests (approx.) | Items | Notes |
|---|---|---|---|
Probe — 1 subreddit, maxItems: 25, comments off | 1 listing + 1 about | ≤ 26 | Baseline. Confirms proxy and URL shape. |
| Subreddit sweep — 200 posts, comments off | ~3 listing pages | ~201 | Cheapest per item. |
Same 200 posts with maxComments: 20 | ~3 listings + 200 post fetches | up to ~4,200 | ~200× the requests — this is the expensive switch. |
One thread, maxComments: 500, depth 0 | 1 fetch | ≤ 501 | Best value per comment; a single payload. |
| Topic search, 50 posts, comments off | ~1 search page | ~50 | Predictable. |
| Community discovery, 25 subreddits | ~1 search page | ~25 | Very cheap. |
| Brand monitor — search + 20 comments/post, 300 items | search pages + ~15 post fetches | 300 (capped) | maxItems stops it precisely. |
Unbounded — maxItems: 0 on a busy subreddit | unbounded | unbounded | Never without maxTotalChargeUsd. |
Keeping runs predictable:
- First run: one source,
maxItems: 25,skipComments: true. - Decide the comment question up front — comments multiply both requests and rows, and they are the part that carries personal data.
- Prefer permalinks for comment-heavy work — one post URL is one predictable fetch; a subreddit sweep with comments is one fetch per post.
- Use the caps, not the filters, to control cost.
- Always set
maxTotalChargeUsdwhenmaxItemsis0or in the thousands. - Free Apify plans get 25 items per run — a connectivity check, not a data pull.
🛠️ Troubleshooting
Most problems are missing sources, the proxy, the global maxItems cap, or the four-record-type
shape — not scraper bugs.
| # | Symptom you see | Most likely cause | Fix |
|---|---|---|---|
| 1 | SUCCEEDED but the dataset is empty | Neither startUrls nor searches supplied — the schema doesn't enforce it | Read the log for No valid start URLs or search terms provided. and supply a source. |
| 2 | A URL you pasted produced nothing | It wasn't recognised — log: Could not classify URL, skipping: … | Use a documented URL shape (/r/…, /comments/…, /user/…, /search?q=…, /subreddits/search?q=…). |
| 3 | Thin results, or Could not obtain JSON from … in the log | Reddit's bot wall or an IP block | Keep Apify Proxy on, prefer residential, and lower maxConcurrency to 2–3. |
| 4 | Log repeats Rate limited by Reddit (HTTP 429) | Too many requests from one IP | Self-healing via retries; if persistent, lower maxConcurrency and raise maxRequestRetries. |
| 5 | Far fewer posts than expected, lots of comments | maxItems is global and comments count toward it | Raise maxItems, or set skipComments: true for post breadth. |
| 6 | Consumer crashes on a missing field | Four record types share one dataset | Branch on type; use .get() — absent ≠ null. |
| 7 | searchType: "comments" returned off-topic comments | It searches posts, then scrapes their comments | Add the term to keywords too, or accept post-level matching. |
| 8 | Sort seems ignored | relevance/comments are search-only (→ hot); rising is listing-only (→ relevance) | Pick a sort valid for that source type. |
| 9 | time had no effect | It only applies to top/controversial listings and searches | Set sort: "top" alongside it. |
| 10 | Fewer reply levels than expected | depth is 0-based — maxCommentDepth: 3 gives depths 0–2 | Raise it, or use 0 for unlimited. |
| 11 | A big thread came back incomplete | "More comments" placeholders aren't expanded | Raise maxComments; full expansion isn't supported. |
| 12 | Filters didn't make the run cheaper | They're applied after fetching | Use maxItems / maxPostCount / skipComments for cost. |
| 13 | NSFW content in the dataset | includeNSFW defaults to true | Set it false. |
| 14 | No media on posts | extractMedia: false, or the post has none | Enable extractMedia; text posts legitimately have empty media. |
| 15 | postTitle / postUrl missing on comments | Those comments came from a user's history | Join on postId → the post's parsedId. |
| 16 | Scores differ from what the site shows | Reddit fuzzes vote counts | Expected — treat scores as approximate. |
| 17 | numberOfComments ≫ the comment rows you got | That's Reddit's count, not the scrape count | Compare deliberately; raise maxComments if you need more. |
| 18 | Duplicate posts across sources | dedupItems is on by default — if you see repeats, they differ by id | Dedupe downstream on id, or check dedupItems. |
| 19 | Exactly 25 rows came back | Free plan cap — total, not per source | Upgrade the plan; the log says Free plan detected…. |
| 20 | A private/banned subreddit returned nothing | Public content only | Nothing to fix — no scraper can reach it. |
| 21 | Hundreds of connector calls fired | deliveryMode defaults to perItem | Set summary or none. Delivery is capped at 1,000 calls with a warning. |
| 22 | Slack / Notion received nothing | Delivery needs both mcpConnector and mcpTool | Set both. Every dataset row is still saved. |
| 23 | Connector message fields are blank | The template used a field that record type lacks | Use the cross-type aliases {title} / {text} / {permalink}, or branch on {type}. |
| 24 | Log: APIFY_MCP_PROXY_URL is not set | Ran locally; connectors only resolve on the platform | apify push and run on the platform. |
| 25 | HTTP 401 / 403 from the Apify API | Missing, expired or malformed APIFY_TOKEN | Regenerate in Console → Settings → API & Integrations. |
| 26 | HTTP 429 / 408 from the Apify API | Apify rate limits / the sync endpoint's time ceiling | Back off (2s → 4s → 8s), or switch to async POST /runs + polling. |
Diagnostic checklist before opening an issue
- Confirm a source is set —
startUrlsorsearches. - Open the URL signed-out in a browser. Private, banned and quarantined communities look identical to a scraper.
- Confirm Apify Proxy is enabled, and try residential if you see bot-wall lines.
- Baseline run: one subreddit,
maxItems: 25,skipComments: true,deliveryMode: "none". - Count rows by
typebefore concluding anything is missing. - Turn on
debugModefor a verbose log if the above doesn't explain it.
If the issue survives all six, open an Issues ticket (or email support) with the run ID, the input you used, and what you expected.
❓ FAQ
Do I need a Reddit account, app registration or API key?
No. The Actor reads publicly available Reddit content. You need an Apify account and API token; you need no Reddit credentials, client ID or OAuth token.
Is this the official Reddit API?
No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by Reddit, Inc.
Do I need to configure proxies?
Apify Proxy is on by default and you should leave it on — Reddit rate-limits by IP and blocks unfamiliar traffic. For larger runs, a residential group is the most reliable choice.
Can I plug it into Claude, Cursor, or a LangChain agent?
Yes — it is a native MCP tool: claude mcp add --transport http apify "https://mcp.apify.com?tools=techforce.global/reddit-scraper"
What can I put in startUrls?
Subreddit URLs (with or without a sort tab and ?t= window), post permalinks, user profiles and their /submitted, /comments or /overview sub-pages, global or in-subreddit search pages, and community-list URLs. Anything unrecognised is skipped with a log line.
Can I mix URLs and search terms in one run?
Yes. startUrls and searches are processed in the same run, and every item is written to the same dataset.
Why did I get so few posts and so many comments?
Because maxItems is a global cap on the whole run and comments count toward it. With the default of 100 and comments on, the first few threads can consume the entire budget. Raise maxItems, or set skipComments: true when you want post breadth.
How do I search comments specifically?
You can't, natively — and this is the one place the input name is optimistic. searchType: "comments" searches posts for your term and then scrapes those posts' comment threads, so the comments returned belong to matching posts rather than matching the term themselves. Add the term to the keywords filter if it must appear in the comment text.
Why does my sort seem to be ignored?
Because sorts are validated per source. Subreddit listings accept hot, new, top, rising (and controversial/best) — relevance and comments silently become hot. Searches accept relevance, hot, top, new, comments — rising becomes relevance. time only applies to top/controversial listings and to searches.
How does maxCommentDepth work?
Depth is 0-based: 1 returns top-level comments only, 3 returns depths 0, 1 and 2, and 0 is unlimited. Note that Reddit's "more comments" placeholders are not expanded, so very deep threads still come back partial.
Why do rows have different fields?
One dataset holds four record types — post, comment, user and community — discriminated by type. Fields that don't apply to a type are absent keys, not nulls, so branch on type and use optional access.
How do I rebuild a thread from the output?
Join comments to posts on postId → the post's parsedId (t3_…), then nest comments using parentId and order them by depth. postTitle/postUrl are also present on comments scraped through their post.
Do the scores match what I see on Reddit?
Approximately. Reddit deliberately fuzzes vote counts, so score and upVotes are close but not exact, and they are a snapshot as of the run.
Can I filter by keyword or date?
Yes — keywords, postDateFrom and postDateTo (absolute dates or relative strings like 7 days). They're applied after an item is fetched, so they shrink your dataset but don't reduce run cost.
Does it download images and videos?
No. The media object holds thumbnail, preview image, video, gallery and external-link URLs; the files themselves are never fetched.
Can it get deleted, private or quarantined content?
No. Deleted content comes back as Reddit serves it ([deleted] / [removed]), and private, banned or login-gated communities are inaccessible by design.
Which delivery mode should I use, and does it work locally?
summary for one digest of the whole run; perItem — the default — sends one connector call per scraped item and is capped at 1,000 with a warning; none for dataset-only runs. Delivery only works on the Apify platform; locally you'll see APIFY_MCP_PROXY_URL is not set and the dataset saves as normal.
Does the output contain personal data?
Yes. author, authorId, username and free-text body identify pseudonymous individuals, and user rows include karma and account age. skipComments: true removes the largest volume of it while keeping post-level metrics. Read Compliance & Data Privacy before storing or reusing it.
What does it cost, and what do I get on the free plan?
Usage-based: you pay for the platform usage a run consumes, not per row. Because it drives a real browser, cost per item is higher than an HTML-only scraper — maxItems and skipComments are your main levers. Free Apify plans return 25 items total per run. Pass maxTotalChargeUsd for a hard per-run ceiling.
🔍 Companion machine-readable files
| File | What it defines |
|---|---|
| .actor/input_schema.json | Every input, its type, default, bounds and enums |
| .actor/dataset_schema.json | All 59 possible fields across the four record types, and the six Console views |
| .actor/output_schema.json | Where the run's output is exposed |
| .actor/actor.json | Actor identity and storage wiring |
If this page and the schema files disagree, the schema files win. They are what the platform validates against at run time; this page is documentation and can lag a release.
🔗 Related Actors by Techforce Global
| Actor | Best for |
|---|---|
| YouTube Scraper: Videos, Channels & Comments | The same social-listening question on YouTube — video metadata, channel stats and comment threads |
| Facebook Public Comment Tracker | Comment threads under a public Facebook page's posts |
| Advanced Website Crawling Actor | Whole-site text, Markdown or HTML for RAG and search |
| Pinterest Scraper | Pins from searches, boards, profiles and pin URLs |
| YouTube Full Channel Transcripts Scraper | Transcripts of every video on a channel, for a text corpus |
Pipeline patterns: brand monitoring — this Actor on a schedule with postDateFrom: "7 days",
comments on, digest to Slack via MCP · cross-platform listening — Reddit + YouTube + Facebook
comment corpora merged on your own topic keys · community targeting —
searchType: "communities"Browse all Actors by Techforce Global at scraper.techforce.global.
🔐 Compliance & Data Privacy
Trademark & affiliation. This is an independent Actor. It is not affiliated with, endorsed by, or sponsored by Reddit, Inc. Reddit™ is a trademark of Reddit, Inc.; all trademarks are the property of their respective owners.
-
Public content only. Everything returned is visible to any signed-out visitor. No private, banned, quarantined or login-gated communities, no direct messages, no modmail, no credentials.
-
⚠️ Reddit content is personal data.
author,authorId,usernameand free-textbodyrelate to identifiable — if pseudonymous — individuals, and profile rows add karma, account age and verification flags. Under GDPR / UK GDPR and CCPA/CPRA, pseudonymous data is still personal data, and the obligations sit with you, not with the Actor. -
Reddit is unusually sensitive as a source. People post about health, addiction, sexuality, politics, immigration status and employment under a handle they believe is separable from their real name. Aggregating a user's history across subreddits — which a
/user/scrape does by design — is exactly the operation that can de-pseudonymise someone. Treat it as the high-risk processing it is, and don't do it casually. -
The cheapest compliance measure is a single field. If your question is about topics rather than people, set
skipComments: true. You keep post titles, scores, subreddit metrics and media, and you shed the bulk of the user speech. -
Minimise, then aggregate. Sentiment scores, theme counts and engagement ratios rarely require keeping raw
author/authorIdcolumns. Drop them at ingest, keep retention short, and restrict access. -
Special-category content can't be filtered in advance. You cannot know which threads contain it until after retrieval.
includeNSFW: falseremoves 18+ flagged items but is not a sensitivity filter. -
Deleted means deleted. If a user removes a post or comment after your run, your copy no longer reflects their choice. Re-scrape or expire your data rather than treating a snapshot as permanently valid.
-
Content is the authors' copyrighted expression. Internal research, analysis and reporting sit on far safer ground than republishing scraped posts and comments as your own content. Using Reddit content to train a model is a legally distinct act with its own contested position — take advice rather than relying on "it was public".
-
Reddit's Terms and API terms restrict automated access and commercial reuse. This Actor does not remove that risk — it is a risk you accept by running it. The official Data API is the sanctioned route.
-
Attribution over mirroring. Every row carries a
url. Link back to the thread rather than presenting Reddit discussion as your own content, and never quote a pseudonymous user in a way that ties them to an identity they didn't publish.
🆘 Support & Custom Pipeline Engineering
Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?
- Email: bhavin.shah@techforceglobal.com
- Custom Enterprise Integrations: Book a 15-Minute Technical Consultation
- Maintained by: Techforce Global — Specialists in High-Performance Web Scrapers and Agentic Workflows.
🏷️ Structured data for search & AI discovery
SoftwareApplication describes the Actor; FAQPage wraps the FAQ above so those answers are
eligible for rich results, matching it question-for-question.
{"@context": "https://schema.org","@type": "SoftwareApplication","name": "Reddit Scraper – Posts, Comments, Users & Communities","alternateName": "Reddit API Alternative","applicationCategory": "BusinessApplication","applicationSubCategory": "Web Scraping / Social Listening & Community Analysis","operatingSystem": "Cloud (Apify platform)","description": "Scrapes Reddit posts, comments, users and communities from subreddit URLs, post URLs, user profiles, search pages or plain keywords — without a Reddit account, app registration or OAuth. Returns four record types in a single dataset, each discriminated by a type field: posts with score, upvote ratio, flair and media URLs; comments with depth, parent and reply counts; users with karma breakdowns; and communities with member and active-user counts. Includes keyword, date and NSFW filtering, in-run deduplication, media URL extraction, proxy rotation, and optional delivery into Notion, Slack, Linear, Airtable or any MCP connector.","url": "https://apify.com/techforce.global/reddit-scraper","author": { "@type": "Organization", "name": "Techforce Global", "url": "https://www.techforceglobal.com" },"provider": { "@type": "Organization", "name": "Apify", "url": "https://apify.com" },"offers": {"@type": "Offer","priceCurrency": "USD","category": "Pay per platform usage","description": "Usage-based: billed for the compute time and resources a run consumes, not per row. Runs are browser-driven, so cost per item is higher than an HTML-only scraper. Current rates are shown on the Actor's Apify Store page.","url": "https://apify.com/techforce.global/reddit-scraper"},"featureList": ["No Reddit account, app registration or OAuth token required","Subreddit, post, user, search and community-list URLs plus plain keywords in one run","Four record types in one dataset, discriminated by a type field","Posts with score, upvote ratio, flair, crossposts and awards","Comment trees with depth, parentId, postId and reply counts","User profiles with full karma breakdown and account flags","Community records with member and active-user counts","Keyword, absolute-date and relative-date filtering, plus an NSFW switch","Media URL extraction: thumbnails, previews, video, galleries and external links","In-run deduplication on type and id","Global maxItems cap plus per-source and per-post limits","Proxy rotation with automatic retry on rate limits","Optional MCP delivery to Notion, Slack, Linear, Airtable and more","Six Console dataset views covering posts, comments, users, communities and media"],"isAccessibleForFree": false,"softwareRequirements": "Apify account and API token","keywords": "Reddit scraper, Reddit API alternative, scrape Reddit comments, subreddit data extraction, social listening, sentiment analysis data, Reddit user profile scraper, subreddit discovery, market research, MCP connector"}
{"@context": "https://schema.org","@type": "FAQPage","mainEntity": [{"@type": "Question","name": "Do I need a Reddit account, app registration or API key?","acceptedAnswer": { "@type": "Answer", "text": "No. The Actor reads publicly available Reddit content. You need an Apify account and API token; you need no Reddit credentials, client ID or OAuth token." }},{"@type": "Question","name": "Is this the official Reddit API?","acceptedAnswer": { "@type": "Answer", "text": "No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by Reddit, Inc." }},{"@type": "Question","name": "Do I need to configure proxies?","acceptedAnswer": { "@type": "Answer", "text": "Apify Proxy is on by default and you should leave it on — Reddit rate-limits by IP and blocks unfamiliar traffic. For larger runs, a residential group is the most reliable choice." }},{"@type": "Question","name": "Can I plug it into Claude, Cursor, or a LangChain agent?","acceptedAnswer": { "@type": "Answer", "text": "Yes — it is a native MCP tool: claude mcp add --transport http apify \"https://mcp.apify.com?tools=techforce.global/reddit-scraper\"" }},{"@type": "Question","name": "What can I put in startUrls?","acceptedAnswer": { "@type": "Answer", "text": "Subreddit URLs (with or without a sort tab and ?t= window), post permalinks, user profiles and their /submitted, /comments or /overview sub-pages, global or in-subreddit search pages, and community-list URLs. Anything unrecognised is skipped with a log line." }},{"@type": "Question","name": "Can I mix URLs and search terms in one run?","acceptedAnswer": { "@type": "Answer", "text": "Yes. startUrls and searches are processed in the same run, and every item is written to the same dataset." }},{"@type": "Question","name": "Why did I get so few posts and so many comments?","acceptedAnswer": { "@type": "Answer", "text": "Because maxItems is a global cap on the whole run and comments count toward it. With the default of 100 and comments on, the first few threads can consume the entire budget. Raise maxItems, or set skipComments: true when you want post breadth." }},{"@type": "Question","name": "How do I search comments specifically?","acceptedAnswer": { "@type": "Answer", "text": "You can't, natively. searchType \"comments\" searches posts for your term and then scrapes those posts' comment threads, so the comments returned belong to matching posts rather than matching the term themselves. Add the term to the keywords filter if it must appear in the comment text." }},{"@type": "Question","name": "Why does my sort seem to be ignored?","acceptedAnswer": { "@type": "Answer", "text": "Sorts are validated per source. Subreddit listings accept hot, new, top, rising (and controversial/best) — relevance and comments silently become hot. Searches accept relevance, hot, top, new, comments — rising becomes relevance. time only applies to top/controversial listings and to searches." }},{"@type": "Question","name": "How does maxCommentDepth work?","acceptedAnswer": { "@type": "Answer", "text": "Depth is 0-based: 1 returns top-level comments only, 3 returns depths 0, 1 and 2, and 0 is unlimited. Reddit's \"more comments\" placeholders are not expanded, so very deep threads still come back partial." }},{"@type": "Question","name": "Why do rows have different fields?","acceptedAnswer": { "@type": "Answer", "text": "One dataset holds four record types — post, comment, user and community — discriminated by type. Fields that don't apply to a type are absent keys, not nulls, so branch on type and use optional access." }},{"@type": "Question","name": "How do I rebuild a thread from the output?","acceptedAnswer": { "@type": "Answer", "text": "Join comments to posts on postId → the post's parsedId (t3_…), then nest comments using parentId and order them by depth. postTitle/postUrl are also present on comments scraped through their post." }},{"@type": "Question","name": "Do the scores match what I see on Reddit?","acceptedAnswer": { "@type": "Answer", "text": "Approximately. Reddit deliberately fuzzes vote counts, so score and upVotes are close but not exact, and they are a snapshot as of the run." }},{"@type": "Question","name": "Can I filter by keyword or date?","acceptedAnswer": { "@type": "Answer", "text": "Yes — keywords, postDateFrom and postDateTo (absolute dates or relative strings like 7 days). They're applied after an item is fetched, so they shrink your dataset but don't reduce run cost." }},{"@type": "Question","name": "Does it download images and videos?","acceptedAnswer": { "@type": "Answer", "text": "No. The media object holds thumbnail, preview image, video, gallery and external-link URLs; the files themselves are never fetched." }},{"@type": "Question","name": "Can it get deleted, private or quarantined content?","acceptedAnswer": { "@type": "Answer", "text": "No. Deleted content comes back as Reddit serves it ([deleted] / [removed]), and private, banned or login-gated communities are inaccessible by design." }},{"@type": "Question","name": "Which delivery mode should I use, and does it work locally?","acceptedAnswer": { "@type": "Answer", "text": "summary for one digest of the whole run; perItem — the default — sends one connector call per scraped item and is capped at 1,000 with a warning; none for dataset-only runs. Delivery only works on the Apify platform; locally you'll see APIFY_MCP_PROXY_URL is not set and the dataset saves as normal." }},{"@type": "Question","name": "Does the output contain personal data?","acceptedAnswer": { "@type": "Answer", "text": "Yes. author, authorId, username and free-text body identify pseudonymous individuals, and user rows include karma and account age. skipComments: true removes the largest volume of it while keeping post-level metrics. Read the Compliance & Data Privacy section before storing or reusing it." }},{"@type": "Question","name": "What does it cost, and what do I get on the free plan?","acceptedAnswer": { "@type": "Answer", "text": "Usage-based: you pay for the platform usage a run consumes, not per row. Because it drives a real browser, cost per item is higher than an HTML-only scraper — maxItems and skipComments are your main levers. Free Apify plans return 25 items total per run. Pass maxTotalChargeUsd for a hard per-run ceiling." }}]}