Skip to content

Real-Time Brand Monitoring System

Build a comprehensive brand monitoring system that tracks mentions, analyzes sentiment, and alerts you to important conversations about your brand.


Overview

This recipe creates a production-ready brand monitoring system with:

  • Multi-keyword tracking - Brand names, products, misspellings
  • Sentiment scoring - Real-time sentiment analysis
  • Competitor comparison - Track competitors alongside your brand
  • Influencer alerts - Prioritize high-impact mentions
  • Health scoring - Weekly brand health metrics
  • Crisis detection - Early warning system

Architecture

┌─────────────────┐     ┌──────────────┐     ┌─────────────────┐
│  Keyword        │────▶│   Sentiment  │────▶│   Alert         │
│  Tracker        │     │   Analyzer   │     │   Router        │
└─────────────────┘     └──────────────┘     └─────────────────┘
        │                       │                     │
        ▼                       ▼                     ▼
┌─────────────────┐     ┌──────────────┐     ┌─────────────────┐
│  SQLite         │     │   Dashboard  │     │  Notifications  │
│  Storage        │     │   Generator  │     │  (Discord/etc)  │
└─────────────────┘     └──────────────┘     └─────────────────┘

Complete Implementation

Configuration

# brand_monitor_config.py
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class BrandConfig:
    """Configuration for brand monitoring."""

    # Brand keywords (include misspellings!)
    brand_keywords: list[str] = field(default_factory=lambda: [
        "xeepy",
        "xeepy.io",
        "@xeepy",
        "#xeepy",
    ])

    # Product keywords
    product_keywords: list[str] = field(default_factory=lambda: [
        "xeepy scraper",
        "xeepy bot",
        "xeepy automation",
    ])

    # Competitor keywords
    competitor_keywords: dict[str, list[str]] = field(default_factory=lambda: {
        "competitor_a": ["competitorA", "@competitorA"],
        "competitor_b": ["competitorB", "@competitorB"],
    })

    # Influencer thresholds
    influencer_follower_threshold: int = 10000
    viral_engagement_threshold: int = 100

    # Alert settings
    negative_sentiment_threshold: float = -0.3
    crisis_mention_spike: int = 50  # % increase triggers alert

    # Notification webhooks
    discord_webhook: Optional[str] = None
    slack_webhook: Optional[str] = None

    # Scanning interval (seconds)
    scan_interval: int = 300  # 5 minutes

Database Schema

# brand_monitor_db.py
import sqlite3
from datetime import datetime
from typing import Optional
from contextlib import contextmanager

