L’automatisation du suivi de classement ressemble à une tâche cron enveloppant un appel API, et pour la première semaine, c’est exactement cela. Les problèmes arrivent plus tard : deux exécutions qui ne sont pas comparables parce que l’une a été lancée à une heure différente, un jour manquant dans la série que personne ne remarque jusqu’à ce qu’un graphique paraisse faux, une alerte qui se déclenche sur du bruit, et un client qui demande pourquoi votre chiffre ne correspond pas au sien.
Aucun de ces problèmes n’est un problème de récupération de données. Ce sont des problèmes de pipeline, et il vaut la peine de les anticiper avant de se retrouver avec six mois de données trouées. Voici la construction.
Décider d’abord le contrat de mesure
Avant d’écrire le moindre code, fixez les variables qui rendent deux mesures comparables, car un classement n’a pas de sens sans elles : la localisation, l’appareil, la langue, et si la recherche est personnalisée. Ces choix deviennent des constantes dans votre tâche, et non des options par exécution.
La raison est que toute dérive sur ces éléments apparaît dans vos données comme un mouvement de classement qui n’a jamais eu lieu. Un mot-clé mesuré depuis une ville le lundi et une autre le mardi semblera avoir bougé. Le raisonnement complet se trouve dans mesurer des classements de mots-clés précis, et la version courte est que la cohérence compte plus que la précision absolue.
Écrivez le contrat sous forme de schéma, car c’est aussi ce que vous remettrez à un client quand il demandera ce que signifient vos chiffres :
# one row per keyword per market: this is the unit of measurement
TARGET = {
"keyword": "residential proxies",
"country": "de",
"city": "berlin", # optional, only where local intent matters
"language": "de",
"device": "desktop",
}
Récupérer via une API SERP
Vous pouvez construire vous-même la couche de collecte sur des proxies, ou appeler une API qui renvoie des résultats analysés. Pour une tâche de suivi quotidien, la voie API élimine les deux charges de maintenance qui consomment réellement du temps : suivre les changements de mise en page des pages de résultats, et faire fonctionner l’infrastructure de collecte.
Une API SERP prend les paramètres ci-dessus et renvoie des résultats structurés, de sorte que votre tâche consiste en une requête et une écriture plutôt qu’une récupération et une analyse :
import requests, datetime as dt
def fetch_serp(t):
r = requests.get("https://serp.shifter.io/v1", timeout=45, params={
"api_key": API_KEY,
"q": t["keyword"],
"gl": t["country"], # market
"hl": t["language"], # interface language
"location": t.get("city"),
"device": t["device"],
})
r.raise_for_status()
return r.json()
Vérifiez les noms de paramètres actuels dans la documentation de l’API plutôt que de faire confiance à un article de blog, car ils évoluent. L’alternative, faire fonctionner soi-même la collecte via des sorties résidentielles, est traitée dans pourquoi un suivi de classement précis nécessite des proxies résidentiels, et le compromis est le contrôle contre la maintenance.
Stocker le résultat entier, pas seulement votre position
L’erreur de conception la plus courante, et celle qu’il est coûteux de corriger après coup, est de stocker un seul chiffre par mot-clé et par jour.
Stockez la liste classée complète des URL, la présence et la position des fonctionnalités de résultats, et la charge utile brute. Trois raisons. Les mouvements de vos concurrents constituent le contexte qui rend votre propre mouvement interprétable. Les changements de fonctionnalités, comme l’apparition d’un aperçu IA au-dessus de la ligne de flottaison, changent ce que vaut une position sans changer le chiffre. Et vous ne pouvez pas collecter rétroactivement une SERP de mardi dernier, donc tout ce que vous n’avez pas stocké est perdu.
CREATE TABLE serp_snapshot (
id BIGSERIAL PRIMARY KEY,
keyword TEXT NOT NULL,
country TEXT NOT NULL,
city TEXT,
device TEXT NOT NULL,
captured_at TIMESTAMPTZ NOT NULL,
results JSONB NOT NULL, -- full ranked list
features JSONB NOT NULL, -- ai overview, local pack, shopping
raw JSONB -- keep it, storage is cheaper than regret
);
CREATE INDEX ON serp_snapshot (keyword, country, device, captured_at DESC);
Les positions sont ensuite dérivées des snapshots plutôt que stockées comme enregistrement principal, ce qui signifie qu’une correction d’analyse peut être réappliquée à l’historique au lieu de s’appliquer uniquement aux exécutions futures.
Planifier pour la comparabilité
Exécutez à la même heure chaque jour, dans une fenêtre que vous maintenez stable, car les résultats évoluent au cours de la journée et une série collectée à des heures variables présente une variance que vous ne pouvez pas attribuer.
Répartissez le travail sur cette fenêtre plutôt que de lancer tous les mots-clés d’un coup. Une rafale est à la fois plus difficile pour la source et plus susceptible d’être limitée, et l’étalement ne vous coûte rien quand l’échéance est dans plusieurs heures. Ajoutez du jitter entre les requêtes, plafonnez la concurrence, et laissez l’exécution durer une heure plutôt que quatre minutes.
import random, time
def run_daily(targets, window_seconds=3600):
gap = window_seconds / max(len(targets), 1)
for t in targets:
snapshot = fetch_with_retries(t)
store(t, snapshot)
time.sleep(gap * random.uniform(0.7, 1.3)) # spread, with jitter
Distinguer « aucun changement » de « aucune donnée »
C’est le détail qui sépare une série fiable d’une série trompeuse. Si une récupération échoue et que vous n’écrivez rien, un lecteur ultérieur voit un trou qui ressemble exactement à un jour où rien n’a bougé. Ensuite, une comparaison avec « hier » s’étend silencieusement sur deux jours et rapporte un mouvement qui n’a pas eu lieu.
Enregistrez chaque tentative avec un résultat explicite, et faites en sorte que l’analyse en aval refuse de comparer à travers un trou :
def fetch_with_retries(t, attempts=3):
for i in range(attempts):
try:
data = fetch_serp(t)
if is_valid(data): # sanity-check the payload
return {"status": "ok", "data": data}
record_status(t, "invalid") # parsed, but not a real SERP
except requests.RequestException:
record_status(t, "error")
time.sleep(2 ** i * random.uniform(0.5, 1.5))
return {"status": "failed", "data": None} # written as a failure, not skipped
Valider la charge utile compte autant que d’intercepter les exceptions, car une réponse peut arriver avec succès et ne pas être pour autant une page de résultats utilisable, ce qui est le problème général traité dans détecter du contenu bloqué ou factice.
Alerter sur les mouvements qui ont du sens
Une alerte naïve sur tout changement de position se déclenchera constamment, et un système d’alerte auquel personne ne fait confiance est pire que pas d’alerte du tout.
Trois règles rendent les alertes utiles. Exiger un seuil proportionnel à la position, car un passage de 3 à 6 compte bien plus qu’un passage de 47 à 50. Exiger une persistance, c’est-à-dire deux ou trois exécutions consécutives, car les rebonds d’un seul jour sont normaux. Et alerter séparément sur l’entrée ou la sortie de la première page, car cette frontière a de réelles conséquences en trafic.
Puis ajoutez l’alerte que tout le monde oublie : la santé de la collecte elle-même. Si un marché a silencieusement cessé de renvoyer des données depuis trois jours, c’est plus urgent que n’importe quel changement de classement, et cela n’est visible que si vous traitez les résultats d’exécution comme des données de première classe, selon surveiller un pipeline de scraping.
Faire en sorte que les données s’expliquent d’elles-mêmes
Deux ajouts transforment une table de classement en quelque chose que vous pouvez présenter à un client.
Annotez votre propre chronologie : déploiements, publications de contenu, migrations. La moitié des enquêtes « pourquoi avons-nous chuté » se terminent par une mise en production sortie cette semaine-là, et un graphique annoté le révèle immédiatement.
Calculez le mouvement au niveau du portefeuille, pas seulement les positions par mot-clé, car un déplacement large sur de nombreux mots-clés non liés est un événement différent d’une seule page qui glisse. C’est l’analyse de volatilité présentée dans détecter la volatilité des SERP et les mises à jour d’algorithme, et cela n’est possible que parce que vous avez stocké l’ensemble complet des résultats plutôt que votre seule position.
Collecter de manière responsable
Limitez-vous aux résultats de recherche publics, respectez les conditions d’utilisation de chaque moteur, et adoptez un rythme respectueux plutôt que de traiter les limites de débit comme un obstacle. Une tâche de suivi quotidien n’a aucune raison d’être agressive : l’échéance est le lendemain matin, donc étaler le travail ne coûte rien.
L’essentiel
La tâche cron est la partie facile. Fixez d’abord votre contrat de mesure, afin que la localisation, l’appareil, la langue et la personnalisation soient des constantes plutôt que des variables accidentelles. Stockez l’ensemble complet des résultats et la charge utile brute, pas seulement votre position, car le contexte concurrentiel et les fonctionnalités de résultats sont ce qui rend un chiffre interprétable, et vous ne pourrez pas revenir en arrière pour les obtenir. Exécutez à une heure constante, étalée sur la fenêtre avec du jitter. Enregistrez les échecs explicitement afin qu’un trou ne puisse jamais être confondu avec de la stabilité. Alertez sur les mouvements persistants et pondérés par la position, ainsi que sur la santé de la collecte. Puis annotez vos propres changements, afin que le graphique réponde à la question que les gens se posent réellement.
La couche de récupération est une API SERP si vous voulez des résultats analysés sans maintenir la collecte, ou des proxies résidentiels avec ciblage par pays et ville si vous préférez le faire vous-même, avec une tarification au Go adaptée aux vérifications petites et fréquentes.