Reddit Post Comments Scraper
Pricing
from $3.00 / 1,000 post scrapeds
Reddit Post Comments Scraper
Scrape any public Reddit post together with its full comment tree. Paste post URLs, short links, or bare post IDs — or a keyword to find posts — and get one clean row per post and one row per comment, with reply depth, parent links, comment text, and export-ready fields.
Pricing
from $3.00 / 1,000 post scrapeds
Rating
0.0
(0)
Developer
APISmith
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Scrape any public Reddit post together with its full comment tree. Paste post URLs, short links, or bare post IDs — or a keyword to find posts — and get one clean row per post and one row per comment, carrying the comment text, author, score, timestamp, reply depth, and the ID of the comment each reply answers.
No Reddit account, no login, no cookies. Run it in Apify Console and export the dataset as JSON, CSV, or Excel, or read the same rows over the API.
Every input gets an answer. A post that is deleted, private, or unreadable still returns a row that says what happened — and those rows are not billed. You pay for delivered content: $3.00 per 1,000 posts and $0.95 per 1,000 comments, plus a $0.003 charge per run that covers looking the posts up.
What you get
One dataset holding three kinds of row, all joinable on _post_id:
- post rows — one per post, with the post's own fields: title, subreddit, author, score, upvote ratio, comment count, body, permalink, link URL, creation time, flair, awards, media, and the rest of the fields Reddit exposes for a post;
- comment rows — one per comment, with its text, author and author ID, score, timestamps, reply depth, parent comment ID, permalink, child count, and moderator/removed state;
- status rows — one per post that could not be delivered, so a dead or private link shows up in your export instead of vanishing from it.
Here is what that looks like in practice, for a real post:
[{"_type": "post","_post_id": "t3_1wm4sf0","_status": "found","title": "Deinfluence me to buy BYD","subreddit": "phcars","score": 19,"commentCount": 217},{"_type": "comment","_post_id": "t3_1wm4sf0","_status": "found","id": "t1_pb458pe","author": "Physical-Ad4744","body": "For now, they're saying that we need an EV because gas prices are going up…","score": 24,"depth": 0}]
Comment rows carry depth and parentId, so the thread's shape survives the flattening: rebuild the tree from the dataset alone, group by depth to see how a conversation branches, or sort by score to find the answers that landed.
The Posts & Comments view in the Output tab shows the 29 columns that matter most. The rows themselves keep every field the source returned, so switching views never costs you data.
Key features
Every input gets a row. A URL that is dead, deleted, or private produces a _status row instead of silence, so a hundred inputs give you a hundred rows and you can tell "no comments" apart from "never looked at".
The thread's shape survives. depth and parentId on every comment row are enough to rebuild the reply tree, measure how deep a conversation went, or find the branch nobody continued.
Missing data stays missing. A field the source did not return is left out of that row rather than written as null, which is what makes isDeleted: false mean "the source said not deleted" instead of "we are not sure".
IDs you can use as-is. Comment IDs keep their t1_ prefix and author IDs their t2_, so an id from the dataset pastes straight into a Reddit URL or fullname parameter without editing.
Built for batches, in the order you asked for. Send up to 500 posts in one run; rows come out in input order whatever order the posts finish in, duplicate entries collapse, and one bad URL never stops the rest of the batch.
Quick start
- Open the Actor and go to the Input tab.
- Paste one post into Post URLs or IDs:
{"postUrls": ["https://www.reddit.com/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/"]}
- Click Start.
- Open the Output tab when the run finishes and switch to the Posts & Comments view.
- Export it as JSON, CSV, or Excel — or read it over the API.
That's it. There is no login step, no browser session, and no manual pagination.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
| Post URLs or IDs | array | [] | The posts to scrape. One per line: full URL, short link, share link, bare ID, or t3_ ID. A pasted CSV row is read as one post. |
| Search keywords | array | [] | Used only when no post URL or ID resolves. Finds posts by keyword, then collects their comments. |
| Max posts per keyword | integer | 25 | How many matching posts to pull comments from, per keyword. Capped at 200. |
| Comment sort order | select | Best | Best, Top, New, Old, Controversial, Q&A — see the note on Q&A below. |
| Comment filter | select | All comments | All comments, Answered by OP only, Unanswered only. |
| Max comments per post | integer | 200 | Comments per post, counting replies. 0 means unlimited, up to a 5,000-comment ceiling. |
| Expand all sub-threads | boolean | false | Recursively opens every collapsed reply chain. Slower and more expensive, and the only way to reach comments buried deep in sub-threads. |
If both post fields are filled, the post list wins. Keywords are only used when nothing in Post URLs or IDs resolves to a post, so one run does not silently do two jobs at once. Duplicates collapse, and entries are read in the order you gave them.
On the free plan, a run is capped at 5 posts, 10 comments per post, and 5 search keywords. Those ceilings are applied on top of the fields above, so a list of 50 post URLs still delivers 5 posts — and the run log names what was skipped rather than dropping it silently. All three ceilings count together: 5 posts in total, however they were found.
Scrape one post or a list
One post per line. All of these forms are understood:
https://www.reddit.com/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/reddit.com/comments/1wm4sf0redd.it/1wm4sf01wm4sf0t3_1wm4sf0
Short links and share links are resolved to the post they point at. Use Bulk edit in the Console to paste a list. A line that is not a post URL or ID is listed in the run log with the line's own text — text without a post reference is never quietly dropped.
Paste a CSV export straight in
The post URL column is picked out of each pasted row and a header row is ignored, so a spreadsheet export can go in as-is without extracting URLs first.
Find posts by keyword
Leave Post URLs or IDs empty and put your keywords in Search keywords. Each keyword is searched, and comments are collected from up to Max posts per keyword matches.
Keywords are passed to the search exactly as you type them — no rewriting and no translation of search operators. Whether a given operator is honoured is a property of the search itself, so test a query with a low Max posts per keyword before running it large.
Advanced options
Comment filter has to know whether the original poster replied to a comment, and collapsed reply chains are not collected by default — so with a filter on, the first reply level of each candidate is opened to find out, and the run log reports how many candidates were checked. A reply buried deeper inside a still-collapsed chain can be missed, so on a very large thread it is more reliable to collect everything and filter the export yourself.
Q&A sort returns the same order as Best. The source does not offer a Q&A ordering, so the option is kept for compatibility and labelled as such rather than pretending to reorder anything. For Q&A structure, use the Comment filter — sorting and filtering are separate decisions here.
Expand all sub-threads is the expensive option: every collapsed chain is fetched separately, and a chain may hold only a comment or two. Pair it with Max comments per post so the run has a ceiling.
Output
One dataset, three kinds of row, all carrying _type, _post_id, and _status.
Post rows — _type: "post"
One row per delivered post, holding the post's own object plus the routing keys.
{"_type": "post","_post_id": "t3_1wm4sf0","_status": "found","title": "Deinfluence me to buy BYD","subreddit": "phcars","commentCount": 217,"id": "1wm4sf0","name": "t3_1wm4sf0","author": "SolsticeGloom","score": 19,"num_comments": 217,"created_utc": 1789971863,"permalink": "/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/","url": "https://www.reddit.com/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/","is_self": true,"over_18": false}
Comment rows — _type: "comment"
One row per comment. depth is 0 for a top-level comment and grows with nesting; parentId holds the comment this one replies to, and is absent on a top-level comment because that comment replies to the post.
{"_type": "comment","_post_id": "t3_1wm4sf0","_status": "found","id": "t1_pb458pe","author": "Physical-Ad4744","score": 24,"createdAt": "2026-09-21T06:34:08.950000+0000","depth": 0,"permalink": "/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/pb458pe/","body": "For now, they're saying that we need an EV because gas prices are going up…","isStickied": false,"isDeleted": false,"authorId": "t2_1tkjx3pmtx","languageCode": "fil"}
Two details worth knowing before you parse the output: post timestamps use Reddit's own created_utc (Unix seconds) while comment timestamps use createdAt / editedAt (ISO strings), and comment IDs keep their t1_ prefix while author IDs keep t2_.
Status rows
A post that could not be delivered still gets a row:
{ "_type": "post", "_post_id": "t3_zzzzzz", "_status": "not_found" }
_status | What it means | What to do | Billed |
|---|---|---|---|
found | The post was delivered, with its comments where any were returned | Nothing | Yes |
not_found | The post does not exist, or was deleted before the run | Check the post ID or URL | No |
unavailable | The post is private, quarantined, or age-gated — or could not be read | Confirm the content is publicly visible | No |
A status row carries only the three routing keys: there is no content, so nothing else would be honest to fill in.
Field reference
Rows carry the fields the source actually answered. This is the full set a row of each type can carry.
Post rows (65 possible fields):
| Group | Fields |
|---|---|
| Routing keys | _type, _post_id, _status |
| Post identity | title, subreddit, id, name, permalink, url, domain, subreddit_id, subreddit_subscribers, full_link, subreddit_name_prefixed, subreddit_type |
| Author | author, author_fullname, author_flair_text, author_flair_css_class, author_flair_richtext, author_flair_type, author_premium |
| Engagement | commentCount, score, upvote_ratio, num_comments, total_awards_received, all_awardings, ups, gilded, awarders, num_crossposts, contest_mode, pinned, no_follow, send_replies |
| Content and media | selftext, is_self, is_video, link_flair_text, link_flair_background_color, thumbnail, post_hint, preview, distinguished, media, secure_media, media_metadata, gallery_data, is_gallery, is_original_content |
| Flags and timestamps | created_utc, over_18, spoiler, stickied, locked, archived, edited, is_crosspostable, is_meta, is_robot_indexable, can_mod_post |
| Moderation | banned_by, approved_by, removed_by_category |
Comment rows (30 possible fields):
| Group | Fields |
|---|---|
| Routing keys | _type, _post_id, _status |
| Comment | id, score, createdAt, editedAt, depth, parentId, permalink, body, isStickied, isLocked, isScoreHidden, distinguishedAs, isDeleted, childCount, languageCode, isArchived, isRemoved, removedByCategory, isCommercialCommunication, isInitiallyCollapsed, contentTypeHint |
| Author | author, authorFlair, authorId, authorAccountType, authorIsCakeDay, authorIcon |
Because absent fields are omitted rather than nulled, a false or 0 in a row is always a value the source sent: isLocked: false means not locked, and childCount: 0 means no replies — never "unknown".
Pricing
This Actor uses pay-per-event pricing. You pay for delivered rows, plus one small charge per run:
| Event | Price | Charged when |
|---|---|---|
| Post scraped | $3.00 per 1,000 posts | A post row is written |
| Comment scraped | $0.95 per 1,000 comments | A comment row is written |
| Actor start | $0.003 per run | Once, at the start of the run |
The post used throughout this page has 217 comments. Collecting 200 of them — the default limit — costs about $0.20: $0.19 for the comment rows plus $0.006 for the post row and the run itself. A post with 1,000 comments costs about $0.96. A thousand posts with no comments costs about $3.00.
Why there is a charge per run
Looking a post up costs the same whether the thread turns out to be busy or empty, and that work does not belong to any delivered row. The flat per-run charge covers it, and it is the same whether the run holds one post or five hundred.
What is never billed
- Status rows — a post that could not be delivered is never billed. The lookup that discovered the failure is covered by the per-run charge.
- Comments you did not ask for — Max comments per post puts a ceiling on comment rows, and reaching it also ends pagination early.
- Duplicate inputs — the same post sent twice is collected once, and billed once.
Keeping the bill down
Set Max comments per post to what you actually need; it caps both the rows and the work. Leave Expand all sub-threads off unless you need the deep replies. And when exploring keywords, start with a low Max posts per keyword — the search runs before any post is looked up, so a lower limit is a cheaper run.
You can also set a maximum charge for a run in the Apify Console. When it is reached the run stops collecting instead of continuing to spend: posts already in flight finish, nothing new starts, and the rows collected so far are still delivered. The run log says the limit was reached, so a short dataset is never left unexplained.
Give the limit room for at least one charge per post. A limit that cannot cover the per-run charge plus one post charge for every post in the input delivers nothing at all, so the run stops before its first request and tells you how much it needed. At the rates above, three posts need about $0.012.
See the Pricing tab for current rates.
Automate it
A run can be started by hand, on a schedule, or from your own code — and the dataset is the same in every case.
- Scheduled runs — set a Schedule in the Console to re-collect a watchlist of posts daily or weekly.
- API — every run is addressable, so a backend, a script, or a CI job can trigger it.
- Webhooks and integrations — connect the Actor to Google Sheets, Airtable, Slack, Make, Zapier, or a webhook, and each finished run pushes its rows onward.
- Export — download any dataset as JSON, CSV, or Excel from the Output tab.
Run it from your code
JavaScript, using the Apify client — this starts a run, waits for it, and reads the rows:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('apple_yang~reddit-post-comments-scraper-api').call({postUrls: ['https://www.reddit.com/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/'],});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items);
Python:
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("apple_yang~reddit-post-comments-scraper-api").call(run_input={"postUrls": ["https://www.reddit.com/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/"]})items = client.dataset(run["defaultDatasetId"]).list_items().itemsprint(items)
Or straight from the shell, keeping the token in the header where it belongs:
curl -X POST "https://api.apify.com/v2/acts/apple_yang~reddit-post-comments-scraper-api/runs" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"postUrls": ["https://www.reddit.com/r/phcars/comments/1wm4sf0/deinfluence_me_to_buy_byd/"]}'
Each of these starts a run, and a run is billed as described above — the per-run charge plus whatever rows it delivers. The start response carries the run's defaultDatasetId, which you then read from the dataset API.
Use it with AI agents
A finished run is a tidy unit of Reddit material: post text, comment text, authors, scores, timestamps, and the reply structure that connects them. That is useful raw input for a downstream model — classify sentiment per comment, summarize what a thread concluded, retrieve answers into a RAG index, or label the questions that went unanswered.
Reach the Actor through the Apify MCP server and an assistant can fetch a thread's comments on request, in the same JSON shape shown above. The fetching is done by the run, so the same per-row and per-run charges apply.
Use cases
Sentiment and topic analysis on a discussion — collect a thread once and run your own text model or spreadsheet over the body column, using score and depth to weigh what the room agreed with.
Finding unanswered questions — point the Actor at a support subreddit, use the Unanswered only filter, and get the top-level questions nobody from the original poster's side answered.
Building a Q&A dataset from an AMA — use Answered by OP only to keep the question-and-answer pairs and drop everything else, then load the rows into a dataset or knowledge base.
Monitoring what a subreddit argues about — collect the same handful of posts on a schedule and compare commentCount and top comments over time.
Archiving a thread before it changes — posts and comments get deleted; a saved dataset keeps the text, author, score, and shape of a thread as it was when you collected it.
FAQ
Do I need a Reddit account, an API key, or cookies?
No. The Actor collects publicly visible content only, so there is nothing to log in with. You need an Apify account to run it.
Can I collect comments for a post whose URL I do not have?
Yes — put a keyword in Search keywords instead, and comments are collected from the matching posts up to your Max posts per keyword.
Can I get every comment of a very large thread?
Usually yes. Set Max comments per post to 0 for unlimited, up to a 5,000-comment ceiling, and leave Expand all sub-threads on to include replies hidden inside collapsed chains. On an exceptionally deep thread the expansion stops at a per-post ceiling: the comments already collected are kept, the post row is still delivered, and the run log warns about it.
Why is a field missing from one row and present on another?
Because the source did not return it for that row. An absent key means "not provided", while a false or 0 means a real value. A top-level comment has no parentId for exactly this reason.
What happens to a post that is deleted, private, or age-gated?
It returns a _status row — not_found or unavailable — which is not billed. Your export keeps one row per input, so a failed post is visible rather than missing.
Can I run this on a schedule, or from my own code?
Yes to both. Use a Schedule in the Console, or call the Actor from the API or an Apify client as shown above.
Limitations and responsible use
Public content only. The Actor does not log in, use cookies, or reach anything a signed-out visitor could not see. Content behind a login, a private subreddit, or an age gate cannot be collected, and comments or posts deleted before the run are not recoverable.
Very large threads are bounded. Deep reply expansion stops at a per-post ceiling to keep a run finite. When that happens, comments already collected are kept, the post row is still delivered, and the log says so. A thread shortened by Max comments per post is the same kind of documented limit, not a failure.
Comments can fail independently of the post. If comment collection is interrupted for a post, the post row is still delivered but without its comments, and the run log warns about it. Re-running that single post is usually enough.
Q&A sort does not reorder anything — it currently returns the same order as Best; use the comment filter for Q&A structure. Keyword searches are passed through as written, and availability depends on public content staying public: Reddit may restrict access to some otherwise public posts, and content reachable today may not be tomorrow.
Please use this Actor only for content you are permitted to access and process. Respect Reddit's terms, the privacy of the people whose comments you collect, and any law that applies to you — including data-protection rules such as the GDPR. Do not use it to build profiles of individuals or to harass anyone.
Related Actors and support
Other Actors by the same developer, for when your data lives on a different platform:
| Need | Actor |
|---|---|
| Collect comments from public Instagram posts | Instagram Comments Scraper |
| Collect Instagram posts, captions, and metadata | Instagram Post Scraper |
| Turn a public video into text | Instagram Transcript API |
| Download public video and audio files | Facebook Video Downloader API |
Support: write to support@apismith.online, or open an issue on the Actor page. Including the Apify run ID, your input (if it is not sensitive), and the _status values you saw makes a report much faster to act on.
Please never send API tokens, cookies, session IDs, passwords, or authorization headers — nobody needs them to look into a problem.