Resumen
Este flujo de trabajo rastrea up to 200 palabras clave objetivo cada semana, registros your domain's position in Google resultados de busqueda, diffs contra el anterior week's positions, y fires alertas cuando posicionamientos caida mas alla de un configurable umbral. It produces un informe semanal con gainers, losers, y stable palabras clave, feeding directamente en SEO paneles de control o Slack digests.
Desencadenador
Cron programar (cada Monday at 7 AM UTC)
Programación
Ejecuta cada Monday at 7 AM UTC
Pasos del flujo de trabajo
Cargar lista de palabras clave
Leer el palabras clave objetivo y dominio de un configuracion file.
Consulta Google for cada palabra clave
Call el Scavio API con plataforma google for cada palabra clave, requesting 30 resultados to cover pagina one y two.
Extraer dominio positions
Scan resultados organicos for cada palabra clave y registro el position donde el dominio objetivo appears.
Diff contra anterior semana
Cargar last week's positions y compute deltas for cada palabra clave.
Categorizar cambios
Sort palabras clave en gainers, losers, stable, y not-found buckets basado on umbral.
Generar informe y alerta
Construir un structured informe, guardar to disk, y enviar alerta for cualquier significativo drops.
Implementacion en 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()Implementacion en 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();Plataformas utilizadas
Búsqueda web con grafo de conocimiento, PAA y resúmenes de IA