Reddit sentiment analysis with Python: VADER on live comments
Reddit sentiment analysis in Python: fetch live comments through an API, score them with VADER, and see where lexicon scores and real opinion part ways.
Piyushh · October 17, 2026 · 5 min read
Reddit sentiment analysis means pulling Reddit posts or comments about a topic and scoring each one as positive, negative or neutral. In Python that takes two parts: something to fetch the comments, and a library to score them. This tutorial uses ChatterAPI's comment search for the first part and VADER, an open-source sentiment analyzer built for social media, for the second. Everything below ran against live Reddit data on October 17, 2026, including the part where VADER got it badly wrong.
The test topic was "cursor ide". We wanted to know how people on Reddit felt about the Cursor editor that week. VADER said 85% positive. Reading the comments ourselves told a different story, and the gap is the most useful thing in this post.
What you need
- Python 3.9 or newer
pip install vaderSentiment requests- A ChatterAPI key in
CHATTERAPI_KEY. Sign up with Google for 100 free credits, no card needed. That's 50 pages of comment search.
VADER (Valence Aware Dictionary and sEntiment Reasoner) is a lexicon and rule-based model by C.J. Hutto and Eric Gilbert, described in their 2014 ICWSM paper. It needs no training data and no GPU. It handles capital letters, exclamation marks, "but" clauses and common slang, so it's the usual first try for Reddit text.
Step 1: Fetch Reddit comments about a topic
Reddit's own API doesn't search comments, and it stops accepting new access requests on October 31, 2026 (see the shutdown timeline). ChatterAPI's GET /v1/reddit/comments/search does, for 2 credits per page:
curl "https://api.chatterapi.com/v1/reddit/comments/search?q=cursor+ide&sort=new" \
-H "Authorization: Bearer $CHATTERAPI_KEY"
Here's one comment from the live response we got on October 17, 2026, plus the envelope:
{
"data": [
{
"fullname": "t1_ph2344s",
"postTitle": "Final straw, cancelled Cursor",
"author": "redsyndrome_",
"body": "You're not the only one don't worry… I've been a fan, I even subscribed to the annual offer. But now I can barely work on my projects without hitting the limit in a week or two...",
"score": 3,
"subreddit": "cursor",
"createdAt": "2026-10-16T06:18:47.000Z",
"url": "https://www.reddit.com/r/cursor/comments/1x6onge/final_straw_cancelled_cursor/ph2344s/"
}
],
"page": { "nextCursor": "AdlbSadASBdE…", "hasMore": true },
"credits": { "charged": 2, "remaining": 74 }
}
Each page returned 10 comments for this query. Pass page.nextCursor back as cursor to get the next page. Sorting by new gives you a time series, and sorting by relevance gives you the comments that match your words most closely. The free Reddit comment search tool runs the same query in a browser if you want to look before you code.
Step 2: Score every comment with VADER
The whole pipeline is one script. It fetches two pages, scores each body, then prints the label counts, a per-subreddit breakdown and the extremes.
import os
from collections import Counter, defaultdict
import requests
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
BASE = "https://api.chatterapi.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CHATTERAPI_KEY']}"}
def fetch_comments(query, pages=2):
"""Newest Reddit comments that mention the query, one page per call."""
comments, cursor = [], None
for _ in range(pages):
params = {"q": query, "sort": "new"}
if cursor:
params["cursor"] = cursor
r = requests.get(f"{BASE}/reddit/comments/search", headers=HEADERS, params=params, timeout=60)
r.raise_for_status()
body = r.json()
comments += body["data"]
print(f"page: {len(body['data'])} comments, charged {body['credits']['charged']} credits")
if not body["page"]["hasMore"]:
break
cursor = body["page"]["nextCursor"]
return comments
def label(compound):
# Thresholds from the VADER README: >= 0.05 positive, <= -0.05 negative.
if compound >= 0.05:
return "positive"
if compound <= -0.05:
return "negative"
return "neutral"
analyzer = SentimentIntensityAnalyzer()
rows = []
for c in fetch_comments("cursor ide"):
score = analyzer.polarity_scores(c["body"])["compound"]
rows.append({**c, "compound": score, "label": label(score)})
counts = Counter(r["label"] for r in rows)
avg = sum(r["compound"] for r in rows) / len(rows)
print(f"\n{len(rows)} comments, average compound {avg:+.3f}")
for name in ("positive", "neutral", "negative"):
print(f" {name:<8} {counts[name]:>3} ({counts[name] / len(rows):.0%})")
by_sub = defaultdict(list)
for r in rows:
by_sub[r["subreddit"]].append(r["compound"])
print("\nby subreddit (2+ comments):")
for sub, scores in sorted(by_sub.items(), key=lambda kv: -len(kv[1])):
if len(scores) > 1:
print(f" r/{sub:<14} n={len(scores)} avg {sum(scores) / len(scores):+.3f}")
rows.sort(key=lambda r: r["compound"])
for title, pick in (("most negative", rows[0]), ("most positive", rows[-1])):
print(f"\n{title} ({pick['compound']:+.3f}) r/{pick['subreddit']}:")
print(f" {pick['body'][:140]}")
Here is the real output from October 17, 2026 (the two printed comment excerpts are cut short here):
page: 10 comments, charged 2 credits
page: 10 comments, charged 2 credits
20 comments, average compound +0.507
positive 17 (85%)
neutral 2 (10%)
negative 1 (5%)
by subreddit (2+ comments):
r/cursor n=6 avg +0.660
r/claude n=2 avg -0.015
r/ClaudeCode n=2 avg +0.576
most negative (-0.850) r/claude:
Man it was literally mentioned in CURSOR docs and even in the Ui ...
most positive (+0.998) r/Backend:
Between hand coding for months and vibing out spaghetti in an hour ...
That cost four credits for 20 comments. On those numbers, Reddit loved Cursor that week.
Step 3: Read the comments before you believe the number
We read all 20. Judged on what each comment says about Cursor the product, we counted 8 negative, 2 positive, 1 mixed, and 9 that mention Cursor without giving an opinion on it. VADER called 17 of them positive.
Here's where the two disagreed most:
| Comment (r/subreddit, author) | VADER | What it says about Cursor |
|---|---|---|
| "Final straw, cancelled Cursor" thread (r/cursor, u/redsyndrome_) | +0.963 | Cancelled the annual plan after hitting limits, happier on another tool |
| "I just switched from cursor to Claude" (r/cursor, u/skyhighskyhigh) | +0.710 | Left Cursor. The positive words are about the replacement |
| "I have really enjoyed cursor as an IDE..." (r/cursor, u/benjamingrx) | +0.954 | Past praise, then "I am looking for other options" |
| "Cursor will be 'grok only' :/" (r/vibecoding, u/altervoid) | +0.128 | Says they would not stay and switched back to Copilot |
| The most negative comment (r/claude, u/Machine2024) | -0.850 | Not about Cursor. The swear word was aimed at another tool's old CLI |
Three things went wrong, and each shows up in nearly every Reddit sentiment project.
First, tone isn't stance. People who leave a product usually sound happy about the one they moved to. VADER scores the whole comment's mood, so a churn story reads as positive.
Second, keyword matching catches the wrong thing. "cursor ide" matched a dictation app whose "words land at your cursor", which has nothing to do with the editor. Nine more comments named Cursor in passing, in a games-industry thread or a Hackintosh question, and said nothing about it. VADER scored all of them on whatever else they were talking about.
Third, one voice can swamp a small sample. One account wrote 5 of the 20 comments, so a quarter of the "sample" was one person.
Step 4: Score the sentences about your target
A cheap fix for the first two problems is to keep only coding subreddits, then score the sentences that name the target instead of the whole comment.
import re
TARGET = re.compile(r"\bcursor\b", re.I)
CODING_SUBS = {"cursor", "CursorAI", "vibecoding", "ClaudeCode", "claude", "ChatGPTCoding", "Backend"}
def target_score(text):
"""Average VADER compound over the sentences that name the target only."""
sentences = re.split(r"(?<=[.!?])\s+", text)
hits = [analyzer.polarity_scores(s)["compound"] for s in sentences if TARGET.search(s)]
return sum(hits) / len(hits) if hits else None
kept = [c for c in rows if c["subreddit"] in CODING_SUBS]
scored = [(c, target_score(c["body"])) for c in kept]
scored = [(c, s) for c, s in scored if s is not None]
Run over the same 20 comments with no new API calls, it printed:
20 comments -> 14 in coding subreddits -> 14 with a sentence about Cursor
positive 9
neutral 4
negative 1
Better, but not fixed. The subreddit filter dropped two real opinions (in r/Leadership and r/AskReddit) and still let the dictation app through. The cancelled-subscription comment fell from +0.963 to +0.700, and "I just switched from cursor to Claude" went from +0.710 to 0.000, because the happy words sit in other sentences. The overall picture still reads positive. The sentences that name Cursor are mostly neutral facts ("I switched from cursor"), while the opinion sits in the next sentence.
What to use instead (or as well)
VADER is still worth running. It's fast, free and consistent, and it's fine for tone over time: is the conversation in r/yourproduct getting angrier this month than last? It doesn't work for opinion about a specific product, which is usually what people mean by Reddit sentiment analysis. For that, a few things help.
Hand-label a sample first. Read 50 to 100 comments, label them yourself, and measure how often your method agrees with you. Without that, you can't know whether 85% means anything.
Ask a stance question, not a mood question. A language model prompted with "Is this comment positive, negative or neutral about Cursor specifically, and is the author leaving, staying or undecided?" handles churn stories that lexicons can't. Check it against your hand labels too.
Weight by author. Count each author once per period, or cap their influence, before reporting percentages.
Filter by subreddit. A short list of subreddits that discuss your topic removes most false keyword matches. Find similar subreddits shows how to build one.
The cost of running this every day
Comment search costs 2 credits a page. Checking two pages for one keyword every hour costs:
2 pages × 24 hours × 30 days = 1,440 calls × 2 credits = 2,880 credits a month
That fits in the 25,000 credits of the $19 Starter plan, with room for seven more keywords. Posts work the same way through GET /v1/reddit/search. An agent can call both as reddit_search_comments and reddit_search on the MCP server at https://api.chatterapi.com/mcp.
Run it on your own topic
Swap "cursor ide" for your product name, run the script, then read the 20 comments it scored before you show anyone the percentage. Sign up with Google for 100 free credits, which covers 50 pages of comments. For the bigger picture of Reddit data in AI and analytics work, see Reddit data for AI.