Reddit Scraper & API Alternative | $1.5 / 1K
Pricing
from $1.05 / 1,000 post scrapeds
Reddit Scraper & API Alternative | $1.5 / 1K
Reddit data without an API key: posts from subreddits and searches, plus comment threads when Reddit JSON is reachable (otherwise an RSS fallback without scores or comments). Incremental mode returns only new posts each run. No login. $1.50/1,000 rows.
Pricing
from $1.05 / 1,000 post scrapeds
Rating
5.0
(1)
Developer
Hriday Rungta
Maintained by CommunityActor stats
0
Bookmarked
5
Total users
2
Monthly active users
2 days ago
Last modified
Categories
Share
Reddit Scraper | Reddit API Alternative for Posts, Comments & Monitoring
Reddit data without a Reddit API key or OAuth app. Read subreddits and Reddit search results, and when Reddit's JSON is reachable also full comment threads, into a flat dataset you can export to CSV, JSON or Excel, or feed to Google Sheets, Make, n8n or Zapier. Put it on a schedule with Only new posts since my last run and every run returns just the posts published since the previous one: no duplicates, nothing billed twice.
$1.50 per 1,000 rows, less on paid Apify plans ($1.35 Starter, $1.20 Scale, $1.05 Business). No start fee. Posts and comments are the same price.
Read this first: current data coverage. Since 2026-10-15 Reddit blocks its JSON pages through the Actor's proxy. When that happens the Actor falls back to Reddit's RSS feed. Fallback rows (
dataSource: rss) have title, body, author (if enabled), subreddit, exact time, link and domain, but no score, upvote ratio, comment count or flair, and comment rows are skipped. Skipped comments are not charged. Every row says which source it came from indataSource. Details in the fallback section.
What can you use Reddit data for?
- Monitor brand mentions on Reddit. Track a brand, product or competitor name with Reddit search queries and get only newly matching posts on every scheduled run.
- Reddit social listening and market research. Read what a community talks about: pull a subreddit's recent or top posts for topic, pain-point and language research.
- Find Reddit leads and buyer-intent posts. Search phrases like "looking for a CRM" or "alternative to X" inside the subreddits where your buyers ask, newest first, and send new matches to your CRM or Slack.
- Reddit sentiment analysis datasets. Collect post text with timestamps for your own classifier or LLM pipeline. Post scores and comment threads are included when Reddit JSON is reachable; in RSS fallback mode they are not.
- Track Reddit trends and top posts. Read a subreddit sorted by top for a day, week, month or year to see what performs.
- Download Reddit comment threads. When Reddit JSON is reachable, every reply becomes a row with depth and parent id, so a thread rebuilds with one self-join. In RSS fallback mode comments are skipped.
- Build a Reddit dataset for AI and RAG. Markdown post bodies, ISO timestamps and permalinks in one flat table.
Input
| Input | Default | Notes |
|---|---|---|
subreddits | ["smallbusiness"] | any of name, r/name or a full community link |
searchQueries | [] | Reddit search syntax, including quoted phrases |
searchInSubreddits | false | on: every query runs inside every subreddit listed |
sort | new | new, hot, top, relevance (search only) |
timeRange | week | top listings and search only |
maxPostsPerSource | 25 | one Reddit request returns up to 100 |
minScore | 0 | dropped posts and comments are never billed; ancestors kept to close a thread are flagged and free |
includeComments | false | one extra request per post; posts with no comments are skipped |
maxCommentsPerPost | 50 | applied while the tree is walked, not after |
commentDepth | 10 | 0 is top-level only |
commentConcurrency | 5 | comment trees fetched at once; 150 posts ≈ 30–35 min. Output order is unchanged |
onlyNewSinceLastRun | false | the incremental mode described above |
firstRunSince | 30 days | or all for a deliberate backfill |
postsNewerThan / postsOlderThan | empty | inclusive start, exclusive end, so adjacent windows never overlap |
personalData | false | off: author usernames are redacted |
maxItems | 1000 | hard row cap for the whole run |
requestTimeoutSecs | 90 | minimum 45; the unblocker is slow by design |
failOnEmpty | true | an unexplained empty source fails the run |
The proxyConfiguration field is preset to Apify Proxy with the Unblocker group and needs no changes.
Input example
{"subreddits": ["smallbusiness", "SaaS"],"searchQueries": ["crm recommendation"],"sort": "new","maxPostsPerSource": 100,"onlyNewSinceLastRun": true,"firstRunSince": "7 days","maxItems": 500}
Output
Output fields
One row per post. When Reddit JSON is reachable (dataSource: json) every column below is filled; in RSS fallback rows (dataSource: rss) score, ups, upvoteRatio, numComments, flair and the NSFW/spoiler/locked flags are null (see the fallback section).
title, selftext | the post and its body, as Markdown |
score, ups, upvoteRatio, numComments | how it did |
createdUtc, createdAtIso | exact publication time, as an epoch and as an ISO-8601 UTC timestamp |
permalink, url, domain, isSelf | where it lives and what it points at |
subreddit, linkFlairText, over18, spoiler, locked, stickied | how it sits in its community |
sourceType, sourceValue, sort | which subreddit or query found it, in which order |
Switch on Also scrape comments and every reply becomes its own row, in the same columns, plus:
body | the comment text. [removed] and [deleted] are kept verbatim — a removed comment is information |
depth | 0 for a top-level comment, 1 for a reply to it, and so on |
parentId, parentType | the parent's id with no prefix, and whether the parent is the post or another comment |
postId, postTitle | the post the comment belongs to |
isSubmitter | true when the post's own author wrote it |
retainedForTree | true on the few rows kept only so the thread still joins up — see below |
parentId joins straight to commentId, so the whole thread rebuilds from the flat table with
one self-join — no JSON parsing, no nested objects, no ragged CSV columns. Posts and comments
share one column set. The contract is exact: every parentType: "comment" row's parentId
matches a commentId that appears earlier in the dataset, one depth level up.
Output example
[{"postId": "1abc2de","title": "What's the best way to validate a SaaS idea with $0?","selftext": "Before you spend a dollar on ads...","score": 412,"ups": 430,"upvoteRatio": 0.96,"numComments": 87,"createdUtc": 1758963200,"createdAtIso": "2026-09-27T09:33:20.000Z","permalink": "/r/smallbusiness/comments/1abc2de/whats_the_best_way/","url": "https://www.reddit.com/r/smallbusiness/comments/1abc2de/whats_the_best_way/","domain": "self.smallbusiness","isSelf": true,"subreddit": "smallbusiness","linkFlairText": "Question","over18": false,"spoiler": false,"locked": false,"stickied": false,"sourceType": "subreddit","sourceValue": "smallbusiness","sort": "new","scrapedAt": "2026-09-28T06:00:11.201Z"},{"commentId": "kx1y2z3","postId": "1abc2de","postTitle": "What's the best way to validate a SaaS idea with $0?","body": "Landing page + a waitlist, then talk to the first 20 people who sign up.","depth": 0,"parentId": "1abc2de","parentType": "post","isSubmitter": false,"retainedForTree": false,"sourceType": "subreddit","sourceValue": "smallbusiness","scrapedAt": "2026-09-28T06:00:14.884Z"}]
Posts and comments share one column set, so this is one dataset, not two — export it straight to CSV, Sheets, or a WHERE retainedForTree IS NOT TRUE query.
RSS fallback output example
{"type": "post","postId": "1abc2de","title": "What's the best way to validate a SaaS idea with $0?","selftext": "Before you spend a dollar on ads...","score": null,"upvoteRatio": null,"numComments": null,"linkFlairText": null,"createdAtIso": "2026-09-27T09:33:20.000Z","subreddit": "smallbusiness","permalink": "https://www.reddit.com/r/smallbusiness/comments/1abc2de/whats_the_best_way/","sourceType": "subreddit","sort": "new","dataSource": "rss"}
minScore cannot break the thread, and the rows that keep it whole are free
Reddit returns downvoted comments routinely, so a score floor — including the default
minScore: 0parentId in no row of your dataset, so a comment that fails
minScore but is an ancestor of one that passes is kept, and flagged retainedForTree: true —
it is in your dataset despite failing your filter, purely so the tree closes. Those rows are
never charged: you excluded them, so you do not pay for them, and WHERE retainedForTree IS NOT TRUEmaxItems). A below-threshold comment with nothing
surviving under it is dropped as you would expect, subtree and all.
Comments are fetched in parallel, and the order never changes
A comment tree is one unblocking-proxy request and the unblocker answers in 40–90 seconds, so
commentConcurrency (default 5) fetches several at once: 150 posts with comments takes
roughly 30–35 minutes at the default, against over two hours one at a time — and about 15–18
minutes at commentConcurrency: 10. Each post's tree is buffered and written in listing order, so
the dataset is identical at any setting: parents still precede their replies, and nothing is
billed or counted twice. A tree that fails to load is reported in the log; its post row is still
saved and the run carries on.
When Reddit blocks its JSON, the Actor falls back to RSS
Reddit decides, per proxy and per day, whether its JSON pages are reachable. If the first JSON request of a run fails (HTTP 502/403 or a timeout), the Actor reads the same subreddit or search through Reddit's RSS feed for the rest of that run, instead of failing. The log says so, and every row carries dataSource: json for a full row, rss for a fallback row.
What a fallback (rss) row has: id, title, body as Markdown, author (if enabled), subreddit, exact post time, permalink, outbound link and domain, thumbnail, and normal paging, search, sort and incremental mode. What it does not have: score, ups, upvoteRatio, numComments, flair and the NSFW/spoiler/locked flags are null, and comment rows are skipped (the feed has no depth or parent, so a tree can't be built). minScore cannot filter rows in fallback mode. Posts are charged as usual; skipped comments are not.
If you need scores or comments, check dataSource and re-run later.
Pricing
Pay per event, charged as each row is saved, with no start fee and no charge for platform usage:
| Event | Price | When |
|---|---|---|
post-scraped | $0.0015 | one post row |
comment-scraped | $0.0015 | one comment row — except a row flagged retainedForTree, which is free |
Paid Apify plans get a lower price on both events: $0.00135 on Starter (Bronze), $0.0012 on Scale (Silver) and $0.00105 on Business (Gold) and above.
Set maxItems as your cost cap, or a maximum total charge on the run. When a charge limit is
reached the run stops cleanly, keeps everything already saved, and holds the incremental marks
where they were so nothing is re-billed on the next run.
Author names are redacted unless you ask for them
Include author usernames is OFF by default, and every row's author, authorFullname and
authorFlairText come back as null while it is off. Everything that is not a person — the
title, the body, the score, the timestamp, the permalink, the subreddit — is kept, so the dataset
is fully useful for market research, monitoring and trend analysis without carrying anybody's
identity.
Turn it on only if you have a lawful basis for processing the identity of the people who wrote the posts. It is a single switch, and it is your decision, recorded in your run's input.
Incremental runs
| Input | What it does |
|---|---|
onlyNewSinceLastRun | off by default. On: return only posts published since this source's last run, then move the mark forward. |
firstRunSince | how far back a source the Actor has never seen reaches. Default 30 days. Set it to all for a full backfill. |
stateStoreName | the key-value store on your account that holds the marks. Default reddit-scraper-state, created for you. |
stateNamespace | optional extra prefix, for keeping several independent tracking sets inside one store. |
The first run, spelled out
A source the Actor has never seen has no mark, so it has to start somewhere. It starts from a date, not from the beginning of the listing. By default that date is 30 days ago, so the first run on a new subreddit costs you roughly a month of posts — not everything the listing will give. You decide how much history you pay for:
firstRunSince | First run on a new source returns |
|---|---|
30 days (default) | posts from the last 30 days, then sets the mark |
6 months, 1 year, 2026-01-01 | posts back to that point, then sets the mark |
all | everything the listing will give, then sets the mark — an explicit, deliberate backfill |
Every later run ignores firstRunSince entirely and uses the mark. The run log names which of the
two happened for each source, so there is never any doubt about what you were billed for.
What the marks are, and where they live
One record per source, holding the timestamp and id of the newest post you have received, the source's own identity, and a run counter. They are kept in a named key-value store, which Apify retains indefinitely on every plan, so the marks survive across runs, schedules, plan changes and Actor updates.
- They are yours. The store is created on the account that runs the Actor, alongside your datasets. You can open it, read it, edit a mark to re-pull a period, or delete it to start over.
- Keyed per source. A subreddit and a query are separate sources, and a query is keyed by its own sort and time range too — so "top of the week for X" and "new posts about X" never share a mark.
- Namespaced. Keys carry a namespace taken from your own Apify user id, and a record whose namespace does not match is ignored on read, so marks can never be crossed between accounts.
- Give different tracking sets different store names —
reddit-state-competitorsandreddit-state-leads— and the same subreddit is tracked independently in each. - Cheap. One read and one write per source per run.
Where it saves you money
Reddit's new listing is newest-first, so once the Actor reaches a post older than your mark it
knows everything beyond it is already in your dataset and stops reading that source
immediately. Both things a run pays for fall: the rows you are billed for, and the proxy
requests behind them.
Modelled on a community gaining 50 posts a day, run daily with maxPostsPerSource: 500:
| Run | Billed rows | Listing requests |
|---|---|---|
| Stateless, every run | 500 | 5 |
| Incremental, steady state | 50 | 1 |
That is 10x fewer billed rows and 5x fewer proxy requests per run. Across a 30-day daily
schedule, including the two seeding runs at the start, it is 2,100 rows against 15,000 — 7x
less, or $3.15 against $22.50 at the current $1.50/1,000 price. npm run measure reproduces those row counts locally from the
Actor's own code, with no network.
The quieter the community and the more often you run, the bigger the gap: the stateless bill is always your cap, while the incremental bill is only what was actually posted.
Safety rails
- A source with nothing new returns zero rows and is billed nothing. No empty rows, no minimum charge.
- Two consecutive runs cannot double-count, not even a post sitting exactly on the boundary second: the mark remembers recent post ids as well as the timestamp, and Reddit timestamps are whole seconds, so that collision is common rather than theoretical.
- A run cut short still moves the mark, safely. If
maxItems,maxPostsPerSourceor a charge limit stops a run part-way, the mark advances to the newest post that run actually billed you for — listings are newest-first, so that post is trustworthy even though older ones on the same run were not reached. Either way, you are never billed twice:seenPostIdsremembers what was already charged independently of the mark, so a re-read of the same ground never re-bills it. - A run cut short DOES leave a permanent gap, and the run log and the Actor's OUTPUT say so.
If a source hits its cap before reaching the mark (or, on a first run, before reaching
firstRunSince), the posts older than where it stopped are never returned by a later run — later runs only look forward from the mark, they never go back to fill in what an earlier capped run skipped. Each source's line in the log names it plainly:The same fact is on the mark record itself ("reached back to <date>, requested back to <date>; posts older than that will not be returned by later runs - raise 'maxPostsPerSource' ... for a complete first pull."windowCovered: false,oldestReachedIso) and in the run's OUTPUT undercoverage, so you can check it without reading logs. If you want a source's full history, raisemaxPostsPerSource(ormaxItems) so the first run is never capped, or run once withfirstRunSince: "all". - A pinned post never ends a run early. Communities pin old announcement threads to the top of every listing; the early stop skips them rather than concluding the source is up to date.
- Incremental mode needs newest-first order, and says so in the log if you asked for another.
- Leave at least a minute between incremental runs on the same source. The marks live in
Apify's key-value store, and a run started only seconds after the previous one finished can read
a not-yet-visible mark and reseed from scratch — the log says when this happens, so it reads as a timing note rather than a mystery."mark record found but not yet set"
A source that returns nothing does not pass quietly
A misspelt subreddit name and a missing proxy both look like "no results", and neither should
finish SUCCEEDED with an empty dataset. If a source returns no posts at all and nothing explains it
— no date window, no score floor, no mark, no cap — the run fails and names the source, with
the likely cause. Rows already saved are kept: they are real, and the failure is about the ones
that are missing. Set failOnEmpty: false if an empty result is acceptable to you.
What it needs, and what it never does
This Actor needs a plan with Unblocker proxy access. Reddit answers a plain datacenter IP with HTTP 403 and an HTML block page, so the proxy group is the reason the Actor returns anything at all. Unblocker access is included on every plan, the free one included, and the input's proxy field is already set to it — there is nothing for you to configure. Each successful request costs about 10 proxy units, and one request returns up to 100 posts.
Requests carry a 90-second timeout by default. That is deliberate and measured: the unblocking
proxy does real work per request and a shorter timeout aborts requests that were about to succeed.
Raise it with requestTimeoutSecs if you see timeouts; the minimum accepted is 45 seconds.
It reads only the public JSON views of pages anybody can open in a browser — a subreddit listing, a search results page, and a post's comment tree. It never logs in, never sends a cookie from a signed-in session, and uses no Reddit API key and no OAuth token. It reads no private community, no direct message and no user inbox.
Reddit's own terms restrict automated collection of its content, and commercial use is gated on an agreement with Reddit. Satisfying yourself that your use is permitted is your responsibility as the person running the Actor.
Integrations, scheduling and automation
- Apify Schedules. Run hourly, daily or weekly. Combined with
onlyNewSinceLastRunthis is a Reddit mention monitor that bills only new posts. Leave at least a minute between runs on the same source. - Google Sheets. Use the Apify Google Sheets integration to append each run's new posts to a sheet.
- Make, n8n and Zapier. Start the Actor from a scenario, wait for it to finish, then read the dataset items. Filter on
dataSourceif you only want full JSON rows. - Webhooks. Call your own endpoint when a run succeeds, for example to post new leads or mentions into Slack.
- API and clients. Start runs and read results with the Apify API, the JavaScript client or the Python client. Datasets export as JSON, CSV, Excel, XML or HTML.
- AI agents and MCP. Input and output fields carry plain-language descriptions so an agent can choose inputs correctly.
- Incremental runs. Marks live in a key-value store on your own account, so recurring schedules keep their place across runs.
FAQ
Why use this instead of the official Reddit API?
The official Reddit Data API needs a registered app, OAuth credentials and agreement to Reddit's terms, and commercial use is gated on an agreement with Reddit; check Reddit's current Data API terms for rate limits and pricing. This Actor needs no Reddit API key and no OAuth app: it reads the public pages a logged-out browser can open, and you pay per row. The trade-off is coverage: it cannot reach private communities, messages or anything that needs a login, and while Reddit blocks its JSON the rows have no scores or comment counts.
What are the alternatives to this Reddit scraper?
Other Reddit Actors on the Store include Reddit Scraper Lite by trudax, Reddit Scraper by fatihtahta and Reddit Scraper Pro by harshmaur, which describe wider coverage such as user pages and direct post URLs. This Actor reads subreddits and search results only, and its focus is incremental runs: only new posts per run, with the saved marks on your own account.
How much does it cost to scrape a subreddit?
At the current price, 1,000 posts costs $1.50. Comments are priced the same per row, but each post's comment tree is one extra request, so a comments run over a handful of very quiet posts costs more per row than a posts-only run. Turn includeComments off if you only need post-level data; use maxItems or a max total charge to cap any run's cost outright.
Is this the official Reddit API?
No. This Actor reads the same public, logged-out views of reddit.com that a browser gets without an account, an app or a Reddit API key. It cannot reach private subreddits, direct messages or anything that requires being logged in.
How do I monitor Reddit for brand mentions?
Add your brand or product name to searchQueries, turn on onlyNewSinceLastRun and schedule the Actor daily. Each run returns only new matching posts. Use searchInSubreddits to limit each query to the subreddits you list.
Why are score and numComments empty in my results?
Check dataSource. rss rows come from Reddit's RSS feed, used when Reddit's JSON is blocked, and the feed has no scores, comment counts or flair. Re-run later: json rows carry everything.
Does it capture downvoted or removed comments?
When Reddit JSON is reachable, downvoted comments are kept unless they fail your minScore floor (default 0, so nothing is dropped by default). Removed and deleted comments come back as literal [removed] / [deleted] bodies, the same text Reddit shows a logged-out visitor. In RSS fallback mode comments are skipped.
Can I run this on a schedule without re-paying for posts I already have?
Yes. Turn on onlyNewSinceLastRun. See Incremental runs above; a daily schedule on an active subreddit bills only the posts published since the previous run.
Is scraping Reddit data legal?
Reddit's own terms restrict automated collection and gate commercial use behind an agreement with Reddit. This Actor only reads pages anyone can already open in a browser, logged out, but checking that your specific use is permitted under Reddit's terms is your responsibility as the person running it.
Related Actors
- Reddit Scraper Lite by trudax, one of the most-used Reddit Actors on the Store.
- Reddit Scraper by fatihtahta, posts and comment threads from searches and subreddits.
- Hacker News Scraper, stories and comment trees from Hacker News.
- Bluesky Scraper, posts and profiles from Bluesky.
- Substack Scraper, posts from any Substack newsletter.
- YouTube Scraper, channel videos, search results and comments.