Reddit Scraper & API Alternative | $1.5 / 1K avatar

Reddit Scraper & API Alternative | $1.5 / 1K

Pricing

from $1.05 / 1,000 post scrapeds

Go to Apify Store
Reddit Scraper & API Alternative | $1.5 / 1K

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

Hriday Rungta

Maintained by Community

Actor stats

0

Bookmarked

5

Total users

2

Monthly active users

2 days ago

Last modified

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 in dataSource. 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

InputDefaultNotes
subreddits["smallbusiness"]any of name, r/name or a full community link
searchQueries[]Reddit search syntax, including quoted phrases
searchInSubredditsfalseon: every query runs inside every subreddit listed
sortnewnew, hot, top, relevance (search only)
timeRangeweektop listings and search only
maxPostsPerSource25one Reddit request returns up to 100
minScore0dropped posts and comments are never billed; ancestors kept to close a thread are flagged and free
includeCommentsfalseone extra request per post; posts with no comments are skipped
maxCommentsPerPost50applied while the tree is walked, not after
commentDepth100 is top-level only
commentConcurrency5comment trees fetched at once; 150 posts ≈ 30–35 min. Output order is unchanged
onlyNewSinceLastRunfalsethe incremental mode described above
firstRunSince30 daysor all for a deliberate backfill
postsNewerThan / postsOlderThanemptyinclusive start, exclusive end, so adjacent windows never overlap
personalDatafalseoff: author usernames are redacted
maxItems1000hard row cap for the whole run
requestTimeoutSecs90minimum 45; the unblocker is slow by design
failOnEmptytruean 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, selftextthe post and its body, as Markdown
score, ups, upvoteRatio, numCommentshow it did
createdUtc, createdAtIsoexact publication time, as an epoch and as an ISO-8601 UTC timestamp
permalink, url, domain, isSelfwhere it lives and what it points at
subreddit, linkFlairText, over18, spoiler, locked, stickiedhow it sits in its community
sourceType, sourceValue, sortwhich 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:

bodythe comment text. [removed] and [deleted] are kept verbatim — a removed comment is information
depth0 for a top-level comment, 1 for a reply to it, and so on
parentId, parentTypethe parent's id with no prefix, and whether the parent is the post or another comment
postId, postTitlethe post the comment belongs to
isSubmittertrue when the post's own author wrote it
retainedForTreetrue 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: 0
— will sometimes reject a comment that has a well-scored reply underneath it. Dropping it would leave that reply pointing at a parentId 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 TRUE
gives you exactly the set your filter asked for (they are still rows, so they count towards maxItems). 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:

EventPriceWhen
post-scraped$0.0015one post row
comment-scraped$0.0015one 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

InputWhat it does
onlyNewSinceLastRunoff by default. On: return only posts published since this source's last run, then move the mark forward.
firstRunSincehow far back a source the Actor has never seen reaches. Default 30 days. Set it to all for a full backfill.
stateStoreNamethe key-value store on your account that holds the marks. Default reddit-scraper-state, created for you.
stateNamespaceoptional 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:

firstRunSinceFirst 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-01posts back to that point, then sets the mark
alleverything 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-competitors and reddit-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:

RunBilled rowsListing requests
Stateless, every run5005
Incremental, steady state501

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, maxPostsPerSource or 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: seenPostIds remembers 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:
    "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."
    The same fact is on the mark record itself (windowCovered: false, oldestReachedIso) and in the run's OUTPUT under coverage, so you can check it without reading logs. If you want a source's full history, raise maxPostsPerSource (or maxItems) so the first run is never capped, or run once with firstRunSince: "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
    "mark record found but not yet set"
    when this happens, so it reads as a timing note rather than a mystery.

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 onlyNewSinceLastRun this 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 dataSource if 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.

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.