Comment suivre le classement de mots-clés avec Python
Un tracker de classement minimal en Python : position depuis organic_results, stockage SQLite, et le calcul de crédits derrière la profondeur de suivi.

Un tracker de classement n’a pas besoin d’être un produit. Pour une poignée de mots-clés et un domaine, c’est une boucle, un fichier de base de données, et un moyen de repérer le jour où quelque chose a bougé. Voici le tout en une soixantaine de lignes.
La structure
Quatre étapes, exécutées sur un planning : envoyez chaque mot-clé via /serp avec un pays et un lieu fixes, trouvez votre domaine dans organic_results, écrivez la position dans SQLite, et comparez-la à la ligne d’hier.
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
Récupérer une position
organic_results revient avec un champ rank déjà présent sur chaque entrée, donc trouver la vôtre consiste à comparer le hostname de link avec le domaine, exactement ou comme sous-domaine (un simple domain in link correspond aussi à https://example.org/review/fastaf.co). La profondeur est une boucle sur page, puisque Google ne renvoie plus 100 résultats en une seule requête :
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 trouvé dans max_pages
location restreint la recherche à un lieu précis (une ville, par exemple) plutôt qu’à un simple pays, ce qui compte pour tout ce qui a une intention locale. page est le contrôle de profondeur : chaque page est une requête facturable séparée, dix résultats à la fois.
Suivre une liste et enregistrer le run du jour
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)
Lancez ceci une fois par jour (cron, une fonction planifiée, ce que vous avez déjà) et rankings.db accumule une ligne par mot-clé et par jour.
Calculer le delta quotidien
L’intérêt de stocker l’historique, c’est le delta, pas l’instantané. Récupérez les deux dernières lignes de chaque mot-clé et faites la différence :
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 # positif signifie que ça a monté
if change != 0:
print(f"{keyword}: {prev} -> {curr} ({change:+d})")
Le calcul de crédits derrière la « profondeur »
C’est la partie qui plombe discrètement un budget de crédits si elle n’est pas dimensionnée en amont. Une requête /serp coûte 5 crédits quelle que soit la profondeur, mais « top 100 » représente dix requêtes séparées, pas une, puisque Google n’honore plus une seule requête renvoyant 100 résultats.
| Configuration | Requêtes/jour | Crédits/jour | Crédits (30 jours) | Pack qui couvre ça |
|---|---|---|---|---|
| 500 mots-clés, quotidien, profondeur 10 (page 0 seulement) | 500 | 2 500 | 75 000 | Growth, 29 $ |
| 500 mots-clés, quotidien, profondeur 100 (10 pages) | 5 000 | 25 000 | 750 000 | Pro, 299 $ |
Une profondeur de 10 vérifie si vous êtes classé sur la première page tout court, ce qui suffit pour la plupart des suivis. Une profondeur de 100 est nécessaire si vous voulez aussi savoir où vous vous situez sur les pages 2 à 10, et ça coûte dix fois plus pour la même liste de mots-clés. Réglez max_pages délibérément, pas par habitude : un tracker discrètement passé d’une profondeur 10 à 100 représente une facture de crédits multipliée par 10 pour la même liste de mots-clés.
Vérifier deux fois par jour au lieu d’une fois double chaque chiffre de ce tableau ; vérifier chaque semaine au lieu de chaque jour les divise par sept. Choisissez une fréquence dont le mouvement de classement qui vous intéresse a réellement besoin, pas le plus court intervalle que votre planificateur supporte.
En bref
Un tracker de classement, c’est un appel /serp, une correspondance de domaine contre organic_results, et une table SQLite, rien de plus exotique que ça. Le chiffre qui détermine votre facture est la profondeur multipliée par la fréquence multipliée par le nombre de mots-clés, pas l’outil. Calculez ça avant de planifier le job : tarifs donne le coût en crédits par pack, et la documentation couvre l’ensemble des paramètres de /serp, y compris location et device.