Come monitorare il ranking delle keyword con Python
Un rank tracker minimale in Python: query, posizione da organic_results, SQLite, e la matematica dei crediti dietro la profondità di tracking.

Un rank tracker non deve essere un prodotto. Per una manciata di keyword e un dominio, è un loop, un file di database, e un modo per notare il giorno in cui qualcosa si è mosso. Ecco il tutto in circa sessanta righe.
La forma
Quattro passi, eseguiti a schedule: manda ogni keyword a /serp con paese e location fissi, trova il tuo dominio in organic_results, scrivi la posizione in SQLite, e confrontala con la riga di ieri.
import sqlite3
import requests
from datetime import date
from urllib.parse import urlparse
API_KEY = "faf_live_..."
DB_PATH = "rankings.db"
DOMAIN = "fastaf.co"
COUNTRY = "us"
KEYWORDS = [
"web scraping api",
"serp api pricing",
"google search api alternative",
]
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS rankings (
checked_on TEXT,
keyword TEXT,
domain TEXT,
position INTEGER,
PRIMARY KEY (checked_on, keyword, domain)
)
""")
return conn
Recuperare una posizione
organic_results torna con un campo rank già su ogni voce, quindi trovare la tua è questione di confrontare l’hostname di link con il dominio, esatto o come sottodominio (un semplice domain in link combacia anche con https://example.org/review/fastaf.co). La profondità è un loop su page, dato che Google non restituisce più 100 risultati in una richiesta:
def on_domain(link, domain):
host = urlparse(link).hostname or ""
return host == domain or host.endswith("." + domain)
def find_position(keyword, domain, country=COUNTRY, location=None, max_pages=1):
for page in range(max_pages):
params = {"query": keyword, "country": country, "page": page}
if location:
params["location"] = location
resp = requests.get(
"https://api.fastaf.co/v1/serp",
params=params,
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
resp.raise_for_status()
for result in resp.json().get("organic_results", []):
if on_domain(result.get("link", ""), domain):
return result["rank"]
return None # non trovato entro max_pages
location restringe la ricerca a un luogo specifico (una città, per esempio) invece che al solo paese, il che conta per qualunque cosa con intento locale. page è il controllo di profondità: ogni pagina è una richiesta fatturabile separata, dieci risultati alla volta.
Monitorare una lista e salvare la corsa del giorno
def track(conn, keywords, domain, max_pages=1):
today = date.today().isoformat()
for keyword in keywords:
position = find_position(keyword, domain, max_pages=max_pages)
conn.execute(
"INSERT OR REPLACE INTO rankings VALUES (?, ?, ?, ?)",
(today, keyword, domain, position),
)
print(f"{keyword}: {position}")
conn.commit()
if __name__ == "__main__":
conn = init_db()
track(conn, KEYWORDS, DOMAIN)
Esegui questo una volta al giorno (cron, una funzione schedulata, quello che già usi) e rankings.db accumula una riga per keyword per giorno.
Calcolare il delta giornaliero
Il punto di salvare la storia è il delta, non lo snapshot. Estrai le ultime due righe di ogni keyword e confrontale:
def deltas(conn, domain):
rows = conn.execute("""
SELECT keyword, checked_on, position
FROM rankings
WHERE domain = ?
ORDER BY keyword, checked_on
""", (domain,)).fetchall()
by_keyword = {}
for keyword, checked_on, position in rows:
by_keyword.setdefault(keyword, []).append((checked_on, position))
for keyword, history in by_keyword.items():
if len(history) < 2:
continue
(_, prev), (_, curr) = history[-2], history[-1]
if prev is None or curr is None:
continue
change = prev - curr # positivo significa che è salita
if change != 0:
print(f"{keyword}: {prev} -> {curr} ({change:+d})")
La matematica dei crediti dietro la “profondità”
Questa è la parte che manda in crisi silenziosamente un budget di crediti se non viene dimensionata in anticipo. Una richiesta /serp costa 5 crediti indipendentemente dalla profondità, ma “top 100” sono dieci richieste separate, non una, dato che Google non rispetta più una singola richiesta che restituisce 100 risultati.
| Configurazione | Richieste/giorno | Crediti/giorno | Crediti (30 giorni) | Pacchetto che copre |
|---|---|---|---|---|
| 500 keyword, giornaliero, profondità 10 (solo page 0) | 500 | 2.500 | 75.000 | Growth, $29 |
| 500 keyword, giornaliero, profondità 100 (10 pagine) | 5.000 | 25.000 | 750.000 | Pro, $299 |
Profondità 10 controlla se sei in prima pagina, sufficiente per la maggior parte del tracking. Profondità 100 è quello che ti serve se vuoi anche sapere dove ti posizioni dalla pagina 2 alla 10, e costa dieci volte di più per la stessa lista di keyword. Imposta max_pages deliberatamente, non per abitudine: un tracker spostato senza accorgersene da profondità 10 a profondità 100 è una bolletta in crediti 10x per la stessa lista di keyword.
Controllare due volte al giorno invece di una raddoppia ogni numero di quella tabella; controllare settimanalmente invece che giornalmente li divide per sette. Scegli una frequenza di cui il movimento di ranking che ti interessa ha davvero bisogno, non l’intervallo più corto che il tuo scheduler supporta.
In breve
Un rank tracker è una chiamata /serp, un confronto del dominio contro organic_results, e una tabella SQLite, niente di più esotico. Il numero che decide la tua bolletta è profondità per frequenza per numero di keyword, non lo strumento. Calcolalo prima di schedulare il job: la pagina prezzi ha il costo in crediti per pacchetto, e la documentazione copre l’insieme completo dei parametri di /serp, inclusi location e device.