How to turn Reddit upvotes into AI training data

Extract Reddit posts and comments with upvote counts, categorize reaction types, and store the data with embeddings in Supabase for fine-tuning or retrieval.

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.

Apify logo
Try Apify today
Get $5 monthly usage and try any tool for free
Get web data

Workflow summary

Image of final n8n workflow

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

Phase 1: Accounts setup

Step #1: Grab your Apify API token

  • Sign up at apify.com if 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.

Apify console dashboard

Step #2: Get your OpenAI API key

  • Go to platform.openai.com and sign in or create an account.
  • Open the API keys section, click Create new secret key, name it, and copy the key as soon as it appears.
  • Paste it somewhere safe.
  • Open Billing under Settings, 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.

OpenAI platform dashboard

Step #3: Create the Supabase project and keys

  • Sign in at supabase.com and click New 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_role key, 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.

Supabase homepage

Step #4: Create the database tables

  • In the left sidebar, click the SQL Editor icon, then click New 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, and comments 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.

Schema setup code within Supabase SQL Editor

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

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
Open Command line interface
docker ps revealed I already have an existing container running, it will output differently if you don’t

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 pause to pull the official n8n image from the internet. After downloading, 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.
Open Command Line interface showing finished docker setup

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.
n8n dashboard previewing its settings column

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 to Weeks
  • Set Weeks Between Triggers to 1 and Trigger on Weekdays to Monday.
Opened Schedule trigger node

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 the Edit 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 the Type dropdown on the right to select the shown type, and enter the value in the value box.
    • subreddits, type String, value AskReddit: 40, changemyview: 40, technology: 40, movies: 40.
    • sort, type String, value top.
    • timeFilter, type String, value week.
    • minScore, type Number, value 50.
    • maxCommentsPerPost, type Number, value 100.

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.

Opened Define Dataset Inputs node

Step #3: Build subreddit list

  • On the Define dataset inputs node, click the + on its output dot.
  • Search Code and select the Code node. Rename it to Build subreddit list.
  • Set Mode to "Run Once for All Items" and Language 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.

Opened Code in JavaScript node

Step #4: Reddit scraper (Apify) node

  • On the Build subreddit list node, click the + on its output dot.
  • Search Apify, select the Run an Actor and get dataset operation, and rename it to Reddit scraper.
  • Click the Credential to open the dropdown, then choose + Create new credential.
  • Paste your Apify API token into the API token field, click Save, then close.
  • Confirm Resource is "Actor" and Operation is "Run an Actor and Get Dataset".
  • Under Actor, choose "From list" then type and select labrat011/reddit-scraper
  • Scroll to the Input field for the Actor, set it to Expression, 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.

Opened Reddit Actor node

Step #5: Code node for post rows

  • On the Reddit scraper node, click the + on its output dot.
  • Search Code and select the Code node. Rename it to Build post rows.
  • Set Mode to "Run Once for All Items" and Language 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.

Opened Build post rows node

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 the Create a row operation. Rename it to Insert post row.
  • Click Credential to connect with, choose Create new credential, paste your Supabase Project URL into the Host field and your service_role key into the Service Role Secret field, 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 to posts.
  • Set the Mapping Column Mode to "Map Automatically".
  • Open the Settings tab, set On Error to "Continue", and leave Retry 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.

Opened insert post row node

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 to Embed post context.
  • Set Operation Mode to "Insert Documents".
  • Under Credential, reuse your Supabase credential.
  • Set Table Name to documents.
  • This node has two connection points beneath it: Embedding and Document. On the Embedding point, click the +, add Embeddings OpenAI, set its Model to text-embedding-3-small, and pick your OpenAI credential.
  • On the Document point, click the +, add Default Data Loader. Set Type of Data to "JSON" and Mode to "Load Specific Data". In the data field, switch it to Expression and enter {{ $('Build post rows').all()[$itemIndex].json.context_text }}.
  • Under the loader's Metadata, click Add Property three times, switching each value to Expression and input the following accordingly: source_type set to post, post_id set to {{ $('Build post rows').all()[$itemIndex].json.post_id }}, and subreddit 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.

Opened Embed post context node

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 the Code node. Rename it to Build comment rows.
  • Set Mode to "Run Once for All Items" and Language 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.

Opened Build Comment rows node

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, select Message a Model. Rename it to Classify reaction type.
  • Under Credential to connect with, reuse your OpenAI credential.
  • Open the Model dropdown and pick gpt-4.1-mini.
  • Under Messages, click Add Message. Set its Role to "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 its Role to "User", switch its Content box to Expression, and enter {{ $('Build comment rows').item.json.text }}.
  • Leave Simplify Output on; the prompt already forces JSON. If your version shows a Response 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.

Opened Embeddings OpenAI node

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 the Code node. Rename it to Build comment insert row.
  • Set Mode to "Run Once for All Items" and Language 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,
    },
  };
});
Opened Build comment insert row node
  • On the Build comment insert row node, click the + on its output dot.
  • Search Supabase and select the Create a row operation. Rename it to Insert comment row.
  • Under Credential to connect with, reuse your Supabase credential.
  • Set Table to comments.
  • Set the Mapping Column Mode to "Map Automatically".
  • Open the Settings tab, set On Error to "Continue", and leave Retry On Fail off, so a comment already stored gets skipped on its duplicate primary key instead of failing the run.
Opened insert comment row node

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 to Embed comment.
  • Set Operation Mode to "Insert Documents".
  • Under Credential, reuse your Supabase credential.
  • Set Table Name to documents .
  • On the Embedding point, add Embeddings OpenAI with model text-embedding-3-small and your OpenAI credential.
  • On the Document point, add Default Data Loader. Set Type of Data to "JSON" and Mode 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 to comment, comment_id set to {{ $('Build comment rows').all()[$itemIndex].json.comment_id }}, post_id set to {{ $('Build comment rows').all()[$itemIndex].json.post_id }}, and score 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 .

Opened Embed comment node

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 real score.
  • Open comments. You should see the opinions, each with its score, a reaction_type label, and a post_id that matches a row in posts.
  • Open documents. You should see the post and comment rows, distinguishable by source_type in the metadata.
posts table results in Supabase

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.

Join query results in Supabase

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

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

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

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.

Apify logo
Largest marketplace of tools for AI
Thousands of Actors to automate your business. Get real-time web data, track competitors, generate leads, monitor social media, and integrate your apps and agents.

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

On this page

Publish and earn on Apify Store

The largest marketplace of tools for AI

Start here