Vous pouvez surveiller les positions SERP des concurrents en recherchant quotidiennement des mots-clés cibles via API, en extrayant la position du domaine depuis les résultats organiques, en la stockant dans SQLite, et en alertant lorsque le rang d'un concurrent change de plus d'un seuil.
Prérequis
- Python 3.9+
- Clé API Scavio
- SQLite (stdlib)
- cron ou un planificateur
Parcours
Étape 1: Configurer la base de données de suivi
Créer une table SQLite pour stocker les instantanés quotidiens des positions.
import sqlite3
def init_db(path: str = "serp_tracker.db") -> sqlite3.Connection:
conn = sqlite3.connect(path)
conn.execute("""
CREATE TABLE IF NOT EXISTS positions (
date TEXT,
keyword TEXT,
domain TEXT,
position INTEGER,
PRIMARY KEY (date, keyword, domain)
)
""")
conn.commit()
return connÉtape 2: Extraire les positions des domaines des résultats de recherche
Rechercher le mot-clé et trouver chaque domaine suivi dans les résultats organiques.
import requests
from datetime import date
API_KEY = "your-scavio-api-key"
def get_positions(keyword: str, domains: list[str]) -> dict:
r = requests.post(
"https://api.scavio.dev/api/v1/search",
json={"query": keyword, "num_results": 20},
headers={"x-api-key": API_KEY},
timeout=15
)
r.raise_for_status()
results = r.json().get("organic_results", [])
positions = {}
for domain in domains:
pos = next(
(i + 1 for i, res in enumerate(results) if domain in res.get("link", "")),
None
)
positions[domain] = pos
return positionsÉtape 3: Stocker et comparer les positions
Enregistrer les positions du jour et les comparer avec celles d'hier pour détecter les changements.
def store_positions(conn, keyword: str, positions: dict):
today = str(date.today())
for domain, pos in positions.items():
conn.execute(
"INSERT OR REPLACE INTO positions VALUES (?, ?, ?, ?)",
(today, keyword, domain, pos)
)
conn.commit()
def get_yesterday_positions(conn, keyword: str, domains: list) -> dict:
rows = conn.execute(
"SELECT domain, position FROM positions WHERE keyword=? ORDER BY date DESC LIMIT ?",
(keyword, len(domains) * 2)
).fetchall()
# Get second-to-last date for comparison
dates = conn.execute(
"SELECT DISTINCT date FROM positions WHERE keyword=? ORDER BY date DESC LIMIT 2",
(keyword,)
).fetchall()
if len(dates) < 2:
return {}
prev_date = dates[1][0]
rows = conn.execute(
"SELECT domain, position FROM positions WHERE keyword=? AND date=?",
(keyword, prev_date)
).fetchall()
return dict(rows)Étape 4: Alerter en cas de changements de rang
Afficher (ou envoyer à Slack/email) lorsqu'un concurrent gagne ou perd 3 positions ou plus.
def check_alerts(keyword: str, today_pos: dict, yesterday_pos: dict, threshold: int = 3):
alerts = []
for domain, pos in today_pos.items():
prev = yesterday_pos.get(domain)
if prev is None or pos is None:
continue
change = prev - pos # positive = rank improved (moved up)
if abs(change) >= threshold:
direction = "up" if change > 0 else "down"
alerts.append(f"{domain} moved {direction} {abs(change)} positions for '{keyword}': {prev} -> {pos}")
return alerts
# Example run
conn = init_db()
KEYWORDS = ["project management software", "task tracker online"]
DOMAINS = ["asana.com", "monday.com", "notion.so", "clickup.com"]
for kw in KEYWORDS:
positions = get_positions(kw, DOMAINS)
yesterday = get_yesterday_positions(conn, kw, DOMAINS)
store_positions(conn, kw, positions)
alerts = check_alerts(kw, positions, yesterday)
for alert in alerts:
print("ALERT:", alert)Exemple Python
import requests
import sqlite3
from datetime import date
API_KEY = "your-scavio-api-key"
KEYWORDS = ["project management software", "task tracker for teams", "kanban board online"]
DOMAINS = ["asana.com", "monday.com", "notion.so", "clickup.com", "linear.app"]
def init_db(path="serp_tracker.db"):
conn = sqlite3.connect(path)
conn.execute("CREATE TABLE IF NOT EXISTS positions (date TEXT, keyword TEXT, domain TEXT, position INTEGER, PRIMARY KEY (date, keyword, domain))")
conn.commit()
return conn
def get_positions(keyword, domains):
r = requests.post("https://api.scavio.dev/api/v1/search",
json={"query": keyword, "num_results": 20},
headers={"x-api-key": API_KEY}, timeout=15)
r.raise_for_status()
results = r.json().get("organic_results", [])
return {d: next((i+1 for i, res in enumerate(results) if d in res.get("link","")), None) for d in domains}
def store(conn, keyword, positions):
today = str(date.today())
for domain, pos in positions.items():
conn.execute("INSERT OR REPLACE INTO positions VALUES (?,?,?,?)", (today, keyword, domain, pos))
conn.commit()
def prev_positions(conn, keyword):
dates = conn.execute("SELECT DISTINCT date FROM positions WHERE keyword=? ORDER BY date DESC LIMIT 2", (keyword,)).fetchall()
if len(dates) < 2:
return {}
rows = conn.execute("SELECT domain, position FROM positions WHERE keyword=? AND date=?", (keyword, dates[1][0])).fetchall()
return dict(rows)
def run_tracker():
conn = init_db()
for kw in KEYWORDS:
today_pos = get_positions(kw, DOMAINS)
yesterday_pos = prev_positions(conn, kw)
store(conn, kw, today_pos)
print(f"\nKeyword: {kw}")
for domain in DOMAINS:
pos = today_pos.get(domain)
prev = yesterday_pos.get(domain)
change = f" (was {prev})" if prev and prev != pos else ""
print(f" {domain}: {pos or 'not ranking'}{change}")
conn.close()
if __name__ == "__main__":
run_tracker()Exemple JavaScript
const API_KEY = 'your-scavio-api-key';
const KEYWORDS = ['project management software', 'task tracker for teams'];
const DOMAINS = ['asana.com', 'monday.com', 'notion.so', 'clickup.com'];
async function getPositions(keyword, domains) {
const res = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY },
body: JSON.stringify({ query: keyword, num_results: 20 })
});
const data = await res.json();
const results = data.organic_results ?? [];
return Object.fromEntries(domains.map(d => [
d,
(results.findIndex(r => r.link?.includes(d)) + 1) || null
]));
}
for (const kw of KEYWORDS) {
const positions = await getPositions(kw, DOMAINS);
console.log(`\nKeyword: ${kw}`);
for (const [domain, pos] of Object.entries(positions)) {
console.log(` ${domain}: ${pos ?? 'not ranking'}`);
}
}Sortie attendue
Keyword: project management software
asana.com: 2
monday.com: 4 (was 6)
notion.so: 7
clickup.com: 11
linear.app: not ranking
ALERT: monday.com moved up 2 positions for 'project management software': 6 -> 4