Project Goal
Build an SEO monitoring tool in 30 minutes:
- Check 100 keywords daily for SERP ranking
- Store data in SQLite
- Email alerts on ranking changes
- Simple query interface
Complete Code (5 Steps)
Step 1: Database Design (2 minutes)
import sqlite3
from datetime import datetime
def init_db():
conn = sqlite3.connect("seo_monitor.db")
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS rankings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts DATE NOT NULL,
keyword TEXT NOT NULL,
domain TEXT NOT NULL,
position INTEGER,
url TEXT,
snippet TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
c.execute("CREATE INDEX IF NOT EXISTS idx_keyword_ts ON rankings(keyword, ts)")
c.execute("CREATE INDEX IF NOT EXISTS idx_domain_ts ON rankings(domain, ts)")
conn.commit()
conn.close()
Step 2: SERP API Fetch Function (5 minutes)
import requests
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ["SERPBASE_KEY"]
ENDPOINT = "https://api.serpbase.dev/google/search"
def fetch_serp(keyword, gl="us", hl="en", num=20):
r = requests.post(
ENDPOINT,
headers={"X-API-Key": API_KEY},
json={"q": keyword, "gl": gl, "hl": hl, "num": num},
timeout=10,
)
r.raise_for_status()
return r.json()
def get_position(data, target_domain):
for i, item in enumerate(data.get("organic", []), 1):
if target_domain in item.get("link", ""):
return i, item["link"], item.get("snippet", "")
return None, None, None
Step 3: Collect + Store Function (5 minutes)
import sqlite3
from datetime import datetime
def collect_and_store(keyword, target_domain, gl="us"):
data = fetch_serp(keyword, gl=gl)
pos, url, snippet = get_position(data, target_domain)
conn = sqlite3.connect("seo_monitor.db")
c = conn.cursor()
c.execute("""
INSERT INTO rankings (ts, keyword, domain, position, url, snippet)
VALUES (?, ?, ?, ?, ?, ?)
""", (datetime.now().date().isoformat(), keyword, target_domain, pos, url, snippet))
conn.commit()
conn.close()
return {"keyword": keyword, "position": pos, "url": url}
Step 4: Batch + Cron Scheduling (8 minutes)
KEYWORDS = [
"best serp api",
"cheap serp api",
"serp api for seo",
# ... up to 100
]
DOMAIN = "yourdomain.com"
def daily_check():
for kw in KEYWORDS:
try:
result = collect_and_store(kw, DOMAIN)
print(f"OK {kw}: #{result['position']}")
except Exception as e:
print(f"FAIL {kw}: {e}")
if __name__ == "__main__":
daily_check()
Add cron: 0 9 * * * /usr/bin/python3 /path/to/seo_monitor.py
Step 5: Alerts + Reports (10 minutes)
import sqlite3
from datetime import datetime, timedelta
def detect_changes(days=2):
"""Detect ranking changes, send email alerts"""
conn = sqlite3.connect("seo_monitor.db")
c = conn.cursor()
today = datetime.now().date()
yesterday = today - timedelta(days=1)
c.execute("""
SELECT keyword, position, url, ts FROM rankings
WHERE ts IN (?, ?)
""", (yesterday.isoformat(), today.isoformat()))
rows = c.fetchall()
# Group by keyword, ts
today_data = {}
yesterday_data = {}
for kw, pos, url, ts in rows:
if pos is None:
continue
if ts == today.isoformat():
today_data[kw] = (pos, url)
else:
yesterday_data[kw] = (pos, url)
# Compare changes
changes = []
for kw, (pos_now, url) in today_data.items():
if kw in yesterday_data:
pos_yesterday, _ = yesterday_data[kw]
delta = pos_yesterday - pos_now
if abs(delta) >= 5: # 5+ position change
changes.append({
"keyword": kw,
"old": pos_yesterday,
"new": pos_now,
"delta": delta,
})
return changes
def send_email(changes):
import smtplib
from email.mime.text import MIMEText
if not changes:
return
body = "SERP Monitor Alert:\n\n"
for c in changes:
body += f"- {c['keyword']}: #{c['old']} -> #{c['new']} ({'+' if c['delta'] > 0 else ''}{c['delta']})\n"
msg = MIMEText(body)
msg["Subject"] = f"SERP Alert: {len(changes)} keyword(s) changed"
msg["From"] = "alert@yourdomain.com"
msg["To"] = "you@yourdomain.com"
with smtplib.SMTP("localhost") as s:
s.send_message(msg)
if __name__ == "__main__":
changes = detect_changes()
send_email(changes)
Complete Flow
[Daily 9am cron] → [daily_check()] → [SERP API] → [SQLite storage]
↓
[detect_changes()] → [Email alert]
↓
[Query interface / Report]
5 Advanced Directions
1. Add Dashboard (Streamlit)
import streamlit as st
import pandas as pd
import sqlite3
def dashboard():
conn = sqlite3.connect("seo_monitor.db")
df = pd.read_sql("SELECT * FROM rankings ORDER BY ts DESC LIMIT 1000", conn)
st.dataframe(df)
# Simple chart
st.line_chart(df.groupby("ts")["position"].mean())
# streamlit run dashboard.py
2. Add More Regions
REGIONS = [("us", "en"), ("uk", "en"), ("jp", "ja"), ("cn", "zh-CN")]
for gl, hl in REGIONS:
for kw in KEYWORDS:
collect_and_store(kw, DOMAIN, gl=gl)
3. Add Competitor Monitoring
def monitor_competitors(keyword, my_domain, competitors):
data = fetch_serp(keyword)
for i, item in enumerate(data.get("organic", []), 1):
for comp in competitors:
if comp in item.get("link", ""):
return {"competitor": comp, "position": i}
return None
4. Add AI Overview Tracking
def check_ai_overview(keyword, my_domain):
data = fetch_serp(keyword)
ao = data.get("ai_overview", {})
if not ao:
return {"triggered": False}
cited = any(my_domain in link.get("link", "") for block in ao.get("blocks", []) for link in block.get("links", []))
return {"triggered": True, "cited": cited, "text": ao.get("text", "")[:200]}
5. Add LLM Analysis
import anthropic
def llm_analyze_changes(changes):
client = anthropic.Anthropic()
prompt = f"Analyze these SERP ranking changes:\n{changes}"
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=500,
messages=[{"role": "user", "content": prompt}],
)
return response.content[0].text
Deployment Options
| Option | Best for |
|---|---|
| Local cron | Personal / small team |
| VPS ($5/month) | Small-medium SEO tool |
| Cloud Function | Serverless, pay-per-call |
| GitHub Actions | Free, once per day |
5 Common Mistakes
- Too many keywords: 10k keywords = 10k calls/day = $3/month
- No country dimension: only us = incomplete data
- No raw data storage: only storing position, lose snippet/url for later analysis
- Alert threshold too strict: 1-position change = alert fatigue
- No database archival: 1 year later, huge DB; archive old to cold storage
What's Next
| Direction | Resource |
|---|---|
| Real-time alerts | "SERP API + Slack alert bot" |
| Cache layer | "SERP API cache layer: 5 strategies" |
| RAG grounding | "SERP API + Claude in practice" |
| Error handling | "SERP API error handling: 5 patterns" |
100 free searches: serpbase.dev signup, run 100 keywords / 1 week to see your SEO monitoring data.
Top comments (0)