Reddit threads have something most AI training datasets lack: a built-in human approval score (upvotes and downvotes) from niche communities. Instead of scraping random web chatter, you're getting feedback from subreddits filled with people who actively care about the topic.
A comment with 4,000 upvotes versus one with two gives you a strong signal on what resonates most with particular audiences, and Reddit attaches that number to every opinion shared.
While Reddit is arguably the internet’s largest archive of candid human opinions, extracting its data has never been more challenging. Reddit recently shut down unauthenticated .json endpoints in May 2026, severing the easiest routes for bulk data extraction.
This guide solves that problem using Apify. You'll learn how to extract posts and comments with upvote counts intact, categorize reaction types using OpenAI, and store the enriched data with embeddings in Supabase for fine-tuning or retrieval.
Extract Reddit posts, comments, and vote counts with Apify #
Reddit Scraper is a ready-made Apify Actor that reads Reddit's public pages directly, so it doesn't depend on the endpoints Reddit retired. Every run returns:
- Post titles, body text, subreddit, and flair
- Score and upvote ratio on every post
- Full comment trees, each comment carrying its own score
- Authors, permalinks, and timestamps
With Reddit Scraper, you can:
- Pull posts from any subreddit, sorted by hot, new, top, or rising
- Search across all of Reddit or inside a single community
- Pull the full comment tree for a specific post
- Run it from Apify Console, on a schedule, or from n8n, which is what you'll do here
Pricing starts at $1.20 per 1,000 results.
Workflow summary #
Reddit Scraper is scheduled to extract posts and comments from Reddit, along with their respective vote counts. From there, the pipeline splits into two distinct workflows: the post branch stores and embeds the original post as the primary context, while the comment branch classifies each opinion, logs a unique signal score, and generates its embedding, before joining it back to the parent post via its ID. The stored posts and comments serve as the core training dataset and can be easily exported in JSONL or Parquet formats for LLM fine-tuning or training.
Prerequisites #
Apify account.- Self-hosted n8n instance via Docker ( skip if you prefer).** n8n’s cloud tier** OpenAI accountwith an API key (used to generate embeddings and for data classification).Supabase account.
Phase 1: Accounts setup #
Step #1: Grab your Apify API token
- Sign up at apify.comif you don’t have an account. - Open Apify Console. - In the left sidebar, click Settings, then open the** API & Integrations**tab. - Under "Personal API tokens," copy your default token, or click Add new token to create one and name it. - Paste it somewhere safe for a moment.
This token lets n8n run the scrapers on your behalf. Keep it private, and revoke or regenerate it from this same screen if it ever leaks.
Step #2: Get your OpenAI API key
- Go to platform.openai.comand sign in or create an account. - Open the API keys section, clickCreate new secret key, name it, and copy the key as soon as it appears. - Paste it somewhere safe.
- Open Billing underSettings, add a payment method, and buy a small amount of credit; $5 is plenty for testing.
You’ll use this key in two separate n8n nodes: one to label each comment, the other to generate embeddings. Copy it and paste it somewhere safe.
Step #3: Create the Supabase project and keys
- Sign in at supabase.comand clickNew project. - Create an organization, name the project, input a database password, choose the region nearest you, then click Create new project. - In the left sidebar, click the gear to open Project Settings, then click Data** API**. - On the Data API page, copy the Project URL. - Under API keys, find the** service_rolekey, click Reveal**, and copy it.
The service_role key can write to your tables, which is what the pipeline needs. Keep it server-side inside n8n and never put it in client code, since it bypasses row-level security.
Step #4: Create the database tables
- In the left sidebar, click the SQL Editor icon, then clickNew query. - Paste the first block below into the editor, then click Run. You should see "Success. No rows returned."
create extension if not exists vector;
create table documents (
id bigserial primary key,
content text,
metadata jsonb,
embedding vector(1536)
);
create function match_documents (
query_embedding vector(1536),
match_count int default null,
filter jsonb default '{}'
) returns table (
id bigint,
content text,
metadata jsonb,
similarity float
)
language plpgsql
as $$
begin
return query
select
documents.id,
documents.content,
documents.metadata,
1 - (documents.embedding <=> query_embedding) as similarity
from documents
where documents.metadata @> filter
order by documents.embedding <=> query_embedding
limit match_count;
end;
$$;
- Open a new query tab, paste this second block, and click Run again to create the relational tables.
create table posts (
post_id text primary key,
subreddit text,
title text,
self_text text,
score bigint,
upvote_ratio numeric,
num_comments int,
link_flair text,
domain text,
author text,
url text,
permalink text,
context_text text,
created_at timestamptz,
scraped_at timestamptz,
source_sort text
);
create table comments (
comment_id text primary key,
post_id text,
subreddit text,
author text,
text text,
score bigint,
reaction_type text,
permalink text,
created_at timestamptz,
scraped_at timestamptz,
source_sort text
);
create index on comments (post_id);
- Confirm the tables exist by opening the
Table Editor in the left sidebar; you should see
documents
,posts
, andcomments
listed.
The posts table stores the context, while the comments table captures the reactions by logging the type of opinion (reaction_type
) and using the vote score to measure how deeply it resonated. The documents table handles the embeddings for AI retrieval. Because everything is linked by a shared post_id
, you can easily take any post and instantly pull up every community reaction it sparked.
Phase 2: Install and set up n8n #
Self-hosting n8n lets you build and run unlimited workflows for free, with full control over your data privacy. But, if you don’t want to manage your own infrastructure, the n8n cloud tier works fine.
Step #1: Install Docker Desktop
Download and install Docker Desktop for your OS.- Confirm it's running by opening your terminal and running:
docker --version
- You should see a version number. If you see "command not found," Docker isn't in your PATH yet, so restart your terminal or reboot.
- Afterward, confirm the engine is running by typing:
docker ps
Step #2: Run n8n in Docker
- Open your terminal.
- Run the command below to pull and start n8n:
docker run -d --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n n8nio/n8n
- The first time you run this command, Docker will to pull the official n8n image from the internet. After down, it will assign and print your Container ID.
- Open a browser and go to
http://localhost:5678
. - You'll see n8n's initial setup screen. Create an owner account with your email and a password.
Step #3: Install the Apify community node
n8n doesn't feature the Apify node natively. You’ll need to install it as an official community node before you can use it. Here’s how to do that:
-
In your n8n workspace, click Settings at the bottom of the left sidebar, then navigate to Community Nodes and click “Install”. - In the npm package field, paste exactly this:
@apify/n8n-nodes-apify -
Check the risk acknowledgment box and click Install.
Phase 3: Build the pipeline #
The Actor feeds two branches, both of which read its output: a post branch that writes context (post content and metadata), and a comment branch that writes opinions and their scores. Build the post branch first, then come back to the scraper and start the comment branch from the same output.
Step #1: Schedule Trigger node
- Create a new workflow by clicking
Add workflow in the top left. - On the empty canvas, click the large
+ in the center or the "Add first step" option. - In the search panel, type
Schedule Trigger
and select it to place it on the canvas. - Set
Trigger Interval toWeeks - Set
Weeks Between Triggers to1
andTrigger on Weekdays to Monday.
Step #2: Define dataset inputs
- On the Schedule Trigger node, click the
+ on its right-hand output dot. - In the search panel, type
Edit Fields
and select theEdit Fields (Set) node. - Open the node and click its title to rename it to
Define Dataset Inputs
. - Leave
Mode on "Manual Mapping". - Click
Add Field once for each input below. For each one, type the name in the left box, open theType dropdown on the right to select the shown type, and enter the value in the value box.subreddits
, type String, valueAskReddit: 40, changemyview: 40, technology: 40, movies: 40
.sort
, type String, valuetop
.timeFilter
, type String, valueweek
.minScore
, type Number, value50
.maxCommentsPerPost
, type Number, value100
.
Keeping all your inputs in a single node makes it easier to update your settings. For example, the subreddits field uses a simple name:count
format, allowing you to pull more posts from massive communities and fewer from niche ones. If you omit the count, it defaults to 100.
Step #3: Build subreddit list
- On the Define dataset inputs node, click the
+ on its output dot. - Search
Code
and select theCode node. Rename it toBuild subreddit list
. - Set Mode to "Run Once for All Items" andLanguage to "JavaScript". - Delete the sample code and paste the code below.
const plan = $('Define Dataset Inputs').first().json.subreddits || '';
return plan
.split(',')
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => {
const [name, count] = entry.split(':').map((s) => s.trim());
return {
json: {
subreddit: name,
maxResults: Number(count) || 100,
},
};
});
This converts the single subreddits
string into one item per community, each carrying its own maxResults
. It matters because, without it, the Actor spends its entire result budget on the first subreddit before moving to the next, so pointing it at four communities at once would exhaust the first and never reach the others. By emitting a separate item per subreddit, you run the scraper once for each subreddit.
Step #4: Reddit scraper (Apify) node
- On the Build subreddit list node, click the
+ on its output dot. - Search
Apify
, select theRun an Actor and get dataset operation, and rename it toReddit scraper
. - Click the
Credential to open the dropdown, then choose**+ Create new credential**. - Paste your Apify API token into the
API token field, clickSave, then close. - Confirm
Resource is "Actor" andOperation is "Run an Actor and Get Dataset". - Under
Actor, choose "From list" then type and selectlabrat011/reddit-scraper
- Scroll to the Input field for the Actor, set it toExpression, and paste this JSON:
{
"mode": "subreddit_posts",
"subreddits": [{{ JSON.stringify($json.subreddit) }}],
"sort": "{{ $('Define Dataset Inputs').first().json.sort }}",
"timeFilter": "{{ $('Define Dataset Inputs').first().json.timeFilter }}",
"maxResults": {{ $json.maxResults }},
"includeComments": true,
"maxCommentsPerPost": {{ $('Define Dataset Inputs').first().json.maxCommentsPerPost || 100 }},
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
This node runs once per community. It uses {{ $json.subreddit }}
and {{ $json.maxResults }}
to read the current subreddit and its target post count, while referencing the inputs node for global settings like sort and comment depth.
Step #5: Code node for post rows
- On the Reddit scraper node, click the
+ on its output dot. - Search
Code
and select theCode node. Rename it toBuild post rows
. - Set Mode to "Run Once for All Items" andLanguage to "JavaScript". - Delete the sample code and paste the code below.
const items = $input.all();
const inputs = $('Define Dataset Inputs').first().json || {};
const minScore = Number(inputs.minScore) || 0;
const sortTag = inputs.sort || '';
return items
.filter((item) => item.json.type === 'post' && (Number(item.json.score) || 0) >= minScore)
.map((item) => {
const p = item.json;
const title = p.title || '';
const selfText = p.selftext || '';
const subreddit = p.subreddit || '';
const contextText = [
`Subreddit: r/${subreddit}`,
`Title: ${title}`,
selfText ? `Body: ${selfText}` : '',
].filter(Boolean).join('\n\n');
return {
json: {
post_id: p.id,
subreddit,
title,
self_text: selfText,
score: Number(p.score) || 0,
upvote_ratio: Number(p.upvoteRatio) || 0,
num_comments: Number(p.numComments) || 0,
link_flair: p.flair || '',
domain: p.domain || '',
author: p.author || '',
url: p.url || '',
permalink: p.url || '',
context_text: contextText,
created_at: p.created || null,
scraped_at: new Date().toISOString(),
source_sort: sortTag,
},
};
});
- Click Execute step once, with the scraper already run, to confirm the output shows post rows.
This step prepares your posts for the database. It filters out everything except post
records that meet your minScore
, and translates the raw fields into snake_case
(like selftext
to self_text
). This exact matching allows the next node to auto-map the columns easily.
Step #6: Create a row (Supabase) node for posts
- On the Build post rows node, click the
+ on its output dot. - Search
Supabase
and select theCreate a row operation. Rename it toInsert post row
. - Click
Credential to connect with, choose** Create new credential**, paste your Supabase Project URL into the** Hostfield and your service_role key into the Service Role Secretfield, click Save**, and close the dialog. - Note that the host is the bare project URL,
https://YOUR-REF.supabase.co
, with no/rest/v1/
path on the end. - Set
Table toposts
. - Set the Mapping Column Mode to "Map Automatically". - Open the Settings tab, setOn Error to "Continue", and leaveRetry On Fail off.
This step writes a single context row for each post. Because your incoming data already carries every necessary column, including the newly created context_text
and source_sort
, you can rely entirely on auto-map to handle the insert without manual configuration.
Step #7: Supabase Vector Store node for post context
- On the Insert post row node, click the
+ on its output dot. - Search
Supabase Vector Store
and select it. Rename it toEmbed post context
. - Set
Operation Mode to "Insert Documents". - Under
Credential, reuse your Supabase credential. - Set
Table Name todocuments
. - This node has two connection points beneath it:
Embedding andDocument. On the** Embeddingpoint, click the+**, addEmbeddings OpenAI
, set itsModel totext-embedding-3-small
, and pick your OpenAI credential. - On the
Document point, click the**+**, addDefault Data
. SetType of Data to "JSON" andMode to "Load Specific Data". In the data field, switch it toExpression and enter{{ $('Build post rows').all()[$itemIndex].json.context_text }}
. - Under the 's
Metadata, click** Add Property**three times, switching each value to Expression and input the following accordingly:source_type
set topost
,post_id
set to{{ $('Build post rows').all()[$itemIndex].json.post_id }}
, andsubreddit
set to{{ $('Build post rows').all()[$itemIndex].json.subreddit }}
.
This step embeds the post context, storing the content as searchable vectors tagged with source_type: post
. Pay close attention to the syntax: you must use .all()[$itemIndex]
instead of the usual .item
. The previous insert skips duplicates, so fetching by index is required to grab the right row.
Step #8: Code node for comment rows
- Scroll back to the
Reddit scraper node and click the**+** on its output dot a second time. A node output can feed more than one path, so this starts the parallel comment branch. - Search
Code
and select theCode node. Rename it toBuild comment rows
. - Set Mode to "Run Once for All Items" andLanguage to "JavaScript". - Delete the sample code and paste this:
const items = $input.all();
return items
.filter((item) => item.json.type === 'comment')
.map((item) => {
const c = item.json;
return {
json: {
comment_id: c.id,
post_id: c.postId || null,
subreddit: c.subreddit || '',
author: c.author || '',
text: c.body || '',
score: Number(c.score) || 0,
permalink: c.url || '',
created_at: c.created || null,
scraped_at: new Date().toISOString(),
},
};
})
.filter((row) => {
const t = (row.json.text || '').trim();
return t !== '' && t !== '[deleted]' && t !== '[removed]';
});
This branch processes only the comment
records. Since your Actor automatically attaches a postId
to each comment, they link cleanly back to their parent posts without any permalink parsing. The opinion itself is pulled from the body
field, while the score
acts as your resonance signal.
Step #9: OpenAI node to classify the reaction
- On the Build comment rows node, click the
+ on its output dot. - Search
OpenAI
and, under the OpenAI node's actions, selectMessage a Model. Rename it toClassify reaction type
. - Under
Credential to connect with, reuse your OpenAI credential. - Open the
Model dropdown and pickgpt-4.1-mini
. - Under Messages, click** Add Message**. Set its** Roleto "System" and paste the prompt below into the Content**box.
You are labeling Reddit comments for a machine learning dataset about how people react to and endorse opinions. You will be given a single comment. Classify the nature of the opinion or reaction it expresses into exactly one category from this list: "agreement", "disagreement", "insight", "humor", "personal experience", "critique", "question", "other". Judge only the comment text. Return a single JSON object with two keys: "reaction_type" (one of the categories) and "reasoning" (a short phrase under 10 words). Return only the JSON object.
- Click
Add Message again. Set itsRole to "User", switch itsContent box toExpression, and enter
{{ $('Build comment rows').item.json.text }}
. - Leave Simplify Output on; the prompt already forces JSON. If your version shows aResponse Format option, set it to "JSON".
This step labels each opinion, while shielding the vote score from the model and feeding it only the text, ensuring that each label evaluates the opinion purely on its own terms.
Step #10: Assemble the comment row and insert it
Because the classifier replaces the current item with the model's output, your original comment fields are temporarily lost, and the new reaction label is buried in the response. This node fixes that by rebuilding a new comment row by reaching back to the 'Build comment rows' step to retrieve the original text and scores, extracts the label from the classifier, and appends your global sort preference.
- On the Classify reaction type node, click the
+ on its output dot. - Search
Code
and select theCode node. Rename it toBuild comment insert row
. - Set Mode to "Run Once for All Items" andLanguage to "JavaScript". - Delete the sample code and paste the code below.
const comments = $('Build comment rows').all();
const sortInputs = $('Define Dataset Inputs').first().json || {};
const sort = sortInputs.sort || '';
// Dig the reaction label out of whatever shape the classifier returned, then parse it.
const readReaction = (j) => {
const text =
j?.output?.[0]?.content?.[0]?.text ??
j?.content?.[0]?.text ??
j?.content ??
j?.text ??
(typeof j?.message?.content === 'string' ? j.message.content : null);
if (!text || typeof text !== 'string') return null;
try {
return JSON.parse(text).reaction_type ?? null;
} catch {
const m = text.match(/"reaction_type"\s*:\s*"([^"]+)"/);
return m ? m[1] : null;
}
};
return $input.all().map((item, i) => {
const c = comments[i].json;
return {
json: {
comment_id: c.comment_id,
post_id: c.post_id,
subreddit: c.subreddit,
author: c.author,
text: c.text,
score: c.score,
reaction_type: readReaction(item.json),
permalink: c.permalink,
created_at: c.created_at,
scraped_at: c.scraped_at,
source_sort: sort,
},
};
});
- On the Build comment insert row node, click the
+ on its output dot. - Search
Supabase
and select theCreate a row operation. Rename it toInsert comment row
. - Under
Credential to connect with, reuse your Supabase credential. - Set
Table tocomments
. - Set the Mapping Column Mode to "Map Automatically". - Open the Settings tab, setOn Error to "Continue", and leaveRetry On Fail off, so a comment already stored gets skipped on its duplicate primary key instead of failing the run.
Step #11: Supabase Vector Store node for comments
- On the Insert comment row node, click the
+ on its output dot. - Search
Supabase Vector Store
and select it. Rename it toEmbed comment
. - Set
Operation Mode to "Insert Documents". - Under
Credential, reuse your Supabase credential. - Set
Table Name todocuments
. - On the
Embedding point, addEmbeddings OpenAI
with modeltext-embedding-3-small
and your OpenAI credential. - On the
Document point, addDefault Data
. SetType of Data to "JSON" andMode to "Load Specific Data", switch the data field to Expression, and enter{{ $('Build comment rows').all()[$itemIndex].json.text }}
. - Under
Metadata, add four properties, switching to Expression where needed:source_type
set tocomment
,comment_id
set to{{ $('Build comment rows').all()[$itemIndex].json.comment_id }}
,post_id
set to{{ $('Build comment rows').all()[$itemIndex].json.post_id }}
, andscore
set to{{ $('Build comment rows').all()[$itemIndex].json.score }}
.
This node embeds each opinion to make the comments searchable, saving the score
directly into the metadata so you can easily filter and rank by resonance. Keep an eye on the syntax here, just like the post embedder, you must use .all()[$itemIndex]
instead of .item
.
Phase 4: Test and publish #
Step #1: Execute the workflow
- Click Execute workflow at the bottom center of the canvas to run the whole pipeline once. - Watch the nodes turn green in sequence.
Step #2: Check the tables in Supabase
- In Supabase, open the
Table Editor in the left sidebar. - Open
posts
. You should see one context row per post, each with a realscore
. - Open
comments
. You should see the opinions, each with itsscore
, areaction_type
label, and apost_id
that matches a row inposts
. - Open
documents
. You should see the post and comment rows, distinguishable bysource_type
in the metadata.
Step #3: Confirm the relationship with a join query
- Open the SQL Editor, click** New query**, paste the query below, and click** Run**.
select p.title, c.text, c.score, c.reaction_type
from comments c
join posts p on p.post_id = c.post_id
order by c.score desc
limit 20;
This script shows the relationship the dataset captures: each row pairs a post's content with one opinion about it, that opinion's reaction type, and how strongly the crowd endorsed it.
The workflow results #
Understanding your database schema is critical for utilizing this dataset effectively. Here is a complete breakdown of what each column represents across the three tables:
**1. **posts
posts
Stores the original context of every scraped post:
| Column | Description |
|---|---|
post_id |
|
| The post's unique ID, which serves as the master key that other tables link back to. | |
subreddit |
|
| The community where the post originated. | |
title |
|
| The post's headline. | |
self_text |
|
| The post's body text (this will often be empty for link or title-only posts). | |
score |
|
| The post's net upvotes. | |
upvote_ratio |
|
| The share of total votes that were upvotes, expressed as a decimal from 0 to 1. | |
num_comments |
|
| The total number of replies the post received. | |
link_flair |
|
| The post's category tag, if applicable. | |
domain |
|
The source of the link (e.g., self.AskReddit for text posts). |
|
author |
|
| The original poster's username. | |
url |
|
| The direct link to the post's content. | |
permalink |
|
| The relative Reddit URL for the thread. | |
context_text |
|
| A single text block bundling the subreddit, title, and body. This is the exact string that gets vectorized for embeddings. | |
created_at |
|
| The timestamp of when the post was originally published. | |
scraped_at |
|
| The timestamp of when you captured the data. | |
source_sort |
|
| The specific filter used during extraction (e.g., top, new). |
**2. **comments
comments
Captures human reactions and tabulates their resonance strength relative to the post's context:
| Column | Description |
|---|---|
comment_id |
|
| The comment's unique ID and primary key. | |
post_id |
|
The foreign key linking this opinion back to its parent post in the posts table. |
|
subreddit |
|
| The community where the comment was made. | |
author |
|
| The commenter's username. | |
text |
|
| The actual words of the voiced opinion. | |
score |
|
| The comment's net upvotes. This is your core resonance signal. | |
reaction_type |
|
| The AI-assigned label categorizing the nature of the opinion (e.g., agreement, disagreement, humor). | |
permalink |
|
| The direct Reddit link to the comment. | |
created_at |
|
| The timestamp of when the comment was originally published. | |
scraped_at |
|
| The timestamp of when you captured the data. | |
source_sort |
|
| The specific filter used during extraction. |
**3. **documents
documents
Functions as a standard vector-store table with a fixed schema:
| Column | Description |
|---|---|
id |
|
| An auto-incrementing row number. | |
content |
|
The raw text that was embedded (either a post's context_text or a comment's text ). |
|
metadata |
|
JSON storing your tags. For posts, this includes the source_type , post_id , and subreddit . For comments, it includes the source_type , comment_id , post_id , and score . |
|
embedding |
|
| The numerical vector representation of the content used for semantic search. |
The columns in your posts
and comments
tables directly mirror the outputs from your Build post rows and Build comment rows Code nodes in n8n. If you ever add or rename a field within those nodes, you must manually update your database tables to reflect the changes. The documents
table, by contrast, is strictly controlled by the vector store node and should never be altered.
Summary #
You now have a dataset where every opinion carries a real number for how hard a crowd endorsed it. Posts, comments, and scores are linked through post_id
, ready for classifiers, retrieval, or resonance modeling.
Accessing this level of context would be nearly impossible without the Reddit Actor used in this build.
Apify Store offers one of the largest catalogs of scraping and automation tools on the web, with 50,000+ ready-made Actors designed to extract data from virtually any site with proxies, anti-bot handling, and infrastructure maintained for you.
Start with a free Apify account and $5 in platform credits every month.
FAQ
Can I see how many downvotes a comment got?
No. Reddit only exposes the net score (upvotes minus downvotes), so a comment sitting at 12 could have been 12 up and 0 down, or 40 up and 28 down. The split isn't published; this is a Reddit limitation, not a scraper limitation.
How do I estimate downvotes?
You can, but only on posts, not comments. Posts carry an upvote_ratio
alongside the score, so you can work backward: Total Votes = Net Score / (2 × Upvote% − 1)
. A post with a score of 9 and an 80% upvote rate received 15 total votes: 12 up and 3 down. Comments have no published ratio, so the math doesn't work there.
Are the scores exact vote counts?
No. Reddit uses "vote fuzzing" to deliberately alter counts slightly on every refresh to frustrate bots and vote manipulators. Your scores are close approximations, reliable for ranking opinions against each other, but not exact tallies. That's fine for this dataset, since what matters is which opinions outscored which, not the precise numbers.
Why are almost all my comment scores positive?
Because you're pulling top comments by default, you're seeing the well-received end of the distribution. Raise maxCommentsPerPost
to dig deeper into each thread and reach downvoted opinions. Even then, Reddit collapses and fuzzes heavily buried comments, so the negative tail is always partial.
Why is self_text
empty on so many posts?
They're link or title-only posts, which is normal on subreddits like AskReddit, where the question is the entire post. There's no body to capture, and nothing is lost, since the title alone is the full prompt the comments are responding to.vv