Aller au contenu principal
Site en cours de refonte — quelques pages peuvent bouger ou évoluer.
15 mars 2026

GSC → BigQuery en panne : comment fiabiliser ton reporting SEO (avec un vrai plan B)

Quand ton SEO dépend d’un export GSC vers BigQuery, une panne te met à l’aveugle. Construis un pipeline qui détecte la casse, bascule en API, et backfill proprement.

11 min de lecture
20 vues
réactions
Partager :
GSC → BigQuery en panne : comment fiabiliser ton reporting SEO (avec un vrai plan B)

Le Bulk Export Google Search Console vers BigQuery, c’est confortable. Tu connectes, tu oublies, tu construis tes dashboards, et toute l’équipe finit par croire que “la donnée arrive”. Jusqu’au jour où l’export se met à échouer et que ton reporting SEO devient un théâtre d’ombres. Le pire n’est pas la panne. Le pire, c’est l’absence de plan B.

Le bon réflexe, c’est de traiter GSC→BigQuery comme n’importe quel pipeline data critique. Ça veut dire trois choses : détecter vite quand ça casse, continuer à produire une donnée exploitable (même moins riche), et rattraper proprement quand ça repart. Je te pose une approche pratico-pratique, orientée résilience, pas “setup parfait”.

La panne “silencieuse” : pourquoi ton reporting meurt sans que personne ne s’en rende compte

Un export qui casse n’explose pas toujours de façon spectaculaire. Souvent, ça se traduit par une table qui n’a pas reçu sa partition du jour, un job en erreur dans un coin, ou une colonne qui change et fait tomber ton SQL sans que le dashboard ne crie très fort. Et côté SEO, la casse est vicieuse : GSC a déjà un délai naturel. Donc tu peux regarder un trou de données et te raconter que “c’est le lag normal”.

Si tu fais du pilotage produit (pages qui perf, requêtes qui montent, effets d’un template, d’un maillage, d’un déploiement), perdre quelques jours de data te met rapidement à décider au doigt mouillé. Et une fois que tout le monde s’est habitué à BigQuery comme source unique, tu n’as plus de garde-fous. Tu as juste une dépendance.

1) Détecter la casse : freshness, volumes, schéma (les trois signaux qui ne mentent pas)

Tu veux un système qui te dise “la donnée n’est pas au niveau attendu” avant que quelqu’un t’en parle sur Slack en mode “t’as vu le graphe ?”. Je commence toujours par un contrôle de fraîcheur bête et méchant : est-ce qu’on a bien une partition récente dans les tables d’export, et est-ce qu’elle est arrivée dans la fenêtre normale de ton orga.

Ensuite, je regarde les volumes. Pas pour surveiller le SEO (ça, c’est l’analyse), mais pour détecter les anomalies mécaniques. Une chute à zéro sur tout le site, sur toutes les dimensions, ce n’est presque jamais du “marché”. C’est souvent un pipeline qui a lâché, une table vide, une ingestion qui s’est arrêtée, ou un filtre qui s’est mis à exclure la moitié des lignes. Le volume est un capteur de panne déguisé.

Le troisième signal, c’est la dérive de schéma. Un export peut se remettre à fonctionner mais avec une structure qui change, des champs renvoyés différemment, un type qui bouge. Ton SQL peut continuer à tourner en renvoyant du null, ce qui est encore pire. La règle : tu surveilles explicitement les colonnes attendues et les types, et tu assumes qu’un schéma n’est pas “garanti pour toujours”.

2) Le vrai plan B : fallback via Search Analytics API (et accepter que ce soit moins riche)

Quand BigQuery est HS, tu veux continuer à alimenter au moins les métriques vitales : clics, impressions, CTR, position, par jour, et avec quelques dimensions utiles. Le candidat logique, c’est l’API Search Analytics de Google Search Console. Elle ne remplace pas le bulk export. Elle te donne moins de granularité, des limites de requêtes, une pagination, et il faut composer avec les règles de confidentialité de GSC. Mais elle a un avantage décisif : elle te sort de la dépendance à un seul tuyau.

L’astuce qui marche bien : tu ne cherches pas à reproduire ton dataset “parfait”. Tu construis un dataset de secours, pensé pour tenir pendant une semaine de crise sans te ridiculiser. Tu choisis une combinaison de dimensions stable (souvent date + page, et éventuellement pays ou device selon ton usage), et tu acceptes que les requêtes (“query”) soient un luxe pendant l’incident si ça te met en difficulté côté volumétrie.