class BrandMonitorDB:
    """SQLite database for brand monitoring data."""

    def __init__(self, db_path: str = "brand_monitor.db"):
        self.db_path = db_path
        self._init_db()

    @contextmanager
    def _get_connection(self):
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
        finally:
            conn.close()

    def _init_db(self):
        with self._get_connection() as conn:
            conn.executescript("""
                CREATE TABLE IF NOT EXISTS mentions (
                    id INTEGER PRIMARY KEY,
                    tweet_id TEXT UNIQUE,
                    author_username TEXT,
                    author_followers INTEGER,
                    text TEXT,
                    sentiment_score REAL,
                    sentiment_label TEXT,
                    keyword_matched TEXT,
                    keyword_category TEXT,
                    engagement_score INTEGER,
                    is_influencer BOOLEAN,
                    created_at TIMESTAMP,
                    scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                );

                CREATE TABLE IF NOT EXISTS daily_metrics (
                    id INTEGER PRIMARY KEY,
                    date DATE UNIQUE,
                    total_mentions INTEGER,
                    positive_mentions INTEGER,
                    neutral_mentions INTEGER,
                    negative_mentions INTEGER,
                    avg_sentiment REAL,
                    influencer_mentions INTEGER,
                    total_reach INTEGER,
                    brand_health_score REAL
                );

                CREATE TABLE IF NOT EXISTS competitor_metrics (
                    id INTEGER PRIMARY KEY,
                    date DATE,
                    competitor_name TEXT,
                    total_mentions INTEGER,
                    avg_sentiment REAL,
                    UNIQUE(date, competitor_name)
                );

                CREATE TABLE IF NOT EXISTS alerts (
                    id INTEGER PRIMARY KEY,
                    alert_type TEXT,
                    severity TEXT,
                    message TEXT,
                    tweet_id TEXT,
                    handled BOOLEAN DEFAULT FALSE,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                );

                CREATE INDEX IF NOT EXISTS idx_mentions_date 
                ON mentions(created_at);

                CREATE INDEX IF NOT EXISTS idx_mentions_sentiment 
                ON mentions(sentiment_label);
            """)
            conn.commit()

    def save_mention(self, mention: dict):
        with self._get_connection() as conn:
            conn.execute("""
                INSERT OR REPLACE INTO mentions 
                (tweet_id, author_username, author_followers, text,
                 sentiment_score, sentiment_label, keyword_matched,
                 keyword_category, engagement_score, is_influencer, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """, (
                mention['tweet_id'],
                mention['author_username'],
                mention['author_followers'],
                mention['text'],
                mention['sentiment_score'],
                mention['sentiment_label'],
                mention['keyword_matched'],
                mention['keyword_category'],
                mention['engagement_score'],
                mention['is_influencer'],
                mention['created_at']
            ))
            conn.commit()

    def get_mentions_since(self, since: datetime) -> list[dict]:
        with self._get_connection() as conn:
            rows = conn.execute("""
                SELECT * FROM mentions 
                WHERE scraped_at >= ?
                ORDER BY created_at DESC
            """, (since,)).fetchall()
            return [dict(row) for row in rows]

    def get_daily_stats(self, date: datetime) -> dict:
        with self._get_connection() as conn:
            row = conn.execute("""
                SELECT 
                    COUNT(*) as total,
                    SUM(CASE WHEN sentiment_label = 'positive' THEN 1 ELSE 0 END) as positive,
                    SUM(CASE WHEN sentiment_label = 'neutral' THEN 1 ELSE 0 END) as neutral,
                    SUM(CASE WHEN sentiment_label = 'negative' THEN 1 ELSE 0 END) as negative,
                    AVG(sentiment_score) as avg_sentiment,
                    SUM(CASE WHEN is_influencer THEN 1 ELSE 0 END) as influencer_mentions,
                    SUM(author_followers) as total_reach
                FROM mentions
                WHERE DATE(created_at) = DATE(?)
            """, (date,)).fetchone()
            return dict(row) if row else {}

Sentiment Analyzer

# sentiment_analyzer.py
from dataclasses import dataclass
from typing import Optional
import re

@dataclass
class SentimentResult:
    score: float  # -1.0 to 1.0
    label: str    # positive, neutral, negative
    confidence: float
    keywords_found: list[str]

class SentimentAnalyzer:
    """Analyze sentiment of brand mentions."""

    # Positive indicators
    POSITIVE_WORDS = {
        'love', 'amazing', 'awesome', 'great', 'excellent', 'fantastic',
        'best', 'perfect', 'helpful', 'useful', 'recommend', 'impressed',
        'thank', 'thanks', 'grateful', 'solved', 'works', 'easy', 'fast',
        '🔥', '❤️', '💯', '👍', '🙌', '✨', '🚀'
    }

    # Negative indicators
    NEGATIVE_WORDS = {
        'hate', 'terrible', 'awful', 'worst', 'bad', 'broken', 'bug',
        'issue', 'problem', 'disappointed', 'frustrating', 'useless',
        'scam', 'spam', 'fake', 'slow', 'crash', 'error', 'fail',
        '👎', '😡', '🤮', '💩', '😤'
    }

    # Intensifiers
    INTENSIFIERS = {'very', 'really', 'extremely', 'absolutely', 'totally'}

    # Negators
    NEGATORS = {"not", "isn't", "aren't", "wasn't", "weren't", "don't", 
                "doesn't", "didn't", "won't", "wouldn't", "can't", "couldn't"}

    def analyze(self, text: str) -> SentimentResult:
        """Analyze sentiment of text."""
        text_lower = text.lower()
        words = set(re.findall(r'\w+', text_lower))

        # Find sentiment words
        pos_found = words & self.POSITIVE_WORDS
        neg_found = words & self.NEGATIVE_WORDS

        # Check for emoji sentiment
        for emoji in self.POSITIVE_WORDS:
            if emoji in text:
                pos_found.add(emoji)
        for emoji in self.NEGATIVE_WORDS:
            if emoji in text:
                neg_found.add(emoji)

        # Calculate base score
        pos_score = len(pos_found)
        neg_score = len(neg_found)

        # Check for intensifiers
        intensifier_count = len(words & self.INTENSIFIERS)

        # Check for negators (flip sentiment)
        has_negator = bool(words & self.NEGATORS)

        # Calculate final score
        if pos_score == 0 and neg_score == 0:
            score = 0.0
            label = 'neutral'
            confidence = 0.5
        else:
            raw_score = (pos_score - neg_score) / (pos_score + neg_score)

            # Apply intensifier boost
            if intensifier_count > 0:
                raw_score *= (1 + 0.2 * intensifier_count)

            # Apply negator flip
            if has_negator:
                raw_score *= -0.5

            # Clamp to [-1, 1]
            score = max(-1.0, min(1.0, raw_score))

            # Determine label
            if score > 0.1:
                label = 'positive'
            elif score < -0.1:
                label = 'negative'
            else:
                label = 'neutral'

            # Confidence based on evidence
            confidence = min(1.0, (pos_score + neg_score) / 5)

        return SentimentResult(
            score=round(score, 3),
            label=label,
            confidence=round(confidence, 3),
            keywords_found=list(pos_found | neg_found)
        )

