How to Build a Sentiment Dashboard from Reddit + Hacker News (2026)
Developer sentiment is the leading indicator every dev-tools founder wishes they had. Twitter/X is noise, LinkedIn is performance art, but Hacker News and Reddit's technical subs are where people say what they actually think about your product, your stack, or the launch you fumbled last week. The problem: aggregating that signal manually is a soul-crushing job.
Some 2026 context on why this matters more than it did three years ago. Hacker News publishes roughly 900-1,200 stories per day with average thread lengths north of 60 comments. Reddit crossed 100M daily active users in 2024 and shows ~3 million comments per day across the 2,000+ technical subreddits most relevant to a dev-tools company. A developer-survey by JetBrains (2025, n=26,348) found that 71% of respondents named Hacker News and Reddit as their "first three sources" when evaluating new infrastructure or tooling. That is a bigger information-gathering role than product pages, vendor documentation, or even YouTube. If you ship a developer product and you are not listening to both, you are letting competitors shape the narrative unchallenged. The mental model most founders carry — "I'll just check HN when I remember" — produces exactly the right amount of information to confirm whatever mood you are in when you check.
In this post we build a sentiment dashboard that monitors Hacker News plus a configurable set of subreddits, runs every comment through an AI sentiment analyzer, and surfaces the themes trending up or down. Runs entirely on Apify plus a Postgres database plus a Grafana dashboard. The end state: a single pane of glass that shows, at any moment, "what is the internet saying about my product and my category?" with drill-down by topic, time, and source. You can replace three $300/month social-listening subscriptions with a stack that costs about $25/month in compute and owns its own data.
Why this is hard
- Reddit API restrictions post-2023. Reddit's pricing changes killed most free Reddit scrapers. You need a route that handles throttling and auth renewal.
- Hacker News has no native sentiment scores. Upvotes are not sentiment. A 300-point story with 400 comments can be 90% "this is garbage."
- Comment threading. A comment at depth 4 disagreeing with its parent reads differently than one at depth 1. Flat sentiment averages lie.
- AI sentiment quality. Off-the-shelf sentiment models (VADER, TextBlob) fail on sarcasm, tech jargon, and mixed polarity. You need an LLM-grade analyzer.
- Topic extraction drift. "Postgres" as a topic at the string level is easy. "Postgres 17's new logical replication bug that affects read replicas but only under logical decoding with streaming enabled" is what you actually need to cluster on. Naive keyword extraction gets you buried in overlapping topics.
- Temporal drift in baselines. What sentiment looks like on HN in December is not what it looks like in June (fewer comments, different moods, more end-of-year layoffs chatter). Alerts need seasonally-adjusted baselines, not hardcoded thresholds.
The architecture
[hacker-news-scraper] --> stories + comments
+
[reddit scraper / keyword watcher] (external)
|
v
[Raw comments JSONL]
|
v
[ai-sentiment-analyzer] --> per-comment sentiment + topic
|
v
[Postgres]
|
v
[Grafana dashboard]
Step 1: Pull Hacker News
The hacker-news-scraper pulls stories and their comment trees with engagement metadata.
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("nexgendata/hacker-news-scraper").call(run_input={
"feeds": ["top", "new", "best"],
"keywords": ["postgres", "rust", "openai", "apify", "anthropic"],
"max_stories": 100,
"include_comments": True,
"max_comments_per_story": 200,
})
stories = list(client.dataset(run["defaultDatasetId"]).iterate_items())
Each story contains nested comment objects. Flatten:
def flatten(story):
out = []
def walk(c, depth, parent_id, story_id):
out.append({
"source": "hn",
"story_id": story_id,
"comment_id": c["id"],
"parent_id": parent_id,
"depth": depth,
"text": c["text"],
"author": c["by"],
"timestamp": c["time"],
})
for child in c.get("children", []):
walk(child, depth+1, c["id"], story_id)
for c in story.get("comments", []):
walk(c, 0, story["id"], story["id"])
return out
comments = [row for s in stories for row in flatten(s)]
Step 2: Run sentiment analysis
The ai-sentiment-analyzer handles sarcasm and mixed polarity better than classic sentiment libraries. It returns polarity (-1 to +1), subjectivity, and a one-line topic summary.
sent_run = client.actor("nexgendata/ai-sentiment-analyzer").call(run_input={
"texts": [{"id": c["comment_id"], "text": c["text"]} for c in comments],
"include_topic": True,
"language": "en",
})
sentiments = {s["id"]: s for s in client.dataset(sent_run["defaultDatasetId"]).iterate_items()}
Result per comment:
{
"id": "39847112",
"polarity": -0.62,
"subjectivity": 0.8,
"label": "negative",
"topic": "postgres 17 performance regressions",
"confidence": 0.91
}
Step 3: Persist to Postgres
import psycopg
with psycopg.connect("postgresql://...") as con:
with con.cursor() as cur:
for c in comments:
s = sentiments.get(c["comment_id"], {})
cur.execute("""
INSERT INTO comments
(source, story_id, comment_id, text, polarity, label, topic, ts)
VALUES (%s,%s,%s,%s,%s,%s,%s,to_timestamp(%s))
ON CONFLICT (comment_id) DO UPDATE
SET polarity=EXCLUDED.polarity, label=EXCLUDED.label
""", (c["source"], c["story_id"], c["comment_id"], c["text"],
s.get("polarity"), s.get("label"), s.get("topic"), c["timestamp"]))
Step 4: Build the dashboard
Grafana queries against Postgres:
Top negative topics (last 24h):
SELECT topic, COUNT(*) AS negatives, AVG(polarity) AS avg_polarity
FROM comments
WHERE label = 'negative' AND ts > now() - INTERVAL '24 hours'
GROUP BY topic
ORDER BY negatives DESC
LIMIT 20;
Sentiment over time for "apify":
SELECT date_trunc('hour', ts) AS hour, AVG(polarity) AS sentiment
FROM comments
WHERE text ILIKE '%apify%'
GROUP BY 1 ORDER BY 1;
Pin both panels to a dashboard and you have an early-warning system. When sentiment dips 2 standard deviations below baseline, trigger a PagerDuty alert. (Yes, for real.)
Here is a more sophisticated rolling-z-score alert that actually works in production — it uses a 14-day trailing baseline per keyword, so seasonal noise doesn't trigger pages:
import psycopg, os, json, urllib.request
PAGERDUTY_KEY = os.environ["PAGERDUTY_INTEGRATION_KEY"]
KEYWORDS = ["apify", "nexgendata", "postgres 17"]
def page(keyword, z, current, baseline):
event = {
"routing_key": PAGERDUTY_KEY,
"event_action": "trigger",
"payload": {
"summary": f"Sentiment anomaly on '{keyword}' (z={z:.2f})",
"severity": "warning",
"source": "sentiment-dashboard",
"custom_details": {
"keyword": keyword,
"current_polarity": current,
"14d_baseline": baseline,
},
},
}
req = urllib.request.Request(
"https://events.pagerduty.com/v2/enqueue",
data=json.dumps(event).encode(),
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req)
with psycopg.connect(os.environ["DATABASE_URL"]) as con, con.cursor() as cur:
for kw in KEYWORDS:
cur.execute("""
WITH recent AS (
SELECT AVG(polarity) AS mean FROM comments
WHERE text ILIKE %s AND ts > now() - INTERVAL '24 hours'
),
baseline AS (
SELECT AVG(polarity) AS mean, STDDEV(polarity) AS sd FROM comments
WHERE text ILIKE %s AND ts > now() - INTERVAL '14 days'
AND ts < now() - INTERVAL '24 hours'
)
SELECT recent.mean, baseline.mean, baseline.sd FROM recent, baseline
""", (f"%{kw}%", f"%{kw}%"))
cur_mean, base_mean, base_sd = cur.fetchone()
if None in (cur_mean, base_mean, base_sd) or base_sd == 0:
continue
z = (cur_mean - base_mean) / base_sd
if z < -2:
page(kw, z, cur_mean, base_mean)
print(f"ALERT: {kw} z={z:.2f}")
Run this on a 2-hour schedule from GitHub Actions or a cron container. The 24h / 14d baseline is a reasonable default, but if your keyword's chatter is sparse, widen the window to 72h / 30d.
Step 5: Extend to Reddit
Reddit's API is locked down post-2023, but two paths work:
- Use Reddit's official Data API with an OAuth client (free for <100 req/min).
- Use Apify's Reddit actor (not ours — this is a commodity one on the store) to scrape public subreddits.
For the sentiment pipeline, only the comment text and timestamp matter. Feed the output through the same ai-sentiment-analyzer call and write to the comments table with source = 'reddit'.
Use cases
1. Dev-tool founder listening post. A database company monitors five competitor names plus their own. Weekly, the dashboard surfaces the top five complaints per product — direct feature-roadmap input.
2. Release post-mortem. A framework team launched a v2 and saw sentiment crater. Grafana showed 70% of negativity clustered around a breaking change nobody mentioned in the blog post. Addressed in a follow-up patch.
3. Hiring pulse. A recruiter monitors "remote work" and "layoffs" threads, detecting sentiment swings that correlate with candidate availability windows.
4. Crypto/macro signal. A trader runs the pipeline over /r/cryptocurrency and WSB to front-run retail sentiment shifts.
5. Community manager priority queue. A DevRel team for an open-source framework uses the dashboard to route themselves toward the threads most worth engaging with. Instead of reading all 40 daily mentions, they look at the 5 with polarity < -0.4 and confidence > 0.8. Response time to legitimate criticism dropped from 3 days to 6 hours, and community sentiment metrics recovered measurably over the following quarter.
6. PR crisis early warning. A consumer SaaS company was quietly getting clobbered on /r/SideProject over a pricing change. The sentiment dashboard flagged the spike 9 hours before it hit their Twitter mentions. They rolled back, posted a clear statement, and contained what would have been a 48-hour news cycle into a morning.
Pricing comparison
| Service | Monthly cost (100k comments) | Reddit? | HN? | AI analysis? |
|---|---|---|---|---|
| Brand24 | $149 | Limited | No | Basic |
| Brandwatch | $1000+ | Yes | Yes | Yes |
| Mention | $41+ | Limited | Limited | Basic |
| Custom (Reddit API + OpenAI) | $60 + eng | Yes | Yes | Yes |
| Apify combo | ~$25 | Via add-on | Yes | Yes |
Common pitfalls
Sentiment pipelines look simple and are not. These are the traps most teams fall into:
-
Comment depth matters. Weight top-level comments more than replies in rollups — replies pile on and amplify whatever the top comment said, creating an echo-chamber effect in your averages. A common heuristic: weight by
1/(depth+1)or only aggregate top-level + depth-1 replies. - Sarcasm on HN is peak. Manually spot-check 20 "negative" comments weekly; if polarity calibration drifts, tune the analyzer prompt. The canonical failure: "oh great, another JavaScript framework" gets tagged positive by naive models because "great" is a strong positive signal.
-
Deletion handling. HN comments get deleted. Re-run your fetcher on a 48-hour lag to capture final state. Reddit deletions are even messier — a deleted user's entire history may vanish, creating gaps in your time series. Store a
first_seen_tsand treat comments as immutable once captured. - Shills and sock puppets. Any subreddit above 100k subs has them. A rash of positive comments from 12-hour-old accounts is a signal, not sentiment. Track account age and karma alongside the comment, and flag suspicious clusters.
- Brigades and cross-posts. A thread linked from Twitter/X or another subreddit can flood with off-topic noise. Watch for sudden comment-volume spikes that are out of proportion to upvote ratio — those are usually brigades, not organic conversation.
- Jargon and acronyms. Tech subreddits have massive in-group vocabulary. "Skill issue" is a pejorative, "NGMI" is negative, "chad" is positive. A general-purpose sentiment model will misread all three. Either fine-tune or prompt-engineer for your domain.
-
Language mixing. Subs like
/r/indiaand/r/brasilhave heavy code-mixing (English + Hindi/Portuguese). Your analyzer should either handle multilingual or you need language detection + filtering upstream. - Quote extraction. A comment that quotes another ("> the GC pauses are unacceptable") may be sarcastic agreement or genuine criticism. Naive analyzers treat the quote as the comment's own text. Strip or tag quoted lines.
- Sentiment vs. engagement. High-engagement threads skew negative (controversy drives comments). If you are tracking rollups, normalize sentiment by expected baseline for that story's score, not the flat average.
- Timezone biases. HN comments peak in US business hours; a sentiment average skewed by time-of-day tells you more about who is online than what they think. Aggregate by rolling 24h window, not calendar day.
How NexGenData handles this
The hacker-news-scraper and ai-sentiment-analyzer were designed to work together, which drives some specific choices:
Full comment tree with depth metadata. Every comment in the output includes its depth, parent ID, and the path from root. You can weight, filter, or flatten however your analysis requires — no restructuring needed.
Incremental fetching. Pass a since timestamp and the scraper returns only new comments since your last run. You are not paying to re-fetch the same 10,000 comments every hour.
Deletion-aware re-runs. A follow-up mode: "refresh" re-examines comments you already have and updates their status if they have been deleted or edited. Keeps your dataset honest.
LLM-grade sentiment with topic extraction. The ai-sentiment-analyzer uses a prompted LLM rather than VADER/TextBlob, which handles sarcasm, tech jargon, and mixed polarity far better. It also returns a topic string, not just polarity, so clustering by theme is immediate.
Batch cost efficiency. The analyzer batches up to 100 texts per API call and uses a small, fast model (the equivalent of GPT-4o-mini class), so per-comment cost is fractions of a cent.
Pay-per-result pricing for both actors. 10,000 comments scraped and sentiment-analyzed costs roughly $3-5 all-in. A comparable Brandwatch subscription that covers the same volume is $1,000+/month with multi-seat minimums.
Conclusion
A sentiment dashboard is one of the highest-leverage projects a solo founder can build. It takes half a day, costs <$30/month, and replaces a surprising amount of anecdotal "what is the vibe" anxiety with actual data.
Build it with:
- Hacker News Scraper — stories, comments, engagement history.
- AI Sentiment Analyzer — LLM-grade polarity + topic extraction.
- AP News Scraper — layer in mainstream news signal alongside developer chatter.
FAQ
Is scraping Hacker News allowed?
Yes, within reason. HN has an official Firebase-backed API at hn.algolia.com and hacker-news.firebaseio.com that anyone can use for free. The Apify actor uses these endpoints and adds structure, pagination, and dedup. HN does not require an API key. Be polite — don't hammer at 100 req/sec.
Can I still scrape Reddit in 2026?
Yes, but carefully. Reddit's Data API is free for <100 req/min with OAuth; higher volumes are paid. For public subreddits, the official API is the safest route. Third-party scrapers exist but carry more risk as Reddit tightens enforcement. For this use case (sentiment over a few focused subs), the Data API is plenty.
Which LLM should I use for sentiment?
For this use case, a small fast model (GPT-4o-mini, Claude Haiku, or Llama 3.1 8B hosted on Groq or Cerebras) is more than enough. The ai-sentiment-analyzer actor uses a prompt-engineered equivalent. The task is not "write Shakespeare," it's "classify this 200-character text reliably." Cheap models win on cost per comment without a meaningful accuracy loss.
How do I handle ambiguous polarity?
Always store the confidence score alongside polarity. In dashboards, filter to confidence > 0.7 for rollups. Comments with low confidence are often genuinely ambiguous (sarcasm, mixed signals) and should go in a "needs human review" bucket, not a statistic.
What about non-English posts?
The ai-sentiment-analyzer supports most major languages out of the box. For subreddits with heavy code-mixing, detect language per-comment and process only those above a confidence threshold.
Can I share this dashboard with my team?
Grafana supports multi-user dashboards with role-based permissions. A common pattern: give the CEO read-only access to a "Sentiment overview" board, give product managers access to the per-topic drilldowns, and keep the alert-config board locked to the ops team.
How accurate is LLM sentiment really?
On developer-heavy text, LLM sentiment with a domain-tuned prompt runs 85-92% agreement with human labels on a hand-coded test set. VADER and TextBlob on the same corpus score 58-65%. The gap is enormous for sarcasm and technical jargon, which is where developer chatter lives.
Top comments (0)