Et surtout, tu sépares les sources. Tu stockes la donnée “fallback” dans des tables dédiées, avec une colonne qui marque clairement l’origine (bulk export vs API). Ça évite les débats absurdes quand quelqu’un compare deux chiffres et conclut que “GSC ment”. Non, c’est toi qui as mélangé deux canaux sans l’assumer.

Stockage : BigQuery peut rester la destination, ce n’est pas le problème

On confond souvent “BigQuery est en panne” et “je dois quitter BigQuery”. Dans la plupart des cas, BigQuery va très bien. C’est l’export GSC qui lâche. Ton plan B peut parfaitement écrire dans BigQuery, dans un dataset séparé, avec des tables partitionnées par date. C’est même pratique : tu gardes tes outils (Looker Studio, Metabase, dbt, notebooks) et tu changes juste la source.

Ce que tu veux éviter, c’est un plan B bricolé en CSV dans un Drive partagé, sans rétention, sans historisation, et sans possibilité de backfill propre. Le fallback doit être automatisable, observable, et réversible.

3) Backfill : la reprise propre qui évite les doublons (et les réécritures sales)

Quand l’export se remet à marcher, tu vas avoir deux problèmes. D’abord, tu vas vouloir combler le trou. Ensuite, tu vas devoir gérer le fait que la donnée GSC est souvent “vivante” sur quelques jours : les chiffres se stabilisent avec du retard. Si tu backfill une seule fois puis tu n’y touches plus, tu te retrouves avec une vérité figée trop tôt.

Le pattern le plus robuste, c’est le backfill en fenêtre glissante. Tous les jours, tu ré-importes les N derniers jours (souvent 7 à 14 selon ton usage), et tu écrases proprement ce qui existe déjà. Ça paraît coûteux, mais ce n’est pas énorme si tu limites les dimensions, et ça te donne une stabilité statistique sans débat.

La clé, c’est l’idempotence. Tu dois pouvoir rejouer une exécution sans créer de doublons ni de divergences. En pratique, ça veut dire une clé unique logique (date + site + dimensions) et une stratégie d’upsert. Avec BigQuery, un MERGE sur une table partitionnée fait très bien le job.

MERGE `seo.fallback_search_analytics` T
USING `seo._staging_search_analytics` S
ON  T.property = S.property
AND T.date = S.date
AND T.page = S.page
AND T.country = S.country
AND T.device = S.device
WHEN MATCHED THEN UPDATE SET
  clicks = S.clicks,
  impressions = S.impressions,
  ctr = S.ctr,
  position = S.position,
  source = S.source,
  updated_at = CURRENT_TIMESTAMP()
WHEN NOT MATCHED THEN INSERT (
  property, date, page, country, device,
  clicks, impressions, ctr, position,
  source, updated_at
) VALUES (
  S.property, S.date, S.page, S.country, S.device,
  S.clicks, S.impressions, S.ctr, S.position,
  S.source, CURRENT_TIMESTAMP()
);

Tu remarques le détail important : je sépare staging et table finale. Le staging, c’est jetable. La table finale, c’est celle que tes dashboards consomment. Cette séparation t’évite de servir une demi-importation à 9h12 parce qu’un job s’est arrêté au milieu.

4) Alerting et “data SLO” : décider quand la donnée est fiable (au lieu de prier)

Un reporting SEO “fiable”, ce n’est pas une promesse morale. C’est un contrat de fraîcheur. Tu définis une attente claire, par exemple : “les données J-2 doivent être complètes avant 10h” ou “la dernière partition disponible ne doit pas avoir plus de 72h”. Peu importe le chiffre, du moment qu’il colle au délai habituel de GSC dans ton contexte et à ton besoin métier.

À partir de là, tu peux faire un alerting propre. Si le SLO n’est pas respecté, tu alertes. Pas un mail perdu, un vrai signal qui arrive au bon endroit. Et tu ne limites pas l’alerte à “il manque des lignes”. Tu peux aussi alerter sur une chute de volume non plausible, ou sur une rupture de schéma.

Le point souvent oublié : tes dashboards doivent intégrer cette réalité. Un graphe sans badge “données en retard” est un graphe qui va te faire prendre des décisions débiles. Afficher une bannière “données incomplètes” sur la période récente, c’est moins glamour, mais c’est honnête. Et ça évite la panique quand tout le monde pense que le SEO s’est effondré en une nuit.