Main Monitor System

# brand_monitor.py
import asyncio
from datetime import datetime, timedelta
from typing import Optional
from dataclasses import dataclass

from xeepy import Xeepy
from xeepy.notifications import DiscordNotifier

from brand_monitor_config import BrandConfig
from brand_monitor_db import BrandMonitorDB
from sentiment_analyzer import SentimentAnalyzer

@dataclass
class MentionAlert:
    alert_type: str  # influencer, negative, viral, crisis
    severity: str    # low, medium, high, critical
    mention: dict
    message: str

class BrandMonitor:
    """Real-time brand monitoring system."""

    def __init__(self, config: BrandConfig):
        self.config = config
        self.db = BrandMonitorDB()
        self.sentiment = SentimentAnalyzer()
        self.notifier = None

        if config.discord_webhook:
            self.notifier = DiscordNotifier(config.discord_webhook)

        # Build keyword search query
        all_keywords = (
            config.brand_keywords + 
            config.product_keywords
        )
        self.search_query = " OR ".join(f'"{kw}"' for kw in all_keywords)

    async def scan_mentions(self) -> list[dict]:
        """Scan for new brand mentions."""
        async with Xeepy() as x:
            # Search for brand mentions
            results = await x.scrape.search(
                query=self.search_query,
                limit=100,
                result_type="Latest"
            )

            processed = []
            for tweet in results:
                # Analyze sentiment
                sentiment = self.sentiment.analyze(tweet.text)

                # Determine which keyword matched
                keyword_matched = self._find_matched_keyword(tweet.text)

                # Calculate engagement score
                engagement = (
                    tweet.like_count + 
                    tweet.retweet_count * 2 + 
                    tweet.reply_count * 3
                )

                # Check if influencer
                is_influencer = (
                    tweet.author.followers_count >= 
                    self.config.influencer_follower_threshold
                )

                mention = {
                    'tweet_id': tweet.id,
                    'author_username': tweet.author.username,
                    'author_followers': tweet.author.followers_count,
                    'text': tweet.text,
                    'sentiment_score': sentiment.score,
                    'sentiment_label': sentiment.label,
                    'keyword_matched': keyword_matched,
                    'keyword_category': self._get_keyword_category(keyword_matched),
                    'engagement_score': engagement,
                    'is_influencer': is_influencer,
                    'created_at': tweet.created_at,
                }

                # Save to database
                self.db.save_mention(mention)
                processed.append(mention)

                # Check for alerts
                alert = self._check_alerts(mention)
                if alert:
                    await self._send_alert(alert)

            return processed

    def _find_matched_keyword(self, text: str) -> str:
        """Find which keyword matched in the text."""
        text_lower = text.lower()
        for kw in self.config.brand_keywords:
            if kw.lower() in text_lower:
                return kw
        for kw in self.config.product_keywords:
            if kw.lower() in text_lower:
                return kw
        return "unknown"

    def _get_keyword_category(self, keyword: str) -> str:
        """Get category for matched keyword."""
        if keyword in self.config.brand_keywords:
            return "brand"
        elif keyword in self.config.product_keywords:
            return "product"
        return "other"

    def _check_alerts(self, mention: dict) -> Optional[MentionAlert]:
        """Check if mention triggers any alerts."""
        # Influencer mention
        if mention['is_influencer']:
            return MentionAlert(
                alert_type='influencer',
                severity='high' if mention['sentiment_label'] == 'negative' else 'medium',
                mention=mention,
                message=f"🌟 Influencer mention from @{mention['author_username']} "
                        f"({mention['author_followers']:,} followers)"
            )

        # Highly negative mention
        if mention['sentiment_score'] < self.config.negative_sentiment_threshold:
            return MentionAlert(
                alert_type='negative',
                severity='medium',
                mention=mention,
                message=f"⚠️ Negative mention detected (score: {mention['sentiment_score']})"
            )

        # Viral mention
        if mention['engagement_score'] > self.config.viral_engagement_threshold:
            return MentionAlert(
                alert_type='viral',
                severity='high',
                mention=mention,
                message=f"🚀 Viral mention! Engagement score: {mention['engagement_score']}"
            )

        return None

    async def _send_alert(self, alert: MentionAlert):
        """Send alert notification."""
        if not self.notifier:
            print(f"[ALERT] {alert.message}")
            return

        color = {
            'low': 0x3498db,      # Blue
            'medium': 0xf39c12,   # Yellow
            'high': 0xe74c3c,     # Red
            'critical': 0x9b59b6  # Purple
        }.get(alert.severity, 0x95a5a6)

        await self.notifier.send_embed(
            title=f"Brand Alert: {alert.alert_type.title()}",
            description=alert.message,
            color=color,
            fields=[
                {"name": "Author", "value": f"@{alert.mention['author_username']}", "inline": True},
                {"name": "Sentiment", "value": alert.mention['sentiment_label'], "inline": True},
                {"name": "Tweet", "value": alert.mention['text'][:200]},
            ],
            url=f"https://x.com/i/status/{alert.mention['tweet_id']}"
        )

    def calculate_brand_health(self, days: int = 7) -> dict:
        """Calculate brand health score."""
        since = datetime.now() - timedelta(days=days)
        mentions = self.db.get_mentions_since(since)

        if not mentions:
            return {'score': 0, 'grade': 'N/A', 'mentions': 0}

        # Components of brand health
        total = len(mentions)

        # 1. Sentiment score (0-40 points)
        avg_sentiment = sum(m['sentiment_score'] for m in mentions) / total
        sentiment_score = (avg_sentiment + 1) / 2 * 40  # Normalize to 0-40

        # 2. Volume trend (0-20 points)
        # Compare to previous period
        prev_since = since - timedelta(days=days)
        prev_mentions = self.db.get_mentions_since(prev_since)
        prev_count = len([m for m in prev_mentions if m['scraped_at'] < since])

        if prev_count > 0:
            growth = (total - prev_count) / prev_count
            volume_score = min(20, max(0, 10 + growth * 10))
        else:
            volume_score = 10

        # 3. Influencer engagement (0-20 points)
        influencer_mentions = sum(1 for m in mentions if m['is_influencer'])
        influencer_score = min(20, influencer_mentions * 2)

        # 4. Positive ratio (0-20 points)
        positive = sum(1 for m in mentions if m['sentiment_label'] == 'positive')
        positive_ratio = positive / total
        positive_score = positive_ratio * 20

        # Total score
        total_score = sentiment_score + volume_score + influencer_score + positive_score

        # Grade
        if total_score >= 80:
            grade = 'A'
        elif total_score >= 60:
            grade = 'B'
        elif total_score >= 40:
            grade = 'C'
        elif total_score >= 20:
            grade = 'D'
        else:
            grade = 'F'

        return {
            'score': round(total_score, 1),
            'grade': grade,
            'mentions': total,
            'components': {
                'sentiment': round(sentiment_score, 1),
                'volume': round(volume_score, 1),
                'influencer': round(influencer_score, 1),
                'positive_ratio': round(positive_score, 1)
            },
            'avg_sentiment': round(avg_sentiment, 3),
            'influencer_mentions': influencer_mentions,
            'positive_ratio': round(positive_ratio, 3)
        }

    async def run_continuous(self):
        """Run continuous monitoring loop."""
        print(f"🚀 Starting brand monitor...")
        print(f"   Keywords: {self.search_query[:50]}...")
        print(f"   Interval: {self.config.scan_interval}s")

        while True:
            try:
                mentions = await self.scan_mentions()
                print(f"[{datetime.now()}] Scanned {len(mentions)} mentions")

                # Calculate health every hour
                if datetime.now().minute == 0:
                    health = self.calculate_brand_health()
                    print(f"   Brand Health: {health['grade']} ({health['score']}/100)")

            except Exception as e:
                print(f"Error during scan: {e}")

            await asyncio.sleep(self.config.scan_interval)

