Most tutorials on how to scrape Reddit still start by adding .json to a URL, and logged-out requests to those endpoints have returned 403 since late May 2026. What remains of Reddit's open access is closing on a published schedule: no new Data API requests after October 31, no RSS after November 13, and no public API for remaining apps from March 2027. Below are the five methods that still work in late 2026, with Python code for posts and full comment trees, and the IP setup each one needs.
Key takeaways
- Logged-out .json requests return 403 since late May 2026, Old Reddit requires a login, and RSS feeds end on November 13, 2026.
- Official Data API access closes to new requests after October 31, 2026, and to remaining public apps in March 2027. Approved apps keep 100 queries per minute per OAuth client until then.
- Logged-in sessions still receive Reddit's JSON. A persistent browser profile on one dedicated mobile IP keeps the session stable, and fetch() from inside the page returns posts and full comment trees.
- Logged-out public pages still load in a real browser, and the current front end carries each post's data as shreddit-post attributes. Hold one IP per browser session, and count a page with no posts as a failed run.
- AI agents can scrape Reddit through Playwright MCP, with the VoidMob MCP server handling verification numbers and proxy details. Apify's maintained Reddit Scraper Lite bills $4.00 per 1,000 results on the free plan, down to $3.40 on Gold and above (October 2026).
What Changed on Reddit in 2026
Two rounds of changes, announced in public posts to moderators and developers, reshaped Reddit scraping this year.
- Logged-out .jsonOpen, rate limited403Since late May 2026
- Old RedditOpen without loginLogin requiredNext: 6-month activity rule
- RSS feedsAvailableEndingNovember 13, 2026
- New Data API appsSelf-service keysClosedNo requests after Oct 31, 2026
In late May, a post in r/modnews titled Protecting communities from scrapers and platform abuse announced the shutdown of unauthenticated .json endpoints, and anonymous requests started failing soon after. The same post said logged-in and authenticated access would not be affected. Methods 2 and 4 below build on that line. Old Reddit began requiring a login around the same time.
On September 30, a second package followed, reported by TechCrunch: RSS support ends November 13, 2026, developers must register their apps by January 12, 2027, and public Data API access closes for remaining apps in March 2027. Old Reddit will be limited to accounts that used it in the past six months, plus moderators. New public API access requests stop on October 31, 2026.
Reddit's replacement for third-party apps is Devvit, its developer platform, where apps are written in TypeScript and run inside Reddit. That suits bots and community tools. It is not a bulk export channel.
Scrapers built on anonymous .json, old.reddit HTML or RSS now return errors or empty pages, which is why so many published Reddit scrapers stopped working this year.
Is Scraping Reddit Allowed?
Scraping Reddit is not allowed under Reddit's own rules. Its User Agreement permits crawling only within the parameters of Reddit's robots.txt and states that "scraping the Services without Reddit's prior written consent is prohibited." Since mid-2024, that robots.txt has read User-agent: * followed by Disallow: /, with access for companies that hold agreements, such as Google.
API access falls under the Responsible Builder Policy, updated October 5, 2026. Commercial use needs written approval, and the policy extends its approval requirements to commercial and non-commercial scraping and to training machine learning or AI models.
“You must request access and get explicit approval before accessing any Reddit data through our API”
In practice, breaking those rules costs accounts and IP addresses first. Legal exposure depends on how you access the data. In hiQ v. LinkedIn, the Ninth Circuit found that scraping publicly accessible pages likely does not violate the federal anti-hacking law, and in 2024 a federal court found that Meta's terms did not stop Bright Data from scraping public pages while logged out. The flip side matters for Method 2 below: an account that logs in has accepted the User Agreement, so logged-in scraping carries contract exposure that logged-out scraping may not.
Reddit also litigates. It sued Anthropic in June 2025, then Perplexity, SerpApi, Oxylabs and AWMProxy in October 2025 over Reddit content scraped from Google results. On July 31, 2026, a federal judge let Reddit's main DMCA anti-circumvention claims against SerpApi and Perplexity proceed. Posts also contain personal data, which brings in privacy laws such as GDPR. None of this is legal advice, and for commercial products a data agreement with Reddit is the clean route.
How to Scrape Reddit Data: 5 Methods Compared
Five methods still return Reddit data in late 2026, and they differ most in how long each will keep working.
| Method | Access | Cost | Volume | Lifespan | Best for |
|---|---|---|---|---|---|
| Method 1: Data API with PRAW | Approved app, OAuth | Free if approved, contract for commercial | 100 queries/min per client | Public API closes March 2027 | Existing approved apps |
| Method 2: Logged-in session scraper | Your own account | A dedicated IP | One session, paced | While logged-in JSON lasts | Posts and full comment trees |
| Method 3: Logged-out browser scraper | None | Per-GB traffic, compute | Moderate, heavy pages | Until the front end changes | Public feeds without accounts |
| Method 4: AI agent over MCP | Your logged-in profile | Model tokens plus Method 2 | Low to moderate | Same as Method 2 | Research and ad hoc pulls |
| Method 5: Apify and hosted scrapers | Vendor's | $3.40-$4.00 per 1,000 results | Vendor-managed | Depends on vendor upkeep | Teams that won't maintain code |
Methods 2 and 4 run on the same kind of logged-in session, and Method 3 is the only one that needs no account at all.
Method 1: The Official Data API With PRAW
If you already hold an approved Data API app, PRAW is still the simplest way to pull posts and comments. Check the calendar before you build on it: Reddit takes no new public API access requests after October 31, 2026, unregistered apps start losing access after January 12, 2027, and public access ends for remaining apps in March 2027, apart from apps covered by written agreements.
Approved clients get 100 queries per minute per OAuth client ID, averaged over a 10-minute window, according to Reddit's Data API Wiki, and responses carry X-Ratelimit-Remaining and X-Ratelimit-Reset headers. Requests also need a descriptive user agent in the form platform:app-id:version (by /u/username), per the same wiki.
PRAW 8, current at 8.0.3 from August 2026, needs Python 3.10 or newer and makes time_filter keyword-only. Calling subreddit.top("week") the way older tutorials do now raises a TypeError.
1# pip install praw (PRAW 8 needs Python 3.10+)2import csv3 4import praw5 6FIELDS = ["post_id", "post_title", "comment_id", "parent_id", "author", "score", "body", "created_utc"]7 8reddit = praw.Reddit(9 client_id="YOUR_CLIENT_ID",10 client_secret="YOUR_CLIENT_SECRET",11 user_agent="script:reddit-research:v1.0 (by /u/your_username)",12)13 14rows = []15for post in reddit.subreddit("python").top(time_filter="week", limit=25):16 post.comments.replace_more(limit=0) # drop "load more" stubs; limit=None fetches them all17 for c in post.comments.list():18 rows.append({19 "post_id": post.id,20 "post_title": post.title,21 "comment_id": c.id,22 "parent_id": c.parent_id,23 "author": str(c.author) if c.author else "[deleted]",24 "score": c.score,25 "body": c.body,26 "created_utc": c.created_utc,27 })28 29with open("comments.csv", "w", newline="", encoding="utf-8") as f:30 writer = csv.DictWriter(f, fieldnames=FIELDS)31 writer.writeheader()32 writer.writerows(rows)33print(f"{len(rows)} comments saved")replace_more(limit=0) drops the load-more stubs and keeps the cost at one request per thread. Set limit=None to expand every stub, and expect each expansion to spend another request from your 100 per minute. A proxy adds nothing to this method: the limit counts per OAuth client ID, and the Data API Terms forbid masking the app's user agent or OAuth identity.
Method 2: A Logged-In Reddit Scraper on Your Own Account
Reddit's May announcement left logged-in access alone, so a browser session on a real account still receives the JSON the old tutorials used. This Reddit scraper works from inside that session. It opens reddit.com in a persistent Chrome profile and calls fetch() from the page, so every request carries the account's cookies, Chrome's own TLS fingerprint and the account's IP.
The account setup matters more than the code. Run the research account in its own browser profile on its own dedicated mobile proxy, a carrier IP only you use that stays fixed until you rotate it, so the session never changes address mid-run. If Reddit asks the account for a phone number, use a non-VoIP carrier number rather than a VoIP line. How Reddit ties bans to devices and IPs covers the account side in depth. Current prices for a Reddit number:
Reddit SMS number pricing
- One-time code (US number)
- from $0.15
- Rental (US number)
- from $1.25
- Dedicated monthly number
- from $16.99, multiple countries
Prices updated
Reddit's rules forbid scraping without consent, so the realistic downside here is a suspended account. Keep collection on an account that holds nothing you need.
1# pip install playwright && playwright install chrome2import json3import random4import sys5import time6 7from playwright.sync_api import sync_playwright8 9BASE = "https://www.reddit.com"10PROFILE = "profiles/reddit-research-01" # one profile per account; keeps the login11# This account's dedicated device: host, HTTP port and login from its proxy page12PROXY = {13 "server": "http://<device-host>:<http-port>",14 "username": "<device-username>",15 "password": "<device-password>",16}17 18# fetch() runs inside the logged-in page: same cookies, same Chrome, same IP as a person browsing19FETCH = """async (url) => {20const r = await fetch(url, {credentials: "include", headers: {"Accept": "application/json"}});21return {status: r.status, remaining: r.headers.get("x-ratelimit-remaining"), body: await r.text()};22}"""23 24 25def get_json(page, path):26 for attempt in range(3):27 res = page.evaluate(FETCH, BASE + path)28 if res["status"] == 200:29 if res["remaining"] and float(res["remaining"]) < 10:30 time.sleep(60) # close to the window's limit: let it reset31 return json.loads(res["body"])32 if res["status"] == 429:33 time.sleep(60 * (attempt + 1))34 continue35 raise RuntimeError(f"HTTP {res['status']} on {path} (403 usually means the session is logged out)")36 raise RuntimeError(f"Still rate limited on {path}")37 38 39def subreddit_posts(page, sub, sort="new", max_pages=5):40 posts, after = [], None41 for _ in range(max_pages): # 100 per page; Reddit stops a listing at about 1,000 items42 path = f"/r/{sub}/{sort}.json?limit=100&raw_json=1" + (f"&after={after}" if after else "")43 data = get_json(page, path)["data"]44 posts += [child["data"] for child in data["children"]]45 after = data["after"]46 if not after:47 break48 time.sleep(random.uniform(4, 8))49 return posts50 51 52with sync_playwright() as pw:53 ctx = pw.chromium.launch_persistent_context(54 PROFILE, channel="chrome", headless=False, proxy=PROXY,55 locale="en-US", timezone_id="America/New_York", # match the proxy's location56 )57 page = ctx.new_page()58 if "--login" in sys.argv:59 page.goto(BASE + "/login")60 input("Log in in the browser window, then press Enter here...")61 page.goto(BASE + "/") # fetch() needs a reddit.com page to run from62 63 keep = ["id", "title", "author", "score", "num_comments", "created_utc", "permalink", "url", "selftext"]64 posts = [{k: p.get(k) for k in keep} for p in subreddit_posts(page, "python")]65 with open("posts.json", "w", encoding="utf-8") as f:66 json.dump(posts, f, ensure_ascii=False, indent=2)67 print(f"{len(posts)} posts saved")68 ctx.close()Run it once with --login and sign in by hand in the window it opens. After that, the profile keeps the session. Each request returns up to 100 posts, and the after cursor fetches the next page. Listings stop at about 1,000 items, so for a busy subreddit, combine sorts (new, top of the week, top of the month) or run the script on a schedule instead of paging deeper.
Both the 4 to 8 second pause and the header check are deliberately conservative. Reddit publishes no limit for logged-in browsing, and the API's 100 per minute is the only figure on record.
How to Scrape Reddit Comments
A thread's JSON is a two-item array: the post, then a tree of comments (t1) with replies nested inside. Large threads cut the tree with two kinds of more stubs. Load-more stubs list comment IDs, which /api/morechildren expands, 100 IDs per call in this script. Continue-this-thread stubs carry no IDs and point at a parent comment, which you reopen as its own thread at /comments/POST_ID/_/COMMENT_ID.
Add this function above the with block:
1def post_comments(page, post_id):2 """Every comment on a thread as flat rows; parent_id rebuilds the tree."""3 _, top = get_json(page, f"/comments/{post_id}.json?limit=500&sort=top&raw_json=1")4 rows, more_ids, continue_from = {}, [], []5 6 def walk(things):7 for t in things:8 d = t["data"]9 if t["kind"] == "t1":10 rows[d["id"]] = {k: d.get(k) for k in ("id", "parent_id", "author", "body", "score", "created_utc")}11 if d.get("replies"):12 walk(d["replies"]["data"]["children"])13 elif t["kind"] == "more" and d["children"]:14 more_ids.extend(d["children"]) # "load more comments"15 elif t["kind"] == "more":16 continue_from.append(d["parent_id"]) # "continue this thread"17 18 walk(top["data"]["children"])19 while more_ids or continue_from:20 if more_ids:21 batch = more_ids[:100]22 del more_ids[:100]23 res = get_json(page, f"/api/morechildren.json?api_type=json&raw_json=1"24 f"&link_id=t3_{post_id}&children={','.join(batch)}")25 walk(res["json"]["data"]["things"])26 else:27 parent = continue_from.pop().split("_", 1)[1]28 _, branch = get_json(page, f"/comments/{post_id}/_/{parent}.json?raw_json=1")29 walk(branch["data"]["children"])30 time.sleep(random.uniform(3, 6))31 return list(rows.values())Call it with an ID from the first run, for example post_comments(page, posts[0]["id"]), inside the with block. Rows come back flat with parent_id, which rebuilds the tree in a few lines of pandas or SQL, and keying rows by ID drops the duplicate parent each continue-thread request returns.
Method 3: Scrape Reddit Logged Out With a Real Browser
Logged-out visitors can still read Reddit's public pages, but scripts that only send HTTP requests rarely get them. Plain requests can come back as HTTP 200 carrying a JavaScript challenge and no posts, or as Reddit's network security block page. In a real browser, the current front end stores each post's data as attributes on a shreddit-post element, so the scraper reads attributes instead of digging through markup. The script runs stock Chrome with its own user agent and solves no CAPTCHA. When Reddit answers with a challenge or a block page instead, no shreddit-post elements appear and the run stops.
Hold one IP for the whole browser session. The script below runs on a proxy list from VoidMob's rotating mobile proxies, set to Sticky or a 30-minute timer in the dashboard, since a list rotates on every request by default. For a new IP on the next run, let the timer roll over or regenerate the list's password. Blocking images, video and fonts keeps a per-GB plan from paying for media the scraper never reads.
1# pip install playwright && playwright install chrome2import json3 4from playwright.sync_api import sync_playwright5 6# A proxy list set to Sticky or a 30-minute timer: one exit IP for the whole run7PROXY = {8 "server": "http://proxy.voidmob.com:10000",9 "username": "<list-username>",10 "password": "<list-password>",11}12FIELDS = ["id", "post-title", "author", "score", "comment-count",13 "created-timestamp", "permalink", "subreddit-prefixed-name"]14READ = "(els, fields) => els.map(e => Object.fromEntries(fields.map(f => [f, e.getAttribute(f)])))"15 16 17def skip_heavy(route):18 # images, video and fonts are most of the bytes on a per-GB plan19 if route.request.resource_type in ("image", "media", "font"):20 return route.abort()21 return route.continue_()22 23 24with sync_playwright() as pw:25 browser = pw.chromium.launch(channel="chrome", headless=False, proxy=PROXY)26 page = browser.new_page(locale="en-US", timezone_id="America/New_York") # match the list's country27 page.route("**/*", skip_heavy)28 page.goto("https://www.reddit.com/r/python/new/", wait_until="domcontentloaded")29 page.wait_for_selector("shreddit-post", state="attached", timeout=30_000) # a challenge page has none30 31 posts = {}32 for _ in range(6):33 for p in page.eval_on_selector_all("shreddit-post", READ, FIELDS):34 posts[p["id"]] = p35 page.mouse.wheel(0, 4000)36 page.wait_for_timeout(2500)37 browser.close()38 39with open("reddit_feed.json", "w", encoding="utf-8") as f:40 json.dump(list(posts.values()), f, ensure_ascii=False, indent=2)41print(f"{len(posts)} posts saved")On the API, Flex mode does the same job with one login for the whole pool. Its credentials come from POST /v1/proxies/:id/flex_credentials, and a username such as <username>_c_US_s_run42_ttl_30m on proxy.voidmob.com:10092 holds one exit IP for the session ID run42 for up to 30 minutes, while a new session ID gets a new IP.
A 200 status is not a result
Reddit's challenge page answers with HTTP 200 and zero posts. Count a run as successful only when shreddit-post elements appear, or when your output file has rows.
Logged out, you see what any anonymous visitor sees: public subreddits and posts, with age-gated and some restricted communities hidden, and comment threads that load in stages. For comments in this mode, open the post page and read the shreddit-comment elements the same way through thingid, parentid and author. Selectors change when Reddit ships front-end updates, so the wait for shreddit-post doubles as a health check.
Method 4: Scrape Reddit With AI Agents Through MCP
An AI agent can scrape Reddit from a chat prompt once it has a browser it controls and an identity Reddit accepts. Microsoft's Playwright MCP server gives Claude Code, Cursor, Codex and other MCP clients a real browser. The VoidMob MCP server gives the same agent non-VoIP numbers for your account's verification codes and control of your mobile proxies, with per-order and per-session caps you set.
Point the browser at a persistent profile and the account's dedicated mobile IP, with the host, HTTP port and login from the device's proxy page:
1{2"browser": {3 "browserName": "chromium",4 "userDataDir": "/home/you/reddit-profiles/research-01",5 "launchOptions": {6 "channel": "chrome",7 "proxy": {8 "server": "http://<device-host>:<http-port>",9 "username": "<device-username>",10 "password": "<device-password>"11 }12 },13 "contextOptions": { "locale": "en-US", "timezoneId": "America/New_York" }14}15}Then register both servers, shown here for Claude Code:
1claude mcp add playwright -- npx @playwright/mcp@latest --config /home/you/reddit-browser.json2claude mcp add voidmob -s user -e VOIDMOB_API_KEY=<key> -- npx -y @voidmob/mcpLog into your Reddit account once in the window the agent opens, and the profile keeps the session. From then on, a prompt like this does the collection:
1Open reddit.com/r/python/new. Use browser_evaluate to fetch2/r/python/new.json?limit=100&raw_json=1 from inside the page and save the3result to python_new.json, then summarize the ten most discussed posts.4If Reddit answers with a 403 or a block page, stop and tell me.Fetching JSON from inside the page is far cheaper than asking the agent to read page snapshots, which spend tokens on every post, and the filename option of browser_evaluate keeps the raw data out of the model's context. For anything beyond a few hundred posts, have the agent write and run the Method 2 script instead.
On the VoidMob side, the agent reads the device's connection details with get_proxy_status and receives the account's verification code through rent_number and get_rental. Every purchase carries the price you approved, and the VOIDMOB_MAX_ORDER_CENTS and VOIDMOB_BUDGET_CENTS caps keep a looping agent from overspending.
The config matches Playwright MCP 0.0.83, the current release at publication. If a later version renames an option, the project README lists the current ones.
Method 5: Apify Reddit Scraper and Other Hosted Options
Searches for an Apify Reddit scraper usually land on Trudax's actors. Apify's store now marks the original Reddit Scraper as deprecated, and Reddit Scraper Lite is the maintained one, billed per result at $3.40 to $4.00 per 1,000 results depending on your Apify plan, as of October 2026. It scrapes without a login, takes start URLs plus caps on posts, comments, communities and users, and its examples run on Apify's residential proxies.
Hosted actors suit teams that won't maintain scraper code, at three costs. You get only what a logged-out visitor sees. Spend grows in line with results. You also inherit the vendor's methods and compliance posture, which matters now that Reddit sues data resellers.
Companies that need Reddit data at scale for products or model training have one route Reddit endorses: a commercial data agreement, the kind Google and OpenAI signed in 2024.
What Gets a Reddit Scraper Blocked
Reddit doesn't publish its detection signals, but its terms and block pages point at the usual ones: script user agents, datacenter and VPN addresses, several accounts on one IP, machine-speed request rates and clients that ignore 429 responses. A consistent setup addresses each one:
- One account, one profile, one IP. A dedicated mobile IP sits behind carrier NAT among ordinary phone users, and no other customer shares it.
- Real carrier numbers. If Reddit asks for a phone number, use a non-VoIP number, never a VoIP line.
- Human pace. Leave seconds between requests, and pause when
X-Ratelimit-Remainingruns low instead of retrying into 429s. - Matching locale. Set the browser's timezone and language to the IP's location, and confirm the match with a location consistency test.
- Sticky logged-out sessions. Keep one IP per browser run and switch between runs.
- Stop on 403. Any 403 means logged out or blocked, and retrying faster makes it worse.
If a person on your network gets Reddit's "You've been blocked by network security" page, the fix is Reddit's review form, not a proxy, as our guide to that block page explains. For settings by use case, from logged-out monitoring on a rotating pool to account work on dedicated devices, see the setup for mobile proxies for Reddit.
Real carrier IPs, one per session
Dedicated 4G/5G devices that keep one IP until you rotate it, a per-GB pool with sticky proxy lists, and non-VoIP numbers when a platform asks for one.
FAQ
Is scraping Reddit allowed?
Not under Reddit's own rules. Its User Agreement prohibits scraping without Reddit's prior written consent, robots.txt disallows all crawlers that lack an agreement, and the Responsible Builder Policy requires approval for API access and written approval for commercial use or AI training. Breaking those rules usually costs accounts and IP addresses, and legal exposure depends on how you access the data. This is not legal advice.
Does Reddit allow AI scraping?
Not without a deal. Reddit blocks unlicensed crawlers in robots.txt, forbids training models on Reddit data without written approval, licenses its data to companies such as Google and OpenAI, and has sued Anthropic and Perplexity over scraping. Its 2026 shutdowns of logged-out JSON, RSS and the public API were framed as measures against automated scraping.
Is scraping illegal in the USA?
Scraping publicly accessible pages is not a crime by itself in the US. Courts have read the federal anti-hacking law narrowly in cases such as hiQ v. LinkedIn. Risk comes from elsewhere: breach of contract once you log in and accept a site's terms, copyright and DMCA anti-circumvention claims, and privacy law when you collect personal data. Reddit's main DMCA claims against SerpApi and Perplexity survived a motion to dismiss in July 2026. This is not legal advice.
How to scrape data without getting banned?
Keep each session consistent and slow. Use a clean consumer IP, ideally a mobile carrier IP, with a real browser whose timezone and language match it. Keep a human pace, honor 429 and Retry-After responses and stop on 403. On Reddit, if the account is asked for a phone number, use a real carrier number rather than a VoIP line.
Can web scraping be detected?
Yes. Sites see the IP type and reputation, the TLS and browser fingerprint, the user agent, the request rate and whether the client runs JavaScript challenges. On Reddit, a script-style request can receive the network security block page or a challenge page with no posts. Real browsers on consumer IPs at human pace are much harder to separate from visitors, although logged-in activity always traces back to the account.
Can ChatGPT do web scraping?
ChatGPT can write a scraper and read individual pages with its browsing tools, but those requests come from OpenAI's servers and it is not built for bulk exports. For Reddit data at volume, connect an agent to a browser it controls through MCP, on your own logged-in profile and IP, or have the model write a script and run it yourself.
How do I scrape Reddit comments?
Request the thread as JSON from a logged-in session or the official API, walk the nested comment objects, and expand both kinds of more stubs: load-more stubs through /api/morechildren and continue-this-thread stubs by reopening the parent comment as its own thread. PRAW does the same with submission.comments.replace_more(limit=None). Keep parent_id on every row so you can rebuild the tree.
How much does the Apify Reddit scraper cost?
Trudax's Reddit Scraper Lite, the maintained Reddit actor on Apify, is billed per result at $3.40 to $4.00 per 1,000 results depending on the Apify plan, as of October 2026. Trudax's original Reddit Scraper is marked deprecated. Both scrape logged out, so they return only what an anonymous visitor can see.
Scraping Reddit in late 2026 runs on dates as much as on code. Approved API apps should register before January 12 and plan for March 2027. Everything else runs through a real browser: a logged-in account on its own carrier IP for depth, a sticky mobile session for public feeds, and agents that drive either one. Build the account and IP layer first, and the scrapers above become the easy part.
