Commercial rank trackers charge $30-300/month and check keywords once daily. Building your own tracker with a SERP API gives you full control over check frequency, stored data, and alerting logic. Each keyword check costs $0.005 through Scavio, so tracking 100 keywords daily runs $0.50/day or $15/month, less than most starter plans.
Prerequisites
- Python 3.8+
- requests library
- A Scavio API key from scavio.dev
- SQLite3 (included with Python)
Walkthrough
Step 1: Set up the rank tracking database
Create a SQLite database to store daily rank positions.
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'}
db = sqlite3.connect('rank_tracker.db')
db.execute('''CREATE TABLE IF NOT EXISTS ranks (
keyword TEXT, domain TEXT, position INTEGER,
title TEXT, checked_at TEXT
)''')
db.execute('CREATE INDEX IF NOT EXISTS idx_kw ON ranks(keyword, checked_at)')
db.commit()
MY_DOMAIN = 'scavio.dev'
KEYWORDS = ['serp api', 'search api python', 'google search api',
'web scraping api', 'ai agent search tool']
print(f'Tracking {len(KEYWORDS)} keywords for {MY_DOMAIN}')Step 2: Check keyword rankings
Fetch SERP results and find your domain position for each keyword.
def check_rank(keyword, domain):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us'}).json()
organic = data.get('organic_results', [])
now = datetime.now().isoformat()
for r in organic[:50]:
link = r.get('link', '')
if domain in link:
pos = r.get('position', 0)
title = r.get('title', '')[:100]
db.execute('INSERT INTO ranks VALUES (?,?,?,?,?)',
(keyword, domain, pos, title, now))
db.commit()
return pos
db.execute('INSERT INTO ranks VALUES (?,?,?,?,?)',
(keyword, domain, 0, 'Not found', now))
db.commit()
return 0
for kw in KEYWORDS:
pos = check_rank(kw, MY_DOMAIN)
status = f'#{pos}' if pos else 'Not in top 50'
print(f' {kw:30} | {status}')
print(f'Cost: ${len(KEYWORDS) * 0.005:.3f}')Step 3: Detect rank changes between checks
Compare current positions against previous checks to find movements.
def detect_changes(keyword, domain):
rows = db.execute(
'SELECT position, checked_at FROM ranks WHERE keyword=? AND domain=? ORDER BY checked_at DESC LIMIT 2',
(keyword, domain)).fetchall()
if len(rows) < 2:
return None
current, prev = rows[0][0], rows[1][0]
if current == prev:
return None
change = prev - current # positive = improved
return {'keyword': keyword, 'prev': prev, 'current': current, 'change': change}
def report_changes():
changes = []
for kw in KEYWORDS:
c = detect_changes(kw, MY_DOMAIN)
if c:
changes.append(c)
if changes:
print(f'\n{len(changes)} rank changes:')
for c in changes:
arrow = 'UP' if c['change'] > 0 else 'DOWN'
print(f' {c["keyword"]:30} | #{c["prev"]} -> #{c["current"]} ({arrow} {abs(c["change"])})')
else:
print('No rank changes detected.')
return changes
report_changes()Step 4: Schedule daily checks via cron
Automate the tracker to run daily and accumulate history.
def daily_check():
print(f'Daily rank check: {datetime.now().isoformat()}')
results = []
for kw in KEYWORDS:
pos = check_rank(kw, MY_DOMAIN)
results.append({'keyword': kw, 'position': pos})
cost = len(KEYWORDS) * 0.005
print(f'Checked {len(KEYWORDS)} keywords. Cost: ${cost:.3f}')
changes = report_changes()
# Summary
ranked = [r for r in results if r['position'] > 0]
avg = sum(r['position'] for r in ranked) / len(ranked) if ranked else 0
print(f'\nSummary: {len(ranked)}/{len(results)} keywords ranking, avg position: {avg:.1f}')
print(f'Monthly cost estimate: ${cost * 30:.2f}')
# Cron: 0 6 * * * cd /path/to/project && python rank_tracker.py
daily_check()Python Example
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def track(keyword, domain):
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', [])[:50]:
if domain in r.get('link', ''):
print(f'{keyword}: {domain} at #{r["position"]}. Cost: $0.005')
return r['position']
print(f'{keyword}: {domain} not in top 50')
return 0
track('serp api', 'scavio.dev')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function track(keyword, domain) {
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());
const match = (data.organic_results || []).find(r => (r.link || '').includes(domain));
console.log(match ? `${keyword}: ${domain} at #${match.position}` : `${keyword}: not found`);
}
track('serp api', 'scavio.dev').catch(console.error);Expected Output
Tracking 5 keywords for scavio.dev
serp api | #4
search api python | #7
google search api | #11
web scraping api | #6
ai agent search tool | #3
Cost: $0.025
Summary: 5/5 keywords ranking, avg position: 6.2
Monthly cost estimate: $0.75