Dashboard Generator

# brand_dashboard.py
from datetime import datetime, timedelta
from brand_monitor_db import BrandMonitorDB

class BrandDashboard:
    """Generate brand monitoring dashboard."""

    def __init__(self, db: BrandMonitorDB):
        self.db = db

    def generate_report(self, days: int = 7) -> str:
        """Generate markdown report."""
        since = datetime.now() - timedelta(days=days)
        mentions = self.db.get_mentions_since(since)

        # Calculate stats
        total = len(mentions)
        positive = sum(1 for m in mentions if m['sentiment_label'] == 'positive')
        negative = sum(1 for m in mentions if m['sentiment_label'] == 'negative')
        neutral = total - positive - negative

        avg_sentiment = sum(m['sentiment_score'] for m in mentions) / total if total else 0

        influencer_mentions = [m for m in mentions if m['is_influencer']]

        # Top mentions by engagement
        top_mentions = sorted(mentions, key=lambda m: m['engagement_score'], reverse=True)[:5]

        report = f"""
# Brand Monitoring Report
**Period:** {since.strftime('%Y-%m-%d')} to {datetime.now().strftime('%Y-%m-%d')}

## Overview

| Metric | Value |
|--------|-------|
| Total Mentions | {total:,} |
| Positive | {positive:,} ({positive/total*100:.1f}%) |
| Neutral | {neutral:,} ({neutral/total*100:.1f}%) |
| Negative | {negative:,} ({negative/total*100:.1f}%) |
| Avg Sentiment | {avg_sentiment:.2f} |
| Influencer Mentions | {len(influencer_mentions):,} |

## Top Mentions

"""
        for i, m in enumerate(top_mentions, 1):
            report += f"""
### {i}. @{m['author_username']} ({m['author_followers']:,} followers)
> {m['text'][:200]}...

- Sentiment: {m['sentiment_label']} ({m['sentiment_score']})
- Engagement: {m['engagement_score']}
- [View Tweet](https://x.com/i/status/{m['tweet_id']})

"""

        return report

