Tutorial

How to Build a Competitive SERP Monitor

Monitor competitor rankings across keywords daily, detect position changes, and alert on new entrants. Python pipeline at $0.005/check.

Rank tracking tools charge $50-300/month and update once per day at best. A custom competitive SERP monitor checks exactly the keywords you care about, at your own frequency, and alerts on the changes that matter to your business. This tutorial builds a monitor that tracks multiple competitors across keyword sets at $0.005/check, with Slack or email alerting on position swings.

Prerequisites

  • Python 3.8+
  • requests library
  • A Scavio API key from scavio.dev
  • Competitor domains and target keywords

Walkthrough

Step 1: Define competitors and keywords

Set up the monitoring configuration.

Python
import os, requests, json, sqlite3
from datetime import datetime

API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}

CONFIG = {
    'competitors': ['serpapi.com', 'dataforseo.com', 'serper.dev', 'exa.ai', 'tavily.com'],
    'keywords': [
        'serp api', 'search api for developers', 'google search api python',
        'web scraping api 2026', 'ai agent search api'
    ]
}

db = sqlite3.connect('serp_monitor.db')
db.execute('''CREATE TABLE IF NOT EXISTS rankings (
    keyword TEXT, domain TEXT, position INTEGER,
    checked_at TEXT, title TEXT
)''')
db.commit()
print(f'Monitoring {len(CONFIG["competitors"])} competitors across {len(CONFIG["keywords"])} keywords')
print(f'Daily cost: ${len(CONFIG["keywords"]) * 0.005:.3f}')

Step 2: Check rankings for all keywords

Run a SERP check for each keyword and record competitor positions.

Python
def check_rankings(keywords, competitors):
    results = []
    for kw in keywords:
        data = requests.post('https://api.scavio.dev/api/v1/search',
            headers=SH, json={'query': kw, 'country_code': 'us'}).json()
        organic = data.get('organic_results', [])
        now = datetime.now().isoformat()
        for r in organic[:20]:
            domain = r.get('link', '').split('/')[2] if r.get('link') else ''
            domain = domain.replace('www.', '')
            if domain in competitors:
                pos = r.get('position', 99)
                db.execute('INSERT INTO rankings VALUES (?,?,?,?,?)',
                    (kw, domain, pos, now, r.get('title', '')[:80]))
                results.append({'keyword': kw, 'domain': domain, 'position': pos})
        db.commit()
    print(f'Checked {len(keywords)} keywords. Cost: ${len(keywords) * 0.005:.3f}')
    return results

ranks = check_rankings(CONFIG['keywords'], CONFIG['competitors'])
for r in ranks[:10]:
    print(f'  {r["keyword"]:30} | {r["domain"]:20} | #{r["position"]}')

Step 3: Detect position changes

Compare current check against previous to find movers.

Python
def detect_changes(keyword, domain, threshold=3):
    rows = db.execute(
        'SELECT position, checked_at FROM rankings WHERE keyword=? AND domain=? ORDER BY checked_at DESC LIMIT 2',
        (keyword, domain)).fetchall()
    if len(rows) < 2: return None
    current, previous = rows[0][0], rows[1][0]
    change = previous - current  # positive = improved
    if abs(change) >= threshold:
        return {'keyword': keyword, 'domain': domain,
                'previous': previous, 'current': current, 'change': change}
    return None

def alert_changes(keywords, competitors, threshold=3):
    alerts = []
    for kw in keywords:
        for comp in competitors:
            change = detect_changes(kw, comp, threshold)
            if change: alerts.append(change)
    if alerts:
        print(f'\n{len(alerts)} position changes detected:')
        for a in alerts:
            direction = 'UP' if a['change'] > 0 else 'DOWN'
            print(f'  {a["domain"]:20} | {a["keyword"]:25} | #{a["previous"]} -> #{a["current"]} ({direction} {abs(a["change"])})')
    else:
        print('No significant position changes.')
    return alerts

alert_changes(CONFIG['keywords'], CONFIG['competitors'])

Step 4: Generate competitive dashboard data

Build a summary view of the competitive landscape.

Python
def dashboard(competitors, keywords):
    print(f'\n{"Keyword":30} | ', end='')
    for c in competitors[:5]: print(f'{c[:12]:12} | ', end='')
    print()
    print('-' * (32 + 15 * len(competitors[:5])))
    for kw in keywords:
        print(f'{kw[:30]:30} | ', end='')
        for comp in competitors[:5]:
            row = db.execute(
                'SELECT position FROM rankings WHERE keyword=? AND domain=? ORDER BY checked_at DESC LIMIT 1',
                (kw, comp)).fetchone()
            pos = f'#{row[0]}' if row else '-'
            print(f'{pos:12} | ', end='')
        print()
    total_checks = len(keywords)
    print(f'\nMonthly cost estimate: ${total_checks * 0.005 * 30:.2f} (daily) or ${total_checks * 0.005 * 7:.2f} (weekly)')

# Run a check first, then display
check_rankings(CONFIG['keywords'], CONFIG['competitors'])
dashboard(CONFIG['competitors'], CONFIG['keywords'])

Python Example

Python
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}

def check(keyword, competitors):
    data = requests.post('https://api.scavio.dev/api/v1/search',
        headers=SH, json={'query': keyword, 'country_code': 'us'}).json()
    for r in data.get('organic_results', [])[:20]:
        domain = r.get('link', '').split('/')[2].replace('www.', '')
        if domain in competitors:
            print(f'{keyword}: {domain} at #{r["position"]}')

check('serp api', ['serpapi.com', 'dataforseo.com', 'serper.dev'])

JavaScript Example

JavaScript
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function check(keyword, competitors) {
  const data = await fetch('https://api.scavio.dev/api/v1/search', {
    method: 'POST', headers: SH,
    body: JSON.stringify({ query: keyword, country_code: 'us' })
  }).then(r => r.json());
  for (const r of (data.organic_results || []).slice(0, 20)) {
    const domain = new URL(r.link).hostname.replace('www.', '');
    if (competitors.includes(domain))
      console.log(`${keyword}: ${domain} at #${r.position}`);
  }
}
check('serp api', ['serpapi.com', 'dataforseo.com', 'serper.dev']).catch(console.error);

Expected Output

JSON
Monitoring 5 competitors across 5 keywords
Daily cost: $0.025
Checked 5 keywords. Cost: $0.025

Keyword                        | serpapi.com  | dataforseo.c | serper.dev   | exa.ai       | tavily.com   |
-----------------------------------------------------------------------------------------------
serp api                       | #3           | #7           | #5           | #12          | #15          |
search api for developers      | #4           | #8           | #6           | #9           | -            |
google search api python       | #2           | #5           | #4           | -            | -            |
web scraping api 2026          | #6           | #3           | #8           | -            | -            |
ai agent search api            | #5           | -            | #7           | #4           | #11          |

Monthly cost estimate: $3.75 (daily) or $0.88 (weekly)

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.8+. requests library. A Scavio API key from scavio.dev. Competitor domains and target keywords. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Monitor competitor rankings across keywords daily, detect position changes, and alert on new entrants. Python pipeline at $0.005/check.