Reddit discussions contain unfiltered opinions about products, brands, and industries that surveys and review sites miss. Tracking sentiment across subreddits manually is impractical -- threads move fast and span hundreds of communities. This tutorial builds a Python agent that uses the Scavio API to search Reddit for brand-related posts, applies a simple keyword-based sentiment classifier, and aggregates the results into a daily report with overall sentiment score, top positive threads, top negative threads, and trending concerns. The approach uses no ML models -- just regex-weighted keyword scoring that runs instantly and costs nothing beyond the API calls.
Prerequisites
- Python 3.8 or higher installed
- requests library installed
- A Scavio API key from scavio.dev
- Brand keywords or product names to track
Walkthrough
Step 1: Define brand keywords and sentiment lexicon
Set up the brand terms to search for and a simple weighted keyword lexicon for sentiment classification. Positive and negative words each carry a score.
BRAND_QUERIES = [
"scavio api",
"scavio search",
"scavio mcp"
]
POSITIVE_WORDS = ["love", "great", "fast", "reliable", "cheap", "easy", "best", "solid", "recommend", "impressed"]
NEGATIVE_WORDS = ["slow", "expensive", "broken", "hate", "worst", "terrible", "buggy", "unreliable", "scam", "overpriced"]Step 2: Fetch Reddit posts via the Scavio API
Search Reddit through the Scavio endpoint using the platform parameter. Collect post title, snippet, link, and subreddit for each result.
import os
import requests
API_KEY = os.environ.get("SCAVIO_API_KEY", "your_scavio_api_key")
def search_reddit(query: str) -> list[dict]:
r = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"query": query, "platform": "reddit"}
)
r.raise_for_status()
return r.json().get("organic_results", [])Step 3: Score sentiment for each post
Apply the keyword lexicon to each post title and snippet. Sum positive and negative hits to produce a net sentiment score per post.
def score_sentiment(text: str) -> dict:
text_lower = text.lower()
pos = sum(1 for w in POSITIVE_WORDS if w in text_lower)
neg = sum(1 for w in NEGATIVE_WORDS if w in text_lower)
net = pos - neg
if net > 0:
label = "positive"
elif net < 0:
label = "negative"
else:
label = "neutral"
return {"positive": pos, "negative": neg, "net": net, "label": label}
def analyze_post(post: dict) -> dict:
text = f"{post.get('title', '')} {post.get('snippet', '')}"
sentiment = score_sentiment(text)
return {
"title": post.get("title", ""),
"link": post.get("link", ""),
"subreddit": post.get("source", ""),
"sentiment": sentiment
}Step 4: Generate the daily sentiment report
Aggregate sentiment across all posts, identify top positive and negative threads, and write a summary report.
import json
from datetime import date
def generate_report(brand_queries: list[str]) -> dict:
all_posts = []
for query in brand_queries:
results = search_reddit(query)
analyzed = [analyze_post(p) for p in results]
all_posts.extend(analyzed)
total = len(all_posts)
positive = [p for p in all_posts if p["sentiment"]["label"] == "positive"]
negative = [p for p in all_posts if p["sentiment"]["label"] == "negative"]
neutral = [p for p in all_posts if p["sentiment"]["label"] == "neutral"]
report = {
"date": str(date.today()),
"total_posts": total,
"positive": len(positive),
"negative": len(negative),
"neutral": len(neutral),
"sentiment_ratio": round(len(positive) / total, 2) if total else 0,
"top_positive": sorted(positive, key=lambda p: p["sentiment"]["net"], reverse=True)[:3],
"top_negative": sorted(negative, key=lambda p: p["sentiment"]["net"])[:3]
}
with open(f"reddit_sentiment_{report['date']}.json", "w") as f:
json.dump(report, f, indent=2)
print(f"Report: {total} posts, {len(positive)} positive, {len(negative)} negative")
return report
generate_report(BRAND_QUERIES)Python Example
import os
import json
import requests
from datetime import date
API_KEY = os.environ.get("SCAVIO_API_KEY", "your_scavio_api_key")
ENDPOINT = "https://api.scavio.dev/api/v1/search"
POSITIVE = ["love", "great", "fast", "reliable", "cheap", "easy", "best", "solid", "recommend", "impressed"]
NEGATIVE = ["slow", "expensive", "broken", "hate", "worst", "terrible", "buggy", "unreliable", "scam", "overpriced"]
def search_reddit(query: str) -> list[dict]:
r = requests.post(
ENDPOINT,
headers={"x-api-key": API_KEY},
json={"query": query, "platform": "reddit"}
)
r.raise_for_status()
return r.json().get("organic_results", [])
def score(text: str) -> dict:
t = text.lower()
pos = sum(1 for w in POSITIVE if w in t)
neg = sum(1 for w in NEGATIVE if w in t)
net = pos - neg
label = "positive" if net > 0 else "negative" if net < 0 else "neutral"
return {"pos": pos, "neg": neg, "net": net, "label": label}
def track_sentiment(queries: list[str]) -> dict:
posts = []
for q in queries:
for result in search_reddit(q):
text = f"{result.get('title', '')} {result.get('snippet', '')}"
posts.append({
"title": result.get("title", ""),
"link": result.get("link", ""),
"sentiment": score(text)
})
total = len(posts)
pos_count = sum(1 for p in posts if p["sentiment"]["label"] == "positive")
neg_count = sum(1 for p in posts if p["sentiment"]["label"] == "negative")
report = {
"date": str(date.today()),
"total": total,
"positive": pos_count,
"negative": neg_count,
"neutral": total - pos_count - neg_count,
"ratio": round(pos_count / total, 2) if total else 0,
"top_positive": sorted([p for p in posts if p["sentiment"]["label"] == "positive"],
key=lambda x: x["sentiment"]["net"], reverse=True)[:3],
"top_negative": sorted([p for p in posts if p["sentiment"]["label"] == "negative"],
key=lambda x: x["sentiment"]["net"])[:3]
}
output = f"reddit_sentiment_{report['date']}.json"
with open(output, "w") as f:
json.dump(report, f, indent=2)
print(f"{total} posts: {pos_count} positive, {neg_count} negative")
return report
if __name__ == "__main__":
track_sentiment(["scavio api", "scavio search"])JavaScript Example
const fs = require("fs");
const API_KEY = process.env.SCAVIO_API_KEY || "your_scavio_api_key";
const ENDPOINT = "https://api.scavio.dev/api/v1/search";
const POSITIVE = ["love", "great", "fast", "reliable", "cheap", "easy", "best", "solid", "recommend"];
const NEGATIVE = ["slow", "expensive", "broken", "hate", "worst", "terrible", "buggy", "unreliable"];
async function searchReddit(query) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ query, platform: "reddit" })
});
const data = await res.json();
return data.organic_results || [];
}
function score(text) {
const t = text.toLowerCase();
const pos = POSITIVE.filter(w => t.includes(w)).length;
const neg = NEGATIVE.filter(w => t.includes(w)).length;
const net = pos - neg;
return { pos, neg, net, label: net > 0 ? "positive" : net < 0 ? "negative" : "neutral" };
}
async function trackSentiment(queries) {
const posts = [];
for (const q of queries) {
const results = await searchReddit(q);
for (const r of results) {
const text = `${r.title || ""} ${r.snippet || ""}`;
posts.push({ title: r.title, link: r.link, sentiment: score(text) });
}
}
const pos = posts.filter(p => p.sentiment.label === "positive").length;
const neg = posts.filter(p => p.sentiment.label === "negative").length;
console.log(`${posts.length} posts: ${pos} positive, ${neg} negative`);
const today = new Date().toISOString().split("T")[0];
fs.writeFileSync(`reddit_sentiment_${today}.json`, JSON.stringify({ date: today, total: posts.length, positive: pos, negative: neg }, null, 2));
}
trackSentiment(["scavio api", "scavio search"]).catch(console.error);Expected Output
{
"date": "2026-05-17",
"total": 24,
"positive": 9,
"negative": 4,
"neutral": 11,
"ratio": 0.38,
"top_positive": [
{
"title": "Scavio is the best cheap alternative to SerpApi",
"link": "https://reddit.com/r/webdev/comments/abc123",
"sentiment": { "pos": 2, "neg": 0, "net": 2, "label": "positive" }
}
],
"top_negative": [
{
"title": "Any search APIs that are not slow and overpriced?",
"link": "https://reddit.com/r/artificial/comments/def456",
"sentiment": { "pos": 0, "neg": 2, "net": -2, "label": "negative" }
}
]
}