Reddit User Profile Posts And Comments Scraper With Account Age
Pricing
$19.99/month + usage
Reddit User Profile Posts And Comments Scraper With Account Age
Scrape any Reddit user’s public posts and comments in one click. Ideal for academic research, sentiment analysis, or community behavior tracking. Clean, structured JSON output makes it easy to extract insights from Reddit user activity.
Pricing
$19.99/month + usage
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
0
Bookmarked
6
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
Reddit User Profile Scraper — Posts, Karma and Account Age
Reddit User Profile Posts And Comments Scraper With Account Age turns a list of Reddit usernames into structured JSON. Every row is one post the account submitted, carrying title, selftext, score, subreddit and permalink alongside an accountStats object with total karma, cake-day and a computed accountAgeDays. Set minKarma or minAccountAgeDays and low-quality accounts are dropped before their posts are ever written. No Reddit account, login or API key. Paste a username and press Start.
⚠️ "Comments" means replies on the user's posts, not the user's own comment history
The Actor reads Reddit's submitted posts feed for each account — /user/<name>/submitted. A Reddit account's own comment history lives on a separate feed that this Actor never requests. What maxComments does is fetch up to that many top-level comments on each collected post, written by whoever replied, and nest them in a comments array on the post row.
Three consequences worth knowing before your first run:
- Every dataset row is a post. The
typekey is"post"on every charged row. There is no row whosetypeis"comment". - The usernames inside
comments[].authorare other people, replying to your target — not the target account. - If you need an account's own comments as rows, this Actor does not produce them. Its account-side value is the karma, cake-day and age columns attached to the post rows.
What is Reddit User Profile Posts And Comments Scraper With Account Age?
It is an Apify Actor that collects a Reddit account's submitted posts and attaches account-level statistics — total, post and comment karma, the cake-day, a computed account age in days, karma-per-day, verified-email, gold and moderator flags, and follower count — to every post row. Posts arrive as flat typed rows with 114 keys; the account statistics arrive both as a nested accountStats object and as flat totalKarma, accountAgeDays and hasVerifiedEmail columns.
No Reddit account, login, OAuth app, API key or cookie is required. The Actor never authenticates and sends no session or age-confirmation cookie of any kind. The only credential involved is your Apify token, and only if you call the Actor through the API rather than the Console.
It is built for trust-and-safety and moderation teams vetting accounts before they act on them, lead researchers qualifying Reddit handles, and developers feeding account-scored Reddit history into dashboards or AI pipelines.
What Reddit user profile data is publicly available to scrape?
A logged-out visitor to a Reddit profile sees the account's submitted posts with their titles, bodies, scores, comment counts, subreddits, flair and timestamps, plus the sidebar's post karma, comment karma and cake-day. What that visitor never sees is the account's email address, its subscriptions, its votes or its private messages.
| Data category | Public on logged-out Reddit | Gated behind a Reddit login, or not returned |
|---|---|---|
| Submitted posts: title, body, score, comment count, timestamp, subreddit | ✅ Public | — |
| Post karma, comment karma and cake-day (account creation date) | ✅ Public | — |
| Verified-email, Reddit gold and moderator flags, follower count | ✅ Public in Reddit's about.json payload | Absent from the old.reddit.com sidebar fallback — returned as null when that fallback fires |
| Top-level comments on the account's posts | ✅ Public | — |
| The account's own comment history as rows | ✅ Public on Reddit | ❌ Not returned — this Actor reads the submitted-posts feed only |
| Posts older than the feed's reach | ❌ | Reddit exposes roughly the 1000 most recent items per feed, as the Actor's own input schema documents |
| Suspended, deleted or shadowbanned accounts | ❌ | Reddit serves no public history for them |
| Email address, IP, subscriptions, saved posts, votes, direct messages | ❌ | Behind a Reddit login — never returned |
Reddit User Profile Posts And Comments Scraper With Account Age only returns publicly visible data — what any visitor sees. Nothing behind a login wall.
What data can I extract with Reddit User Profile Posts And Comments Scraper With Account Age?
Each charged row is one submitted post carrying 114 keys: 107 canonical Reddit post fields in a fixed order, plus 7 fields this Actor computes or attaches. A comments array is added as a 115th key when maxComments is above 0. The nested accountStats object holds a further 15 keys, and each comment object 7 keys.
Account, karma and age fields
These seven keys are added by this Actor and are what separates it from a plain post scraper.
| Field Name | Description |
|---|---|
accountStats | Nested object with the full account picture — 15 keys, listed below. null for keyword: targets and whenever includeAccountStats is off |
totalKarma | Flat copy of accountStats.total_karma, for a usable CSV column. null when stats are unavailable |
accountAgeDays | Flat copy of accountStats.accountAgeDays — whole days between the cake-day and the moment of the run |
hasVerifiedEmail | Flat copy of accountStats.has_verified_email. null on sidebar-sourced stats, which do not carry it |
authorAccountSource | Which surface produced the statistics: "about_json" or "old_reddit_sidebar". null when neither worked. Every null inside accountStats is attributable through this key |
passedAccountFilter | Always true on a post row — accounts that fail minKarma or minAccountAgeDays never have their posts fetched. An audit echo |
publishedAt | The post's created_utc rendered as ISO-8601 UTC, YYYY-MM-DDTHH:MM:SSZ. null when created_utc is 0 or unparsable |
Inside accountStats:
| Field Name | Description |
|---|---|
username | The account handle as it was resolved from your input |
total_karma | Reddit's own total_karma on the about_json path. On the old_reddit_sidebar path Reddit publishes no total, so the Actor sums post and comment karma instead — read authorSource before comparing accounts |
link_karma | Post karma |
comment_karma | Comment karma |
account_created_utc | Cake-day as a Unix epoch in seconds, float. This is the stable value — see the note under accountAgeDays |
account_created_iso | The same cake-day as ISO-8601 UTC |
accountAgeDays | Computed by the Actor, not published by Reddit: (now − account_created_utc) // 86400, floored to whole days and evaluated at run time |
karmaPerDay | total_karma / accountAgeDays, rounded to 2 decimals. null when karma or age is unknown, and when the account is under one day old |
commentVsPostKarmaRatio | comment_karma / link_karma, rounded to 3 decimals. null when post karma is 0 or unknown — a high ratio marks a commenter, a low one a submitter |
has_verified_email | Reddit's has_verified_email. null on sidebar-sourced stats |
is_gold | Reddit Premium flag. null on sidebar-sourced stats |
is_mod | Moderator flag. null on sidebar-sourced stats |
followers | Subscriber count of the account's profile subreddit. null on sidebar-sourced stats |
iconImg | Avatar URL with the query string stripped. null on sidebar-sourced stats |
authorSource | "about_json" or "old_reddit_sidebar" — the same value the flat authorAccountSource column carries |
accountAgeDays and karmaPerDay are relative to when the run happened. They are computed against the clock at fetch time, so a row stored six months ago still reports the age it reported then, and that number is now six months wrong. account_created_utc and account_created_iso are the stable facts — recompute the age from those if it matters later.
Post identity, text and link fields
title and selftext are the two text fields worth indexing. selftext is the post body and selftext_html is Reddit's HTML rendering of the same text. permalink is the Reddit-relative path, url the post's target — the post itself for self posts, the outbound link for link posts — and domain reads self.<subreddit> for text posts. id is Reddit's base-36 post id and name the t3_-prefixed fullname. author and author_fullname are two views of the same person: the display handle, which can change, and Reddit's stable t2_ account id, which does not.
subreddit, subreddit_name_prefixed, subreddit_id, subreddit_type and subreddit_subscribers place the post in its community. Flair arrives as link_flair_text, link_flair_css_class, link_flair_richtext, link_flair_type, link_flair_text_color and link_flair_background_color, with the author's own flair in the matching author_flair_* keys.
Engagement, media and safety fields
score, ups, downs, num_comments, num_crossposts, upvote_ratio, total_awards_received and gilded are the numbers. Reddit fuzzes vote counts, so score is a point-in-time snapshot taken at fetch rather than a fixed property of the post. Media and preview data comes through thumbnail, thumbnail_width, thumbnail_height, preview, media, secure_media, media_embed, secure_media_embed, post_hint and is_video. Safety and state flags are over_18, spoiler, locked, archived, stickied, pinned, quarantine, distinguished, edited, is_self, is_original_content and is_robot_indexable, with removal context in removed_by, removed_by_category, removal_reason, banned_by and banned_at_utc.
Each object inside comments has exactly seven keys — id, author, body, score, created_utc, permalink and replies_count. replies_count is a count only; the reply bodies themselves are not returned.
The rest of the canonical row
The remaining canonical keys are Reddit's own and pass through untouched on the JSON path. Several of them are meaningful only to a logged-in session and will read as their documented default on every logged-out row:
type, approved_at_utc, saved, mod_reason_title, clicked, hidden, pwls, wls, top_awarded_type, hide_score, category, content_categories, can_mod_post, approved_by, is_created_from_ads_ui, author_premium, author_patreon_flair, gildings, mod_note, allow_live_comments, likes, suggested_sort, view_count, no_follow, is_crosspostable, all_awardings, awarders, media_only, can_gild, treatment_tags, visited, num_reports, report_reasons, user_reports, mod_reports, mod_reason_by, author_is_blocked, is_reddit_media_domain, is_meta, discussion_type, send_replies, contest_mode, created.
On the old.reddit.com HTML fallback path only 34 of the 107 canonical keys are filled from the page markup — the identity, text, link, subreddit, score, comment-count, timestamp, thumbnail, flair, preview and crosspost keys. Every other key takes its default, so upvote_ratio reads null, subreddit_subscribers reads 0 and all_awardings reads [] on those rows. authorAccountSource describes the stats surface, not the post surface, so it will not tell you which path a post came from; check whether upvote_ratio is null on a row you expected it on.
🤖 Add-on: Need additional Reddit data?
This Actor comes at Reddit from the account side. When you want the topic side instead, Reddit Posts Scraper By Keyword & Score Filter pulls posts from subreddits and search terms with a keyword watchlist and a score floor, and returns nested comment trees with replies. Reddit Trends Scraper By Sort, Time & Score Filter answers "what is climbing right now" from listing pages. A useful pairing is to run one of those first, then feed the author values it returns into this Actor's startUrls to score the people behind the posts.
How does Reddit User Profile Posts And Comments Scraper With Account Age differ from the official Reddit Data API?
Reddit does publish an official Data API, and it is the supported route: it returns objects no public page renders, and it comes with terms you can point at. It also requires you to register an application, hold credentials, and stay inside terms that treat commercial and high-volume use differently from personal use. This Actor reads the same public surfaces a logged-out visitor sees and adds the account scoring on top.
| Feature | Official Reddit Data API | This Actor |
|---|---|---|
| Credentials | Registered app plus OAuth credentials — verify against Reddit's current published API terms | ✅ None. Reads public .json and old.reddit.com pages anonymously |
| Rate and commercial terms | Set and enforced by Reddit per credential, and revised over time — verify against Reddit's current published API terms | Not credential-based. The Actor paces itself between requests and rotates proxies when blocked |
| Account karma and cake-day | Available as a user object you fetch and join yourself | ✅ Fetched automatically and attached to every post row, flat and nested |
| Account age in days | ❌ Not a published field — the API gives you the creation timestamp | ✅ Computed as accountAgeDays, with karmaPerDay and commentVsPostKarmaRatio derived alongside |
| Karma and age vetting | You fetch everything, then filter client-side | ✅ minKarma and minAccountAgeDays run before any post is fetched, so a rejected account costs no row events |
| A user's own comment history | Available through the API | ❌ Not returned — this Actor reads the submitted-posts feed only |
| Where it runs | Your infrastructure, your IP addresses, your retry logic | ✅ Apify, with the proxy ladder, browser impersonation and retries handled |
Use the official API when you need write access, authenticated endpoints, a user's comment history, or a commercial arrangement Reddit has approved. Use this Actor when you want vetted, account-scored post history without registering an application.
How to use Reddit User Profile Posts And Comments Scraper With Account Age
The Actor runs on Apify. Start it from the Apify Console or call it through the Apify API. No Reddit credentials are involved at any point.
- Open the Actor on Apify and click Try for free
- Fill 🧑 Reddit Users to Vet (
startUrls) — this is the one required input. One entry per line: a bare username (spez), au/handle (u/spez), a full profile URL (https://www.reddit.com/user/spez), orkeyword:pythonto run a Reddit search instead of a profile - Leave 🏅 Attach Account & Karma Stats (
includeAccountStats) on to get theaccountStatsobject and the flat karma and age columns - Optionally set 🎯 Minimum Total Karma (
minKarma) and 📆 Minimum Account Age (days) (minAccountAgeDays) to drop throwaway accounts before their posts are collected - Choose 🗂️ Post Sort (
sortOrder) and 🔢 Posts per Account (maxPosts), and set 🗨️ Comments per Post (maxComments) above0if you want replies nested on each post - Click Start, then export as JSON, CSV or Excel, or read the dataset through the Apify API
A run submitted with an empty or missing startUrls does not fall back to a default account. The Actor logs Input 'startUrls' is required. Add at least one username, profile URL, or 'keyword:term'. and exits immediately as a successful run with zero rows and nothing charged — it is not a failed run, so check the item count rather than the run status.
How to scale to bulk Reddit account extraction
startUrls is a list, so bulk is the normal mode — put a hundred handles in one run and they are processed one after another, each independently. maxPosts is per account, not per run: fifty accounts at maxPosts: 25 is a run that can write up to 1,250 rows.
There is no deduplication anywhere in this Actor. The same handle listed twice is scraped twice and charged twice, and a post surfaced by both a keyword: target and a user target is written twice. Deduplicate on id after the run if that matters.
What can you do with Reddit user profile data?
- 🛡️ A trust-and-safety analyst triaging reports runs the reported handles and reads
accountAgeDays,karmaPerDayandcommentVsPostKarmaRatioto separate a long-standing commenter from an account created last week. - 🎯 A community lead recruiting moderators sets
minKarma: 5000andminAccountAgeDays: 365, then groups the surviving rows bysubredditto find people already active where they need help. - 📊 A market researcher profiling a niche pulls each account's
sortOrder: "top"posts and rankssubredditby summedscoreto map where a person's influence actually sits. - 💼 A lead researcher qualifying Reddit handles reads
totalKarma,hasVerifiedEmailandfollowersto drop unusable accounts before anyone spends time on outreach. - 🤖 An AI engineer building an account-reputation tool indexes
titleandselftextinto a vector store withsubreddit,score,publishedAt,totalKarmaandaccountAgeDaysas metadata filters, so the agent can weight what an account says by how established it is.
Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.
How does Reddit User Profile Posts And Comments Scraper With Account Age handle rate limits and blocking?
Requests go out through impit with a real Chrome TLS and HTTP/2 fingerprint, which is the reason this Actor can read Reddit's .json surface at all — plain HTTP clients are filtered at the edge before Reddit ever looks at the request. No headless browser is launched.
Egress runs on a short proxy ladder that is residential-first, not cheapest-first. With the default settings the only tier is Apify residential, and each request gets up to three attempts on it, minting a fresh proxy URL — and therefore a fresh residential IP — between attempts. If you supply your own proxy, either proxyUrls or an Apify group other than RESIDENTIAL, that tier is tried first for three attempts and residential becomes the fallback. Each tier is smoke-tested before the run commits to it; if none can mint a URL the Actor logs a warning and runs direct, where Reddit is likely to block it.
A response counts as blocked on HTTP 403, 429 or 503, and each is handled differently: a 429 sleeps 12 to 18 seconds before rotating the client, a 403 or 503 sleeps half a second to a second and a half. Any other non-200 status is treated as final and returns nothing. The request timeout is 20 seconds. The Actor also detects a silent block — HTTP 200 with an empty payload, which is what Reddit serves a flagged IP — and force-escalates to the next tier rather than recording "this account has no posts"; with the single default tier there is no next rung, so the run falls through to the old.reddit.com HTML transport instead.
There is no CAPTCHA solving in this Actor, and none is claimed. If every tier fails on the first page the Actor switches transports; if that also fails it logs the failure, writes nothing for that account and moves to the next one.
⬇️ Input
Nine parameters. Only startUrls is required — everything else has a working default, so the minimum viable input is a single username.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
startUrls | Yes | array | Reddit accounts to scrape and score, one per line. A bare username (spez), a u/ handle (u/spez), a full profile URL (https://www.reddit.com/user/spez), or keyword:term for a Reddit search. Karma stats apply to user targets only. | ["spez", "u/kn0thing", "keyword:python"] |
includeAccountStats | No | boolean | Attach the accountStats object plus the flat totalKarma, accountAgeDays and hasVerifiedEmail columns to every post row. Default true. | true |
minKarma | No | integer | Drop accounts whose total karma is below this number. Minimum 0. Default 0, which disables the karma filter. | 500 |
minAccountAgeDays | No | integer | Drop accounts younger than this many days, measured from the cake-day. Minimum 0. Default 0, which disables the age filter. | 90 |
sortOrder | No | string | Order in which an account's submitted posts are collected. One of new, hot, top, controversial. Default new. | top |
maxPosts | No | integer | Posts to collect per account or per keyword search. Minimum 1, maximum 1000. Default 25. | 100 |
fetchSelftext | No | boolean | Fetch each post's own thread JSON to merge in fields the listing omits — full untruncated selftext, preview, subreddit_subscribers, video media. Adds one request per post. Default true. | true |
maxComments | No | integer | Top-level comments to attach to each post in a comments array. Minimum 0, maximum 100. Set 0 to skip comment fetching. Default 0. | 10 |
proxyConfiguration | No | object | Apify proxy configuration. Prefilled {"useApifyProxy": false}. A custom proxy is tried first, with residential as the fallback. | {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]} |
Eight honest notes on how these actually behave:
- A bare word is always a username here. There is no subreddit inference and no search inference:
technologyis read as the accountu/technology, not asr/technologyand not as a search.keyword:is the only way to run a search. Both sibling Reddit Actors use different conventions for a bare word, so do not carry the habit across. Anything that is not akeyword:prefix and not a recognisable Reddit URL becomes a user target, including multi-word phrases — which will simply return nothing. - A profile URL is parsed as its last path segment.
https://www.reddit.com/user/spezandhttps://www.reddit.com/user/spez/both resolve tospez, but a deeper link such ashttps://www.reddit.com/user/spez/comments/resolves tocommentsand goes looking for an account by that name. Paste the bare profile URL, not a section of it. Usernames beginning withuare handled correctly — theu/prefix is removed by position rather than by character stripping, sou/unidanresolves tounidan, notnidan. - ⚠️
maxPostsandmaxCommentsare bounded by the Console, not by the code. The 1–1000 and 0–100 ranges are enforced by the input schema when you use the Console form. The Actor itself does not clamp them, so an API caller can sendmaxPosts: 5000and the pagination loop will keep going until Reddit stops offering a next page.sortOrderis not validated either — an out-of-enum value is passed straight through to Reddit in the query string. keyword:terms are not URL-encoded. The term goes into Reddit's search query string exactly as you typed it, so spaces,&and#are not escaped. Join words with+—keyword:machine+learning— or use a single word.minKarmaandminAccountAgeDaysare ignored forkeyword:targets. They apply to user targets only. A keyword target is never filtered out, itsaccountStatsisnull, and itspassedAccountFilteristrue.includeAccountStats: falsedoes not always skip the stats request. When either filter is active the Actor still fetchesabout.json, because it needs the numbers to run the filter — it just leavesaccountStats,totalKarma,accountAgeDays,hasVerifiedEmailandauthorAccountSourceasnullon the rows. Only with both filters at0andincludeAccountStats: falseis the stats request skipped entirely.- The filters fail closed. If karma or the cake-day cannot be read at all, an account with an active filter is skipped rather than kept, with
filterReasonset toaccount_stats_unavailable,total_karma_unknownoraccount_age_unknown. A vetting run never silently keeps an account it could not verify. - The prefilled
{"useApifyProxy": false}still routes through Apify residential. Unlike the sibling Reddit Actors, this one has no direct or datacenter rung by design — residential is the baseline tier, and the prefill only means "no custom proxy of mine". Direct is reached solely as an emergency when no proxy tier can be initialised at all, and it is logged as a warning.
Example input
{"startUrls": ["spez","u/kn0thing","https://www.reddit.com/user/GallowBoob","keyword:self+hosted"],"includeAccountStats": true,"minKarma": 500,"minAccountAgeDays": 90,"sortOrder": "top","maxPosts": 50,"fetchSelftext": true,"maxComments": 5,"proxyConfiguration": {"useApifyProxy": true,"apifyProxyGroups": ["RESIDENTIAL"]}}
⬆️ Output
Typed, normalized JSON with the same key names in the same order on every run. Rows stream into the dataset as each post is built, so the table fills while the run is still going and a run that stops early keeps everything it already collected. Export as JSON, CSV or Excel, or read the dataset through the Apify API.
Keys are never omitted from a post row. Every charged row carries all 114 keys, filled from Reddit's payload where available and from a documented default where not — a missing value arrives as null, 0, false, "", [] or {} depending on the field, never as an absent key. The single exception is comments, which is added only when maxComments is above 0.
Filtered-out accounts are written as uncharged rows. When an account fails minKarma or minAccountAgeDays, the Actor writes a short accounting row with no charged event attached — it costs nothing — so a rejected account stays distinguishable from an account with no posts. The verified marker is the type field: charged post rows always carry "type": "post", uncharged accounting rows carry "type": "account_skipped". There is no success, isError, errorReason, status, isDiagnostic, rowType, fetchOutcome or charged field anywhere in this Actor — type is the one to filter on:
posts = [r for r in items if r.get("type") == "post"]skipped = [r for r in items if r.get("type") == "account_skipped"]
An account_skipped row carries exactly nine keys — type, isChild, username, passedAccountFilter, filterReason, minKarma, minAccountAgeDays, accountStats and authorAccountSource — where filterReason is one of account_stats_unavailable, total_karma_unknown, account_age_unknown, or a formatted string such as total_karma 118 < minKarma 500. These rows are written only when a filter is active. An account that is suspended, deleted or simply has no posts, in a run with both filters at 0, writes no row of any kind and appears only in the run log.
Example output
One post row, from a user target with account stats and comments on. Keys appear in this order on every row:
{"type": "post","approved_at_utc": null,"subreddit": "selfhosted","selftext": "Six months ago I moved everything off a rented VPS onto a mini PC in a cupboard. Total spend was 340 EUR up front and about 4 EUR a month in electricity. Here is the full parts list, the services I run, and the two things I would do differently.","author_fullname": "t2_9k4mzq1x","saved": false,"mod_reason_title": null,"gilded": 0,"clicked": false,"title": "Six months running my whole stack on a mini PC — full cost breakdown","link_flair_richtext": [],"subreddit_name_prefixed": "r/selfhosted","hidden": false,"pwls": 6,"link_flair_css_class": "guide","downs": 0,"thumbnail_height": 78,"top_awarded_type": null,"hide_score": false,"name": "t3_1n4qk2x","quarantine": false,"link_flair_text_color": "dark","upvote_ratio": 0.97,"author_flair_background_color": null,"subreddit_type": "public","ups": 4812,"total_awards_received": 0,"media_embed": {},"thumbnail_width": 140,"author_flair_template_id": null,"is_original_content": false,"user_reports": [],"secure_media": null,"is_reddit_media_domain": false,"is_meta": false,"category": null,"secure_media_embed": {},"link_flair_text": "Guide","can_mod_post": false,"score": 4812,"approved_by": null,"is_created_from_ads_ui": false,"author_premium": false,"thumbnail": "self","edited": false,"author_flair_css_class": null,"author_flair_richtext": [],"gildings": {},"post_hint": "self","content_categories": null,"is_self": true,"mod_note": null,"created": 1753372841.0,"link_flair_type": "text","wls": 6,"removed_by_category": null,"banned_by": null,"author_flair_type": "text","domain": "self.selfhosted","allow_live_comments": false,"selftext_html": "<!-- SC_OFF --><div class=\"md\"><p>Six months ago I moved everything off a rented VPS onto a mini PC in a cupboard.</p></div><!-- SC_ON -->","likes": null,"suggested_sort": null,"banned_at_utc": null,"view_count": null,"archived": false,"no_follow": false,"is_crosspostable": true,"pinned": false,"over_18": false,"preview": {"images": [],"enabled": false},"all_awardings": [],"awarders": [],"media_only": false,"can_gild": false,"spoiler": false,"locked": false,"author_flair_text": null,"treatment_tags": [],"visited": false,"removed_by": null,"num_reports": null,"distinguished": null,"subreddit_id": "t5_2rzsg","author_is_blocked": false,"mod_reason_by": null,"removal_reason": null,"link_flair_background_color": "","id": "1n4qk2x","is_robot_indexable": true,"report_reasons": null,"author": "cupboard_ops","discussion_type": null,"num_comments": 418,"send_replies": true,"contest_mode": false,"mod_reports": [],"author_patreon_flair": false,"author_flair_text_color": null,"permalink": "/r/selfhosted/comments/1n4qk2x/six_months_running_my_whole_stack_on_a_mini_pc/","stickied": false,"url": "https://www.reddit.com/r/selfhosted/comments/1n4qk2x/six_months_running_my_whole_stack_on_a_mini_pc/","subreddit_subscribers": 512847,"created_utc": 1753372841.0,"num_crossposts": 3,"media": null,"is_video": false,"comments": [{"id": "n8rk4qz","author": "rack_and_stack","body": "The electricity number is the one people always leave out. Four euros a month is roughly what a single VPS costs before you add backups.","score": 291,"created_utc": 1753375102.0,"permalink": "/r/selfhosted/comments/1n4qk2x/six_months_running_my_whole_stack_on_a_mini_pc/n8rk4qz/","replies_count": 7},{"id": "n8rm18d","author": "[deleted]","body": "[removed]","score": 12,"created_utc": 1753377455.0,"permalink": "/r/selfhosted/comments/1n4qk2x/six_months_running_my_whole_stack_on_a_mini_pc/n8rm18d/","replies_count": 0}],"publishedAt": "2026-07-24T15:20:41Z","accountStats": {"username": "cupboard_ops","total_karma": 38417,"link_karma": 9204,"comment_karma": 29213,"account_created_utc": 1462104000.0,"account_created_iso": "2016-05-01T12:00:00Z","accountAgeDays": 3737,"karmaPerDay": 10.28,"commentVsPostKarmaRatio": 3.174,"has_verified_email": true,"is_gold": false,"is_mod": true,"followers": 1284,"iconImg": "https://styles.redditmedia.com/t5_9k4mzq1x/styles/profileIcon_a7f2c1.png","authorSource": "about_json"},"totalKarma": 38417,"accountAgeDays": 3737,"hasVerifiedEmail": true,"authorAccountSource": "about_json","passedAccountFilter": true}
And one uncharged accounting row from the same run, for an account that failed minKarma: 500:
{"type": "account_skipped","isChild": false,"username": "throwaway_2026_07","passedAccountFilter": false,"filterReason": "total_karma 118 < minKarma 500","minKarma": 500,"minAccountAgeDays": 90,"accountStats": {"username": "throwaway_2026_07","total_karma": 118,"link_karma": 41,"comment_karma": 77,"account_created_utc": 1750896000.0,"account_created_iso": "2026-06-26T00:00:00Z","accountAgeDays": 29,"karmaPerDay": 4.07,"commentVsPostKarmaRatio": 1.878,"has_verified_email": false,"is_gold": false,"is_mod": false,"followers": 0,"iconImg": null,"authorSource": "about_json"},"authorAccountSource": "about_json"}
How does it work?
Each entry in startUrls is classified as a user target or a keyword: search. For a user target the Actor first requests www.reddit.com/user/<name>/about.json for the account statistics, falling back to parsing the old.reddit.com profile sidebar for post karma, comment karma and the cake-day when about.json is walled — authorAccountSource records which one fired. If a karma or age threshold is set, the account is scored before any post is fetched, and a failing account is written as an uncharged account_skipped row.
Posts then come from www.reddit.com/user/<name>/submitted.json, paged through Reddit's after= cursor at up to 100 items per request, with raw_json=1 appended so text arrives unescaped. Requests go out over impit with Chrome's TLS and HTTP/2 fingerprint through an Apify residential proxy — no browser, no login, no cookie. Only when the first page fails on every proxy tier does the Actor switch to reading old.reddit.com listing HTML with lxml, parsed from Reddit's own data-* post attributes. With fetchSelftext on, each post's own thread JSON is fetched to merge in the untruncated body and the fields listings omit, and each row is written to the dataset the moment it is built. Because every row is shaped against a fixed 107-key field list, your key names and their order stay identical run to run regardless of what Reddit changes on the front end.
Integrations
This is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset — your own code, scheduled runs, webhooks, or a no-code automation platform.
Calling the Actor from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/reddit-user-profile-posts-and-comments-scraper-with-account-age").call(run_input={"startUrls": ["spez", "u/kn0thing"],"includeAccountStats": True,"minKarma": 500,"minAccountAgeDays": 90,"sortOrder": "top","maxPosts": 50,"maxComments": 5,"proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]},})for row in client.dataset(run["defaultDatasetId"]).iterate_items():if row.get("type") == "post":print(row["author"], row["totalKarma"], row["accountAgeDays"], row["score"], row["title"])
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Filter on type to separate charged post rows from uncharged account_skipped rows.
No-code tools (n8n, Make, LangChain)
In n8n, use the Apify node — or an HTTP Request node pointed at the Apify run endpoint with your token — and pass the same JSON input shown above; a Filter node on type drops the accounting rows before an IF node on totalKarma routes qualified accounts to Slack or a database. In Make, the Apify module supports run-and-wait, so a weekly re-scoring of a handle watchlist can feed a Google Sheets or Airtable step directly. In LangChain, wrap the run endpoint as a tool and hand title and selftext to the model as content, with totalKarma and accountAgeDays as reputation metadata. Apify schedules and webhooks handle recurrence and completion triggers.
Is it legal to scrape Reddit user profiles?
Scraping publicly accessible pages is broadly treated as permissible where no authentication is bypassed, and this Actor reads only what a logged-out visitor sees — no account, no login, no session, no cookie. Reddit's terms of service still govern automated access to and reuse of its site, and the post text you collect is user-generated writing its authors own.
This Actor produces personal data, and the pseudonymity does not exempt it. Under GDPR and UK GDPR a pseudonymous identifier that consistently picks out the same individual across records is personal data, and Reddit's identifiers are unusually stable. The fields concerned are author and author_fullname on every post row, author inside every object in comments, username on account_skipped rows, and the whole accountStats object — total_karma, link_karma, comment_karma, account_created_utc, account_created_iso, accountAgeDays, karmaPerDay, commentVsPostKarmaRatio, has_verified_email, is_gold, is_mod, followers and iconImg — together with the flat totalKarma, accountAgeDays and hasVerifiedEmail columns. has_verified_email and iconImg are account and identity attributes rather than content.
Post bodies routinely reveal political opinions, religious beliefs, health conditions, trade-union membership and sexual orientation, which are special categories under GDPR Article 9 and carry a higher bar than a lawful basis alone. One factual point about scale: this Actor assembles one identifiable person's posting history, the communities they choose, the hours they post and their account's full karma and age profile into a single behavioural record. That is a materially more sensitive artefact than reading the same posts spread across a subreddit, and it is what makes an account-side scrape a different compliance question from a topic-side one.
Practical steps that follow from this:
- Controller status. If you decide why and how these rows are collected, you are the controller for them under GDPR and UK GDPR. Apify and this Actor are the processing route, not the decision-maker.
- Lawful basis. Decide yours before you store rows. Legitimate interest is the usual route for research, moderation and trust-and-safety work, and it requires a documented legitimate-interest balancing test weighing your purpose against the reasonable expectations of a pseudonymous poster. Article 9 data needs a separate condition on top; if you cannot identify one, do not retain the posts that carry it.
- Article 14 transparency. Because the data comes from Reddit rather than from the person, the notification duty for indirectly collected personal data applies, along with its exemptions. Record which one you are relying on.
- Minimisation. Collect only the accounts and the fields you need. For reputation signals without content, keep
accountStatsand dropselftextandcommentsat ingestion. For content without reputation, setincludeAccountStats: falseandmaxComments: 0. - Retention. Set a deletion date. Reddit users delete posts and accounts, and
accountAgeDaysandkarmaPerDayare computed at run time, so a stored row drifts out of accuracy as well as out of compliance. - Subject requests. Keep
author,idandpermalinkso you can locate and delete a specific person's records when asked. Under CCPA the same rows are personal information about California residents, with access and deletion rights attached.
Consult legal counsel if your use case involves bulk storage of personal data, profiling of identifiable individuals, or republication of collected post text.
❓ Frequently asked questions
What Reddit user profile fields does this Actor return?
The five you will use most are title, selftext, score, totalKarma and accountAgeDays. Every charged row also carries author, author_fullname, subreddit, permalink, publishedAt, num_comments, upvote_ratio, hasVerifiedEmail, authorAccountSource and the 15-key accountStats object — 114 keys in total, plus 7 more inside each nested comment object. See the data fields section above for the full list.
Does this Actor return a user's own comments as rows?
No. It reads Reddit's submitted-posts feed, so every row is a post the account submitted and type is "post" on all of them. The comments array holds up to maxComments top-level comments on each collected post, written by whoever replied — not by your target account. If you need an account's own comment history as separate rows, this Actor does not produce it.
Does this Actor require a Reddit account, login or API key?
No. There is no OAuth flow, no registered app, no client id, no password and no cookie anywhere in the Actor. It reads Reddit's public .json endpoints and, when those are blocked, old.reddit.com HTML — both anonymously. The only credential involved is your Apify token, and only if you call the Actor through the API rather than the Console.
Is accountAgeDays a Reddit field or something this Actor calculates?
It is calculated by the Actor. Reddit publishes the account's creation timestamp — the cake-day — and the Actor subtracts it from the current time and floors the result to whole days: (now − account_created_utc) // 86400. The number is therefore relative to when the run happened. A row stored last year still reports last year's age. account_created_utc and account_created_iso are the stable values; recompute from those if you need the age as of today. karmaPerDay is derived from the same computed age and goes stale in the same way.
How many posts can I extract per Reddit account in one run?
maxPosts accepts 1 to 1000 through the Console and applies per account — twenty accounts at maxPosts: 100 is a run that can write up to 2,000 rows. Whether you reach the number depends on Reddit: the Actor pages through the submitted feed 100 items at a time following Reddit's after= cursor and stops when the cursor runs out, and the Actor's own input schema notes that Reddit exposes roughly the 1000 most recent items per feed, so older history is unreachable. The 1–1000 bound is enforced by the input schema, not by the Actor's code.
What happens if an account is suspended, deleted, shadowbanned or has no public history?
All four end the same way for your dataset: zero post rows and zero charge for that account, with the detail in the run log.
The statistics request comes back empty, the Actor logs account stats unavailable for u/<name> (about.json + sidebar both blocked), and the submitted feed returns no children — which the Actor first treats as a possible silent block, escalating a proxy tier and then falling through to the old.reddit.com transport before giving up and logging Completed user '<name>' — 0 posts saved. Whether you also get a row depends on your filters. With minKarma or minAccountAgeDays set, the account fails closed and you get one uncharged account_skipped row whose filterReason reads account_stats_unavailable — that is your machine-readable signal. With both filters at 0, nothing is written at all and the account is invisible in the dataset; check the log, or set a filter above 0 purely to get the accounting row.
Can I scrape multiple Reddit accounts at once?
Yes. startUrls is a list and mixes formats freely — bare usernames, u/ handles, profile URLs and keyword: searches in the same run, processed one target after another. Undocumented but accepted: entries may also be {"url": "spez"} objects rather than plain strings, so output from another tool usually drops in unchanged. There is no deduplication — a handle listed twice is scraped and charged twice.
Does this Actor work with Claude, ChatGPT and other AI agent tools?
Yes. It is callable as a standard HTTP-triggered run through the Apify API, so LangChain, CrewAI, n8n or a hand-written tool definition can invoke it and receive typed JSON with no parsing step. Give the agent title and selftext as the content and subreddit, score, publishedAt, totalKarma and accountAgeDays as the metadata it can reason over.
How does this Actor compare to other Reddit user scrapers?
Checked on the Apify Store on 25 July 2026: louisdeconinck/reddit-user-profile-posts-scraper documents scraping both posts and comments from a profile as separate rows, distinguished by a type field reading post or comment, with a full canonical Reddit field list for each; its documented input is Reddit user profile URLs, and it documents no karma, cake-day or account-age fields and no account filtering. simpleapi/reddit-user-profile-posts-and-comments-scraper documents the same submitted-posts-plus-nested-comments shape as this Actor, reading old.reddit.com with a direct → datacenter → residential proxy escalation, maxPosts defaulting to 100 with a maximum of 500, and a dataset view of author, title, subreddit, score, num_comments, permalink, created_utc, is_self and selftext; it documents no karma or account-age fields. signalengine/reddit-user-scraper documents reading public RSS activity feeds with no login, API key or proxy, returning one record per user with an items array of posts and comments plus an inferred company website; it states plainly that the feed exposes no upvote, comment or karma counts and that the actor therefore does not return them.
What this Actor documents explicitly against that field: account karma, cake-day and a computed accountAgeDays, karmaPerDay and commentVsPostKarmaRatio attached to every post row; minKarma and minAccountAgeDays vetting that runs before posts are fetched, with rejected accounts written as uncharged account_skipped rows; a recorded stats provenance in authorAccountSource; and a Chrome-fingerprinted .json transport with an old.reddit.com HTML fallback. It is equally explicit about what it does not return — no comment history as rows.
Does this Actor return data in a format LLMs can use directly?
Yes. Typed, normalized JSON with consistent key names in a fixed order across runs, counters already coerced to integers, and no key ever silently absent from a post row. No HTML parsing, no selectors. Pass a row directly into an LLM, index it into a vector store, or hand it to an agent tool — just strip the account_skipped rows first by filtering on type.
What happens when Reddit changes its layout or anti-bot system?
The scraper is maintained, and the output schema is built to survive front-end change: every row is shaped against a fixed 107-key field list with a documented default for each key, so a field Reddit stops publishing arrives as its default rather than breaking the row. The two transports back each other up — when the .json surface is walled the Actor reads old.reddit.com HTML from Reddit's own data-* post attributes rather than from styling classes, so a restyle degrades individual fields instead of the run. Your field names, their order and their types stay the same on your end.
Can I use this Actor without managing proxies or browser infrastructure?
Yes. The Chrome TLS impersonation, the retries, the IP rotation and the transport fallback are all handled for you. You never create a proxy account, rotate an IP or run a browser — no headless browser is launched at all. Leaving proxyConfiguration at its prefilled {"useApifyProxy": false} still routes the run through Apify residential, which is this Actor's baseline tier; the setting only decides whether a custom proxy of yours is tried first. The Actor does not solve CAPTCHAs, and it makes no claim to.
Which Reddit user profile fields work best for AI training data and RAG indexing?
For RAG indexing: title and selftext carry the most information per record and chunk cleanly, with the body text inside each comments object adding the community's response; use subreddit, link_flair_text and authorAccountSource as metadata filters and url as the citation. For training data and for any reputation model, the most structurally consistent fields across records are score, num_comments, upvote_ratio, created_utc, totalKarma, accountAgeDays, karmaPerDay and commentVsPostKarmaRatio — all typed primitives, nothing to parse. Two things to account for: score is a fuzzed point-in-time number rather than a fixed property of the post, and accountAgeDays and karmaPerDay are computed at run time, so store account_created_utc alongside them if the age will ever be recomputed.
🔗 Related scrapers
| Scraper Name | What it extracts |
|---|---|
| Reddit Posts Scraper By Keyword & Score Filter | Reddit posts from subreddits and searches, filtered by keyword and score, with nested comment trees |
| Reddit Trends Scraper By Sort, Time & Score Filter | Trending Reddit posts by sort mode, time window and score threshold |
| Quora Search Scraper | Quora questions, answers, upvotes and author credentials |
| Threads User Posts Scraper By Keyword & Date Filter | Threads posts for a user, filtered by keyword and date |
| X (Twitter) Posts Search | Public posts matching a search query |
| TikTok Profile Scraper | TikTok creator profiles with follower and engagement metrics |
💬 Your feedback
Found a bug, or need a field that is on the Reddit profile but not in the output? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the username you ran are the fastest to reproduce and fix.