5) Rétention et preuve : garde un raw minimal, même si tu penses ne jamais en avoir besoin

Quand tu enquêtes sur une anomalie, tu veux pouvoir répondre à une question simple : “qu’est-ce qu’on a vraiment ingéré, et quand ?”. Si ta seule table est une table finale déjà transformée, tu as perdu la trace. Donc oui, garde un raw minimal. Pas forcément le raw complet de toute ta vie SEO. Mais au moins des exports journaliers (même compressés) du résultat API et des métadonnées d’ingestion.

Ça te permet aussi de faire du backfill “propre” quand tu corriges un bug. Parce qu’il y aura un bug. La seule vraie question, c’est si tu pourras le réparer sans tout casser.

Un exemple concret : récupérer Search Analytics API et alimenter BigQuery

Je ne vais pas te coller une usine à gaz. L’idée est simple : un job planifié (Cloud Run cron, Cloud Scheduler + Cloud Functions, GitHub Actions, peu importe) appelle l’API, pagine les résultats, écrit en staging, puis merge.

Voilà un squelette en Python qui montre le flux. Tu devras gérer l’auth (service account + accès à la propriété, ou OAuth selon ton contexte), mais le cœur du pattern est là : fenêtre glissante, pagination, staging, idempotence.

from datetime import date, timedelta

# Pseudo-code: à adapter avec google-api-python-client et google-cloud-bigquery

def daterange(days_back: int):
    end = date.today() - timedelta(days=2)  # GSC a du délai, évite de viser J-0
    start = end - timedelta(days=days_back)
    return start.isoformat(), end.isoformat()


def fetch_search_analytics(service, site_url, start_date, end_date, dims):
    start_row = 0
    while True:
        body = {
            "startDate": start_date,
            "endDate": end_date,
            "dimensions": dims,
            "rowLimit": 25000,
            "startRow": start_row,
        }
        resp = service.searchanalytics().query(siteUrl=site_url, body=body).execute()
        rows = resp.get("rows", [])
        if not rows:
            break
        yield from rows
        start_row += len(rows)


def run():
    start_date, end_date = daterange(days_back=14)
    dims = ["date", "page", "country", "device"]

    # 1) fetch API rows
    api_rows = []
    for r in fetch_search_analytics(gsc_service, "https://example.com/", start_date, end_date, dims):
        keys = r.get("keys", [])
        api_rows.append({
            "date": keys[0],
            "page": keys[1],
            "country": keys[2],
            "device": keys[3],
            "clicks": r.get("clicks", 0),
            "impressions": r.get("impressions", 0),
            "ctr": r.get("ctr", 0.0),
            "position": r.get("position", 0.0),
            "property": "sc-domain:example.com",
            "source": "gsc_api",
        })

    # 2) load to staging table (truncate staging each run)
    bq_load_to_staging(api_rows)

    # 3) merge staging into final
    bq_run_merge_sql()


if __name__ == "__main__":
    run()

Ce code ne fait pas “tout”. Il fait ce que tu veux en incident : produire de la donnée, rejouable, et mergeable. Le reste (retries, quotas, logs structurés, métriques, dead-letter) vient après, quand tu as déjà arrêté l’hémorragie.

Mon avis (sec) : si ton SEO est piloté, ton pipeline doit être traité comme de la prod

Quand je vois une orga qui a des rituels produit basés sur GSC (priorisation de contenus, décisions d’architecture, arbitrages de maillage), mais qui n’a aucun monitoring sur l’arrivée des partitions, je sais comment ça finit. Un matin, la donnée est en retard, quelqu’un improvise un “report manuel”, et tu passes deux semaines à recoller les morceaux.

La solution n’est pas de chercher une source “qui ne tombe jamais”. Elle n’existe pas. La solution, c’est d’assumer que ça tombe, et de construire un circuit qui survit. Freshness checks, fallback API, backfill glissant, idempotence, alerting avec SLO. Le reste, c’est du confort.

Sources

Cet article vous a plu ?

Commentaires

Laisser un commentaire

Entre 10 et 2000 caractères

Les commentaires sont modérés avant publication.

Aucun commentaire pour le moment.

Soyez le premier à donner votre avis !