Reddit Scraper — Subreddit Posts, Comments & Search, No API Key
Pricing
from $1.50 / 1,000 items
Reddit Scraper — Subreddit Posts, Comments & Search, No API Key
Type a subreddit and get its posts as rows — title, text, score, upvote ratio, comment count, flair, date, image or playable video link — sorted hot, new, top or rising. Add comments per post, search all of Reddit, or paste post links. No API key, no login, exact limit, no result fee.
Pricing
from $1.50 / 1,000 items
Rating
0.0
(0)
Developer
Steadyfetch Team
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Click Start with nothing set and the run returns a small built-in sample — one public subreddit, five posts, comments off — so you see the output shape before you spend anything, at no result fee.
Using an AI agent? Pin this actor in Apify's MCP server with one link: https://mcp.apify.com?tools=steadyfetch/reddit-scraper
- Actor id:
steadyfetch/reddit-scraper - Input:
{ "subreddits": ["smallbusiness"] }— the one field you have to set. Or{ "searchQueries": ["apify"] }to search all of Reddit, or{ "postUrls": ["https://www.reddit.com/r/smallbusiness/comments/1wdl068/i_understand_why_my_supplier_doesnt_take_cards/"] }for posts you already have links to. - Cap the bill: set
maxTotalChargeUsdon the run (a run option, not Actor input), e.g.0.50— the run stops when it reaches it. - Your cap is the cap.
resultsLimit(default 50) is exact and per subreddit or query;maxItems(default 1,000) caps the whole run, post rows and comment rows together. If your input carrieslimit,countormaxPostsinstead — the names other Reddit scrapers use — the smallest of them is read asmaxItemsand an uncharged note row says which.
Priced from $1.50 per 1,000 items on the cheapest paid tier, $3.00 per 1,000 on the Apify free plan, platform usage included, no start fee. One item is one row — a post, or a comment. You are charged only when a row lands in your dataset: a subreddit Reddit will not show us, a post link that is gone, a search that matched nothing, and every row a limit or the run clock stopped us from collecting all cost $0.
What one row looks like
A real delivered post row, built by this actor's own parser from a harvested subreddit listing:
{"type": "post","id": "1wdl068","fullname": "t3_1wdl068","permalink": "https://www.reddit.com/r/smallbusiness/comments/1wdl068/i_understand_why_my_supplier_doesnt_take_cards/","subreddit": "smallbusiness","subredditId": "t5_2qr34","title": "I understand why my supplier doesn’t take cards but damn","text": "We're placing our biggest inventory order so far and I completely forgot this supplier is ACH only…","authorName": "…","authorId": "…","createdAt": "2026-09-11T16:24:41.000Z","score": 80,"upvotes": 80,"upvoteRatio": 0.92,"commentCount": 46,"flair": null,"isNsfw": false,"mediaType": "text","imageUrl": null,"videoUrl": null,"source": "subreddit_feed","status": "delivered","charged": true}
The post text and the two author fields are trimmed for this page — a real row carries the body in full, as published and as HTML, and the author's name beside the stable ID behind it. A comment row carries the same columns plus the post it belongs to, its parent and its depth. Every column is listed in the table below; a row that could not be delivered carries the same columns with null in them plus a status saying why, and no result fee.
What you get
One row per post and — with Include comments on — one row per comment, on one column set whichever door the row came through. type says which it is, so a post and its thread land in the same table.
| column | what it is |
|---|---|
type | post or comment |
id · fullname · url · permalink | the Reddit ID, the fullname Reddit keys it by, the URL the post points at, and the reddit.com permalink |
subreddit · subredditId | the subreddit it came from, by name and by stable ID |
title · text · textHtml | the post title, the post text as published, and the same text as HTML. On a comment row, text is the comment body. |
authorName · authorId | the author's name and the stable ID behind it. A name can be changed by its owner; the ID cannot. |
createdAt | when it was published, ISO-8601 UTC |
score · upvotes · upvoteRatio · commentCount | the four engagement numbers, as Reddit serves them — real values, not zeros: a measured page carried a score on 25 of 25 rows, with the upvote ratio and the comment count beside it. |
flair · isNsfw · isSpoiler | the flair text, and the over-18 and spoiler flags |
isVideo · postHint · domain | Reddit's own media hints. A row from Reddit's own search is thinner than a feed row: search does not serve flair, postHint or domain, and on those rows they are explicit nulls rather than guesses. |
mediaType | text, image, video, gallery or link |
imageUrl · videoUrl · videoAudioUrl · linkUrl | the direct image link, the playable video link and its separate audio track, and the outbound link on a link post |
awards | how many awards the post carries |
postId · parentId · depth · isReply · collapsed | on a comment row: the post it belongs to, its parent, how deep in the thread it sits, whether it replies to another comment, and whether Reddit had it collapsed |
source | the door this row came through: subreddit_feed, search, comment or link |
charged · chargedEvents | whether this row was billed, and by which event. No row bills two. |
status · statusReason | what happened, in a sentence, on every row including the ones that cost nothing |
A gallery post ships as mediaType: gallery with the gallery page link and no member images. Reddit's feed carries no gallery metadata at all — no image list, no image count, nothing — verified absent across three 82 KB pages of it. The gallery link is what exists, so the gallery link is what the row says, rather than an empty column that promises pictures.
The video column no one else ships
A Reddit-hosted video post comes out playable. Reddit answers a video post with a bare v.redd.it link that plays nothing on its own, and the shelf's bigger listings hand you that link as-is. This row resolves it.
videoUrlis the highest rendition Reddit publishes for that video,CMAF_720.mp4, read from Reddit's own DASH manifest for the post. The manifest is a public file and reading it costs you nothing extra — there is no second event and no surcharge on a video row.videoAudioUrlis a separate track, because that is how Reddit serves it: the video file carries no sound of its own. For the pictures takevideoUrl, for the spoken words takevideoAudioUrl, for both mux the two.- A video whose manifest will not answer leaves
videoUrlandvideoAudioUrlnull withvideoUrlReasonsaying why — and the post row is delivered and charged exactly as it would have been. A link we could not resolve never costs you the row it sits on.
What it costs
Two charged events. Nothing is charged for starting a run, and nothing is charged for a row that did not land.
| event | what one unit is | Apify free plan | paid plans, from |
|---|---|---|---|
| Item | one post row or one comment row, from a subreddit feed, from Reddit's own search, or from a post's comments | $0.003 | $0.0015 |
| Post lookup | one post you named by link, delivered with the same columns | $0.012 | $0.005 |
An Item is a post or a comment — the same row and the same price either way, so the bill reads straight off the row count and nothing else.
50 posts from one subreddit is 50 items: $0.15 on the Apify free plan, $0.075 from the cheapest paid tier.
One post link with comments on is one Post lookup plus that post's first page of comments — about 25 items — so $0.087 and $0.0425. The comments ride on the same read: turning them on costs nothing beyond the rows they produce.
Comments multiply rows, and that is the one number to set before you start. 50 posts with Max comments per post at 50 is up to 2,550 rows, not 50. maxItems (default 1,000) is the ceiling on the whole run, and maxTotalChargeUsd on the run is the ceiling on the bill.
Never charged
Every one of these is still delivered as a row, labelled, with a sentence saying which it was — never a silent gap in the dataset, and never a charge.
| the row says | what happened |
|---|---|
subreddit_unavailable | Reddit returned no posts for that subreddit — it may not exist, be private, be banned or simply be empty |
post_not_found | a post link Reddit answers with an empty shell: the post is gone, or was never there |
no_results | a search that matched nothing. Typed, never retried, and never widened |
feed_unavailable | the read failed on our side. A re-run is the fix, and the row says so |
unsupported_link | a link this actor cannot serve: a /user/ or /wiki/ link, or a host that is not Reddit's |
skipped_date_window | your own date window dropped a post you named by link |
skipped_nsfw | your own over-18 setting dropped it |
skipped_seen · skipped_duplicate | your account already had that row, or the same row arrived twice inside one run. A run whose API token cannot open key-value stores can produce neither — it says so on its first row |
skipped_max_items · skipped_budget · skipped_deadline | your own row ceiling, your own maxTotalChargeUsd, or the run clock ended the collecting first |
skipped_targets | this run's own ceiling of 200 targets trimmed the rest of your list. That ceiling is ours, not yours |
skipped.page_cap | a walk stopped at this run's own reading depth while Reddit was still serving — a subreddit, a search, or one post's comment thread. That depth is ours, not yours, and the rows behind it were never read or charged |
vendor_budget · vendor_out_of_credit | our own monthly collection allowance, or our collection credit running out mid-run. Both ours; neither is ever yours |
input_error — and input_error.subreddits, input_error.searchQueries, input_error.postUrls, input_error.startUrls | something in that field could not be read. The row names the field, never what you typed |
undeclared_field | you sent a field name this actor does not have, so nothing was read from it. The row names the name you sent and the field this actor uses instead, never what you typed |
A walk that runs short also says WHY, on its own uncharged note row. A subreddit is read to a depth of 40 pages and one search query to 5 — so a walk that stopped there says it stopped at this run's own depth, which is ours and not a statement about what Reddit holds, and tells you what narrows the ask (a date window, a different sort, a more specific query). Only a listing that genuinely ran out says the rest does not exist to collect. Those rows are counted on the run's record as skipped.page_cap, so the depth we chose is a number you can see rather than a shortfall that quietly shrinks your ask. One post's comment thread is read to a depth of 20 pages on exactly the same rule: the post row says the thread went deeper than one run reads, and the comments behind our depth are counted the same way. Your own "Max comments per post" ending a thread is the opposite case — that is your setting, nothing was refused, and nothing is counted. And if a read fails part-way through a subreddit you were already being served, that row opens with what was already delivered — those rows are yours and each says what it was charged — and the re-run is for the rest.
And here is the one the shelf does not say out loud: a subreddit that does not exist, one that is private and one that is banned all come back as the same empty answer. Reddit tells us nothing that separates them — one identical body for all three — so the row says all three rather than picking one and sounding certain, and we do not buy a second read to guess. You are charged for none of it. The complaint that fills the Issues tabs of the biggest Reddit scrapers on this store is a run that reports success and returns nothing; this section is the answer to it.
Three things worth knowing
- A Reddit user's own post list is not a door. Reddit does not serve it to any reader we can be. That is a fact about Reddit, not a feature waiting on a roadmap — paste that user's post links instead, and each one comes back as a full row.
- Comments come in pages of about 25, with replies nested under their parents. Max comments per post (
maxCommentsPerPost) is your cap: the run walks pages until it reaches the cap, the thread ends, or this actor's own ceiling of 20 comment pages for one post — so one thread can never spend a whole run. A post whose thread still had more keeps acomments_truncatednote on its own row saying how many were delivered and which of those three stopped it. That is a note on a delivered, charged row — never a miss, and never an extra charge. - Search is Reddit's own search, and it returns 4 to 7 posts per page. Narrow a query with a subreddit or a date window rather than paging deep. And this actor never widens a query that came back empty — an empty search ships one
no_resultsrow and stops there. Widening is why "the results are about the wrong topic" is the second thing buyers write on a Reddit scraper's Issues tab.
How often the data changes
| feed | how fast it moves |
|---|---|
hot · rising | turns over within the hour — the same run an hour later returns a different page |
new | a stream. It is always moving and it never repeats |
top | stable once its window has closed: last week's top for a finished week reads the same next month |
a post's score and commentCount | keep moving for about 48 hours after it is published, then settle |
The schedule that stays cheap. Run sort: new with Only posts published after (postedAfter) set to your window — a day, an hour — and the walk stops as soon as the feed goes older than that, instead of paging into posts you already hold. Your account's memory does the rest: every row this actor has already delivered to your account is skipped on the next run, no row and no charge, so a daily schedule pays for what is new and nothing else. Switch Include rows you already have on to get them anyway, marked repeat: true and still uncharged. The memory is a key-value store in your own Apify account, so a run started with a scoped API token in restricted-access mode needs key-value store Read, Write and Create permission, or Actor runs set to Full access (Write is what lets the run remember what it delivered; without it every run pays again); without it the run says so on its status line and on its first row, and cannot skip what you already have.
For a leaderboard rather than a stream, sort: top over a closed timeframe is the one feed you can re-read without surprises — and the second read of it costs nothing, because the memory already has those rows — as long as the run's API token can open key-value stores in your account.
Every run ends with a receipt
The last row of every run is a receipt: rows delivered by door, post rows against comment rows, what was asked for, what your account already had, what your date window and your over-18 setting dropped, what stopped the run, and the charged totals for both events — so an invoice reconciles from the dataset itself. A cost cap or a time limit ends the collecting, never the delivering — rows already in hand are always written out.
Related actors
- The spoken words of a Reddit video: hand
videoAudioUrltosteadyfetch/media-transcriberin itsurlsfield — the audio track is the one with sound in it. - Comments under a Facebook post:
steadyfetch/facebook-comments-scraper. Posts from a Facebook page or public group:steadyfetch/facebook-posts-scraper.
Something not right?
Open the Issues tab on this actor with your run ID and the input you used, and it gets looked at. If it earned its keep, a rating helps other buyers find it, and saving the actor keeps it one click away.