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.