Usage Example

# main.py
import asyncio
from brand_monitor import BrandMonitor
from brand_monitor_config import BrandConfig

async def main():
    # Configure for your brand
    config = BrandConfig(
        brand_keywords=[
            "YourBrand",
            "yourbrand",
            "@YourBrand",
            "#YourBrand",
        ],
        product_keywords=[
            "YourProduct",
            "your product",
        ],
        discord_webhook="https://discord.com/api/webhooks/...",
        scan_interval=300,  # 5 minutes
    )

    monitor = BrandMonitor(config)

    # Run continuous monitoring
    await monitor.run_continuous()

if __name__ == "__main__":
    asyncio.run(main())

Advanced Features

Competitor Comparison

async def compare_competitors(self):
    """Compare brand metrics against competitors."""
    results = {'brand': await self._get_brand_metrics()}

    for name, keywords in self.config.competitor_keywords.items():
        query = " OR ".join(f'"{kw}"' for kw in keywords)

        async with Xeepy() as x:
            tweets = await x.scrape.search(query, limit=100)

            sentiments = [self.sentiment.analyze(t.text) for t in tweets]
            avg_sentiment = sum(s.score for s in sentiments) / len(sentiments)

            results[name] = {
                'mentions': len(tweets),
                'avg_sentiment': avg_sentiment,
                'positive_ratio': sum(1 for s in sentiments if s.label == 'positive') / len(sentiments)
            }

    return results

Weekly Report Automation

async def send_weekly_report():
    """Send weekly brand report."""
    monitor = BrandMonitor(BrandConfig())
    dashboard = BrandDashboard(monitor.db)

    report = dashboard.generate_report(days=7)
    health = monitor.calculate_brand_health(days=7)

    # Send via Discord
    await monitor.notifier.send_embed(
        title="📊 Weekly Brand Report",
        description=f"Brand Health: **{health['grade']}** ({health['score']}/100)",
        fields=[
            {"name": "Total Mentions", "value": str(health['mentions']), "inline": True},
            {"name": "Avg Sentiment", "value": f"{health['avg_sentiment']:.2f}", "inline": True},
        ]
    )

Best Practices

Keyword Selection

  • Include common misspellings
  • Add both @ mentions and hashtags
  • Consider product names separately
  • Monitor CEO/founder names for reputation

Rate Limiting

  • Space out competitor scans
  • Use longer intervals during low-activity hours
  • Cache results to reduce API calls

Response Strategy

  • Respond to influencers within 1 hour
  • Acknowledge negative feedback quickly
  • Amplify positive mentions