Aperçu
Ce workflow suit jusqu'à 200 mots-clés cibles chaque semaine, enregistre la position de votre domaine dans les résultats de recherche Google, compare avec les positions de la semaine précédente et déclenche des alertes lorsque les classements baissent au-delà d'un seuil configurable. Il produit un rapport hebdomadaire avec les gagnants, les perdants et les mots-clés stables, qui peut être intégré directement dans les tableaux de bord SEO ou les résumés Slack.
Déclencheur
Planification Cron (tous les lundis à 7h UTC)
Planification
S'exécute tous les lundis à 7h UTC
Étapes du workflow
Charger la liste de mots-clés
Lire les mots-clés cibles et le domaine depuis un fichier de configuration.
Interroger Google pour chaque mot-clé
Appeler l'API Scavio avec la plateforme google pour chaque mot-clé, en demandant 30 résultats pour couvrir les pages une et deux.
Extraire les positions du domaine
Analyser les résultats organiques pour chaque mot-clé et enregistrer la position où apparaît le domaine cible.
Comparer avec la semaine précédente
Charger les positions de la semaine dernière et calculer les écarts pour chaque mot-clé.
Catégoriser les changements
Trier les mots-clés dans les catégories gagnants, perdants, stables et non trouvés en fonction d'un seuil.
Générer le rapport et l'alerte
Construire un rapport structuré, le sauvegarder sur le disque et envoyer une alerte pour toute baisse significative.
Implémentation Python
import requests
import json
import time
from pathlib import Path
from datetime import datetime
API_KEY = "your_scavio_api_key"
DOMAIN = "yourdomain.com"
DROP_THRESHOLD = 3
SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
def get_position(keyword: str) -> int | None:
res = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": API_KEY},
json={"platform": "google", "query": keyword, "num": 30},
timeout=15,
)
res.raise_for_status()
for item in res.json().get("organic", []):
if DOMAIN in item.get("link", ""):
return item["position"]
return None
def run():
keywords = json.loads(Path("keywords.json").read_text())
history_path = Path("rank_history.json")
previous = json.loads(history_path.read_text()) if history_path.exists() else {}
current_positions = {}
gainers = []
losers = []
stable = []
for i, kw in enumerate(keywords):
pos = get_position(kw)
current_positions[kw] = pos
prev = previous.get(kw)
if pos and prev:
delta = prev - pos # positive = improved
if delta >= DROP_THRESHOLD:
gainers.append({"keyword": kw, "from": prev, "to": pos, "change": delta})
elif delta <= -DROP_THRESHOLD:
losers.append({"keyword": kw, "from": prev, "to": pos, "change": delta})
else:
stable.append(kw)
if i % 20 == 19:
time.sleep(1)
# Save current as new baseline
history_path.write_text(json.dumps(current_positions, indent=2))
report = {
"date": datetime.utcnow().strftime("%Y-%m-%d"),
"total_keywords": len(keywords),
"tracked": sum(1 for v in current_positions.values() if v),
"gainers": gainers,
"losers": losers,
"stable_count": len(stable),
}
Path(f"rank_report_{report['date']}.json").write_text(json.dumps(report, indent=2))
if losers:
msg = f"Rank Drop Alert: {len(losers)} keywords dropped\n"
for l in losers[:10]:
msg += f" {l['keyword']}: #{l['from']} -> #{l['to']}\n"
requests.post(SLACK_WEBHOOK, json={"text": msg}, timeout=10)
print(f"Report: {len(gainers)} up, {len(losers)} down, {len(stable)} stable")
if __name__ == "__main__":
run()Implémentation JavaScript
const API_KEY = "your_scavio_api_key";
const DOMAIN = "yourdomain.com";
const DROP_THRESHOLD = 3;
const SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL";
async function getPosition(keyword) {
const res = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST",
headers: { "x-api-key": API_KEY, "content-type": "application/json" },
body: JSON.stringify({ platform: "google", query: keyword, num: 30 }),
});
if (!res.ok) throw new Error(`scavio ${res.status}`);
const data = await res.json();
for (const item of data.organic ?? []) {
if (item.link?.includes(DOMAIN)) return item.position;
}
return null;
}
async function run() {
const fs = await import("fs/promises");
const keywords = JSON.parse(await fs.readFile("keywords.json", "utf8"));
let previous = {};
try { previous = JSON.parse(await fs.readFile("rank_history.json", "utf8")); } catch {}
const currentPositions = {};
const gainers = [];
const losers = [];
const stable = [];
for (let i = 0; i < keywords.length; i++) {
const kw = keywords[i];
const pos = await getPosition(kw);
currentPositions[kw] = pos;
const prev = previous[kw];
if (pos && prev) {
const delta = prev - pos;
if (delta >= DROP_THRESHOLD) gainers.push({ keyword: kw, from: prev, to: pos, change: delta });
else if (delta <= -DROP_THRESHOLD) losers.push({ keyword: kw, from: prev, to: pos, change: delta });
else stable.push(kw);
}
if (i % 20 === 19) await new Promise((r) => setTimeout(r, 1000));
}
await fs.writeFile("rank_history.json", JSON.stringify(currentPositions, null, 2));
const date = new Date().toISOString().slice(0, 10);
const report = { date, totalKeywords: keywords.length, tracked: Object.values(currentPositions).filter(Boolean).length, gainers, losers, stableCount: stable.length };
await fs.writeFile(`rank_report_${date}.json`, JSON.stringify(report, null, 2));
if (losers.length) {
const msg = `Rank Drop Alert: ${losers.length} keywords dropped\n` +
losers.slice(0, 10).map((l) => ` ${l.keyword}: #${l.from} -> #${l.to}`).join("\n");
await fetch(SLACK_WEBHOOK, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ text: msg }) });
}
console.log(`Report: ${gainers.length} up, ${losers.length} down, ${stable.length} stable`);
}
run();Plateformes utilisées
Recherche web avec graphe de connaissances, PAA et aperçus IA