Quarra
Guide · Reddit

How to export a Reddit thread with all its comments

Saving a thread is easy until it has a few hundred comments. How to get the whole tree, with who answered whom, and how to tell if some of it is missing.

Measured on October 4 to 7, 2026.

The short answer

For one or two threads, a browser extension or a “convert to text” site is fine, as long as it expands each “load more comments” link before it saves. For many threads, or for a big one, you need the reply structure (parentId and depth on each comment) and a tool that tells you if it missed any. Both the official API and a scraper can do it; this page shows each, then how to get a thread as CSV, JSON or compact Markdown for an LLM.

Why exporting a whole thread is harder than it looks

  • Reddit sends the first comments and “more comments” stubs. A tool that does not follow each stub saves a partial thread and nothing tells you.
  • The number Reddit announces is not the number you can collect. Deleted or removed comments with no replies are not served. On a 1,650-comment thread we got 1,561 comments, and 125 deleted comments with no replies were counted but not served: 1,686 in all, more than the 1,650 Reddit announces, because Reddit's counter is not an exact count of the tree.
  • Deep branches get cut by some tools. On that thread, two widely used scrapers stopped at depth 9; the thread goes to depth 12.
  • A flat list of comments loses who answered whom. Without parentId, a thread is just a pile of text.

With the official API in Python

If your app already has API access, PRAW can walk a thread. Resolve the “more comments” stubs, then flatten:

import praw

reddit = praw.Reddit(client_id="...", client_secret="...", user_agent="thread-export/0.1")
submission = reddit.submission(url="https://www.reddit.com/r/technology/comments/1wu4lnz/")
submission.comments.replace_more(limit=None)   # one network request per stub
rows = [
    {"id": c.id, "parent_id": c.parent_id, "author": str(c.author), "score": c.score, "body": c.body}
    for c in submission.comments.list()
]
print(len(rows), "comments")

It works for apps that already have access, until Reddit closes its public Data API: no new access requests after October 31, 2026, and the remaining public access ends in March 2027 (see our guide to Reddit API alternatives). Count on many sequential requests for a big thread, do not call replace_more twice on the same submission (PRAW's comment tutorial explains why), and expect submission.num_comments to differ from len(rows).

With our Reddit Scraper

Give it the post URL. A post URL returns the post and its whole comment tree, with no account, cookies or API key:

{
  "startUrls": ["https://www.reddit.com/r/technology/comments/1wu4lnz/"]
}

Redd.it short links, share links and comment permalinks work too. Only the comments of the last day of a big thread (you pay for the post and those comments; if none is that recent, only the $0.005 run start):

{
  "startUrls": ["https://www.reddit.com/r/technology/comments/1wu4lnz/"],
  "commentsPostedAfter": "1 day"
}

Only the top-level comments, which is much faster on big threads:

{
  "startUrls": ["https://www.reddit.com/r/technology/comments/1wu4lnz/"],
  "maxCommentDepth": 0
}

CSV or Excel, one comment per row

Each comment is a flat record, so it exports cleanly. These are the fields that let you rebuild the thread in a spreadsheet, from a real comment with its author replaced:

{
  "type": "comment",
  "id": "pd0smho",
  "postId": "1wu4lnz",
  "parentId": "t1_pd02xdu",
  "rootCommentId": "pczzr78",
  "depth": 3,
  "author": "example_user",
  "body": "Not a Swede, but don’t want him there either. ",
  "createdAt": "2026-09-30T15:31:09Z",
  "score": 920
}

Sort by postId then rootCommentId to read the thread branch by branch, or build the tree from parentId. In the Apify Console, download the dataset as CSV, XLSX or JSON. A comment that starts with =, +, - or @ is exported as written, and a spreadsheet may read it as a formula: import the CSV with the column type set to Text (in Excel, Data, From Text/CSV), or take the XLSX or JSON file.

Markdown, one record per thread

For an LLM, a thread as JSON wastes most of its tokens on field names. Set outputFormat to threadMarkdown and each post comes back as one record: the post, then its comment tree as an indented list, each comment written as u/author (score, date): text, replies under their parent in Reddit's order.

{
  "startUrls": ["https://www.reddit.com/r/technology/comments/1wu4lnz/"],
  "outputFormat": "threadMarkdown"
}

On the first 300 comments of the 2012 Obama AMA we measured 523,674 characters as JSON records and 72,284 as Markdown: 7.2 times fewer (October 6, 2026). The record carries the metadata an agent needs to know what it is reading; this is the real record for that run, with the lists and the text shortened:

{
  "type": "thread",
  "id": "z1c9z",
  "url": "https://www.reddit.com/r/IAmA/comments/z1c9z/i_am_barack_obama_president_of_the_united_states/",
  "subreddit": "IAmA",
  "title": "I am Barack Obama, President of the United States -- AMA",
  "author": "PresidentObama",
  "createdAt": "2012-08-29T20:01:36Z",
  "score": 216183,
  "numComments": 22719,
  "commentsIncluded": 300,
  "threadComplete": false,
  "commentIds": ["c60o0iw", "c60q4ga", "c6182qz"],
  "markdown": "# I am Barack Obama, President of the United States -- AMA\nr/IAmA · u/PresidentObama · ..."
}

The price does not change: one post plus one comment per comment included. A thread too large for one dataset record (over about 20,000 comments) comes as several records with part and lastPart, each comment in a single part.

From Python

from apify_client import ApifyClient
import csv

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("quarra/reddit-scraper").call(run_input={
    "startUrls": ["https://www.reddit.com/r/technology/comments/1wu4lnz/"],
})
rows = [r for r in client.dataset(run.default_dataset_id).iterate_items() if r["type"] == "comment"]
with open("thread.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=["id", "parentId", "depth", "author", "score", "createdAt", "body"], extrasaction="ignore")
    w.writeheader()
    w.writerows(rows)

Tested with apify-client 3.2. With apify-client 2.x, write run["defaultDatasetId"] instead of run.default_dataset_id.

The run also writes an OUTPUT record that gives, per thread, the comments Reddit announced, the comments scraped, the deleted leaves skipped and whether the thread is whole.

What we measured

  • 1,650-comment thread in r/technology, comments written since October 1: 263 of 263, down to depth 12. Two of the most used Reddit scrapers returned 245 and 224. The 40 comments they missed all exist on Reddit, under the same parent, with the same text (October 5, 2026, on Apify).
  • The whole thread: 1,562 records in 49 seconds, about $1.57 on the Free plan.
  • The 2012 Obama AMA announces 22,719 comments; we collected 22,806, every “load more” link followed, in about 15 minutes (October 4, 2026). Reddit's counter is not an exact count of the tree.

Limits

  • A 20,000-comment thread takes about 15 minutes.
  • Comments deleted with no replies are not returned by Reddit, so the count can be lower than the one Reddit shows.
  • Comment search and user histories do not carry depth or rootCommentId: Reddit does not provide them there.
  • Scores and counts are what Reddit showed at scraping time.
  • The text of a thread is personal data under laws such as the GDPR, and Reddit's terms apply to what you do with it. Take what you need for a legitimate purpose.

What it costs

Comments are $1.00 per 1,000 on the Free plan, a post $1.50 per 1,000, and $0.005 to start a run; the Starter, Scale and Business plans are cheaper per result. The price per result is the same in the Markdown format. Errors and duplicates are not charged, and you can cap the cost of a run.

Run it on the Apify Store

Posts cost $1.50 per 1,000 and comments $1.00 per 1,000 on the Free plan, plus $0.005 per run. Errors and empty results are not charged, and you can set a maximum cost per run.