Build an app sentiment alert pipeline that monitors how users talk about your app across the web, detects sentiment shifts by analyzing search result snippets, and triggers alerts when negative sentiment spikes or positive sentiment drops. App store reviews only capture a fraction of user sentiment. Users also discuss apps on Reddit, review blogs, forums, and social media. By searching for your app name daily and analyzing the tone of search results, you catch sentiment shifts from all these sources through a single API.
Prerequisites
- Python 3.8+ installed
- requests library installed
- A Scavio API key from scavio.dev
- App names to monitor
Walkthrough
Step 1: Define monitoring targets
Configure the apps and search queries used for sentiment monitoring.
import os, requests, json, datetime
API_KEY = os.environ['SCAVIO_API_KEY']
MONITORED_APPS = [
{'name': 'MyApp', 'queries': ['MyApp review', 'MyApp problems', 'MyApp vs competitor']},
{'name': 'CompetitorApp', 'queries': ['CompetitorApp review', 'CompetitorApp issues']},
]
POSITIVE_WORDS = ['love', 'great', 'excellent', 'best', 'amazing', 'fast', 'reliable', 'smooth', 'perfect', 'recommend']
NEGATIVE_WORDS = ['slow', 'buggy', 'crash', 'terrible', 'worst', 'broken', 'expensive', 'frustrating', 'disappointed', 'awful']
HISTORY_FILE = 'sentiment_history.json'
print(f'Monitoring {len(MONITORED_APPS)} apps')Step 2: Search and collect mentions
Search for each app across Google and Reddit to collect current mentions.
import time
def collect_mentions(app: dict) -> list:
mentions = []
for query in app['queries']:
for platform in ['google', 'reddit']:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY},
json={'platform': platform, 'query': query}, timeout=15)
results = resp.json().get('organic_results', [])
for r in results[:5]:
mentions.append({
'platform': platform,
'title': r.get('title', ''),
'snippet': r.get('snippet', ''),
'url': r.get('link', ''),
'query': query,
})
time.sleep(0.2)
return mentions
mentions = collect_mentions(MONITORED_APPS[0])
print(f'Collected {len(mentions)} mentions for {MONITORED_APPS[0]["name"]}')Step 3: Analyze sentiment
Score the sentiment of each mention based on positive and negative word presence.
def analyze_mention_sentiment(mention: dict) -> dict:
text = (mention.get('title', '') + ' ' + mention.get('snippet', '')).lower()
pos = sum(1 for w in POSITIVE_WORDS if w in text)
neg = sum(1 for w in NEGATIVE_WORDS if w in text)
if pos > neg:
sentiment = 'positive'
elif neg > pos:
sentiment = 'negative'
else:
sentiment = 'neutral'
return {
**mention,
'sentiment': sentiment,
'pos_score': pos,
'neg_score': neg,
}
def batch_sentiment(mentions: list) -> dict:
analyzed = [analyze_mention_sentiment(m) for m in mentions]
pos = sum(1 for a in analyzed if a['sentiment'] == 'positive')
neg = sum(1 for a in analyzed if a['sentiment'] == 'negative')
neutral = sum(1 for a in analyzed if a['sentiment'] == 'neutral')
total = len(analyzed)
return {
'total': total,
'positive': pos,
'negative': neg,
'neutral': neutral,
'sentiment_score': round((pos - neg) / total, 2) if total > 0 else 0,
'mentions': analyzed,
}
result = batch_sentiment(mentions)
print(f"Sentiment: +{result['positive']} -{result['negative']} ={result['neutral']}")
print(f"Score: {result['sentiment_score']}")Step 4: Detect sentiment shifts
Compare current sentiment against historical baseline to detect significant changes.
def store_sentiment(app_name: str, sentiment: dict):
history = []
try:
with open(HISTORY_FILE) as f:
history = json.load(f)
except FileNotFoundError:
pass
history.append({
'app': app_name,
'date': datetime.date.today().isoformat(),
'score': sentiment['sentiment_score'],
'positive': sentiment['positive'],
'negative': sentiment['negative'],
'total': sentiment['total'],
})
with open(HISTORY_FILE, 'w') as f:
json.dump(history, f, indent=2)
def detect_shift(app_name: str, current_score: float) -> dict:
try:
with open(HISTORY_FILE) as f:
history = json.load(f)
except FileNotFoundError:
return {'shift': 'no_baseline'}
entries = [h for h in history if h['app'] == app_name]
if len(entries) < 3:
return {'shift': 'insufficient_data'}
recent = entries[-5:]
avg = sum(e['score'] for e in recent) / len(recent)
change = current_score - avg
return {
'shift': 'negative' if change < -0.15 else 'positive' if change > 0.15 else 'stable',
'change': round(change, 2),
'baseline_avg': round(avg, 2),
'current': current_score,
}
store_sentiment('MyApp', result)
shift = detect_shift('MyApp', result['sentiment_score'])
print(f"Shift: {shift['shift']} (change: {shift.get('change', 'N/A')})")Step 5: Generate alerts
Trigger alerts when sentiment drops below threshold or shifts negatively.
def check_alerts(apps: list) -> list:
alerts = []
for app in apps:
mentions = collect_mentions(app)
sentiment = batch_sentiment(mentions)
store_sentiment(app['name'], sentiment)
shift = detect_shift(app['name'], sentiment['sentiment_score'])
if shift['shift'] == 'negative':
alert = {
'app': app['name'],
'type': 'sentiment_drop',
'score': sentiment['sentiment_score'],
'change': shift.get('change', 0),
'negative_mentions': [m['title'][:60] for m in sentiment['mentions'] if m['sentiment'] == 'negative'][:3],
}
alerts.append(alert)
print(f"ALERT: {app['name']} sentiment dropped ({shift['change']})")
for m in alert['negative_mentions']:
print(f" - {m}")
else:
print(f"{app['name']}: {shift['shift']} (score: {sentiment['sentiment_score']})")
return alerts
alerts = check_alerts(MONITORED_APPS)Python Example
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
POS = ['love', 'great', 'best', 'fast']
NEG = ['slow', 'buggy', 'crash', 'terrible']
def sentiment(app):
data = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': f'{app} review'}).json()
results = data.get('organic_results', [])[:5]
pos = neg = 0
for r in results:
text = (r.get('snippet', '')).lower()
pos += sum(1 for w in POS if w in text)
neg += sum(1 for w in NEG if w in text)
return {'app': app, 'positive': pos, 'negative': neg}
print(sentiment('Slack'))JavaScript Example
const H = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};
const POS = ['love', 'great', 'best', 'fast'];
const NEG = ['slow', 'buggy', 'crash', 'terrible'];
async function sentiment(app) {
const r = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: H,
body: JSON.stringify({platform: 'google', query: `${app} review`})
});
const results = (await r.json()).organic_results || [];
let pos = 0, neg = 0;
results.slice(0, 5).forEach(r => {
const t = (r.snippet || '').toLowerCase();
POS.forEach(w => { if (t.includes(w)) pos++; });
NEG.forEach(w => { if (t.includes(w)) neg++; });
});
return {app, positive: pos, negative: neg};
}
sentiment('Slack').then(console.log);Expected Output
An automated sentiment monitoring pipeline that tracks app mentions across web and Reddit, detects sentiment shifts, and triggers alerts when negative sentiment spikes.