[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fhAbmktGAKtxHQVY6IuCKSpnRIJBzSfYYXvx1KZ9HgC0":3,"$fS1QqJZuAqLKeJJWyWrFi_3ogGyH_0GoWujT87Xqb4lU":25},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":23,"updatedAt":24},"69d83b46aa6b273b0cd6f586","how-to-measure-intent-gaps-using-google-search-console-data",0,"Equipe Seogard","Une page catégorie d'un e-commerce mode se positionne sur 340 requêtes dans Google Search Console. CTR moyen : 1,2%. En isolant les 40 requêtes alignées avec l'intent réel de la page, le CTR monte à 8,7%. Les 300 autres requêtes sont des intent gaps — des signaux de décalage entre ce que Google pense que votre page répond et ce que les utilisateurs cherchent réellement.\n\n## Ce qu'est un intent gap (et pourquoi le CTR seul ne suffit pas)\n\nUn intent gap n'est pas simplement un mot-clé sur lequel vous ne rankez pas. C'est un décalage mesurable entre l'intention de recherche d'un utilisateur et la réponse que votre page fournit, alors même que Google vous positionne sur cette requête.\n\nPrenez une page `/chaussures-running-homme` qui reçoit des impressions sur la requête \"avis chaussures running pronateur\". Google considère la page suffisamment pertinente pour l'afficher. L'utilisateur, lui, cherche un contenu éditorial comparatif. Il voit un listing produit, ne clique pas (ou clique et rebondit). C'est un intent gap.\n\nLe CTR brut par requête est un indicateur, mais il masque le problème. Un CTR de 2% en position 8 est normal. Un CTR de 2% en position 2, c'est un signal d'alerte. La métrique qui révèle les intent gaps, c'est le **ratio CTR/position attendu** — l'écart entre le CTR que vous devriez avoir à une position donnée et celui que vous obtenez réellement.\n\nLes courbes de CTR par position varient selon les industries et les types de SERP (présence de featured snippets, de PAA, de résultats shopping). Mais les études de référence comme celles d'Advanced Web Ranking montrent des patterns stables : position 1 entre 25% et 35% de CTR, position 3 entre 8% et 15%, position 5 entre 4% et 7%. Tout écart significatif à la baisse pour une position donnée mérite investigation.\n\nL'approche proposée par Search Engine Land dans leur article récent va dans le bon sens en utilisant GSC comme source de données brutes. Mais elle reste en surface. Ce qui suit est une méthode technique complète pour extraire, classifier et prioriser les intent gaps à l'échelle.\n\n## Extraire les données brutes via l'API GSC\n\nL'interface web de Google Search Console est limitée à 1 000 lignes par export et ne permet pas de croiser facilement pages et requêtes. Pour une analyse sérieuse sur un site de plus de 500 pages, l'API est indispensable.\n\n### Configuration de l'extraction\n\nVoici un script Python qui extrait les données page + query pour les 90 derniers jours, avec gestion de la pagination :\n\n```python\nfrom google.oauth2 import service_account\nfrom googleapiclient.discovery import build\nimport pandas as pd\nfrom datetime import datetime, timedelta\n\nSCOPES = ['https://www.googleapis.com/auth/webmasters.readonly']\nSERVICE_ACCOUNT_FILE = 'credentials.json'\nSITE_URL = 'https://www.votresite-ecommerce.fr'\n\ncredentials = service_account.Credentials.from_service_account_file(\n    SERVICE_ACCOUNT_FILE, scopes=SCOPES\n)\nservice = build('searchconsole', 'v1', credentials=credentials)\n\nend_date = datetime.now() - timedelta(days=3)  # GSC a ~3j de latence\nstart_date = end_date - timedelta(days=90)\n\nall_rows = []\nstart_row = 0\nROW_LIMIT = 25000\n\nwhile True:\n    request = {\n        'startDate': start_date.strftime('%Y-%m-%d'),\n        'endDate': end_date.strftime('%Y-%m-%d'),\n        'dimensions': ['page', 'query'],\n        'rowLimit': ROW_LIMIT,\n        'startRow': start_row,\n        'dimensionFilterGroups': [{\n            'filters': [{\n                'dimension': 'country',\n                'expression': 'fra'  # Filtrer par pays cible\n            }]\n        }]\n    }\n    \n    response = service.searchanalytics().query(\n        siteUrl=SITE_URL, body=request\n    ).execute()\n    \n    rows = response.get('rows', [])\n    if not rows:\n        break\n    \n    for row in rows:\n        all_rows.append({\n            'page': row['keys'][0],\n            'query': row['keys'][1],\n            'clicks': row['clicks'],\n            'impressions': row['impressions'],\n            'ctr': row['ctr'],\n            'position': row['position']\n        })\n    \n    start_row += ROW_LIMIT\n    if len(rows) \u003C ROW_LIMIT:\n        break\n\ndf = pd.DataFrame(all_rows)\ndf.to_csv('gsc_page_query_raw.csv', index=False)\nprint(f\"Extracted {len(df)} page/query combinations\")\n```\n\nSur un site e-commerce de 12 000 pages actives, cette extraction retourne typiquement entre 200 000 et 800 000 combinaisons page/query. C'est cette granularité qui permet l'analyse des intent gaps — impossible à obtenir via l'interface web.\n\nPour automatiser cette extraction, vous pouvez consulter notre guide sur [l'automatisation du reporting SEO via l'API Search Console](/blog/search-console-api-automatiser-le-reporting-seo).\n\n### Nettoyage et filtrage\n\nFiltrez impérativement les données avant analyse. Les requêtes avec moins de 10 impressions sur 90 jours sont du bruit statistique. Les requêtes de marque doivent être isolées — elles faussent les moyennes de CTR.\n\n```python\n# Filtrer le bruit et segmenter\ndf_clean = df[df['impressions'] >= 10].copy()\n\n# Séparer requêtes brand / non-brand\nBRAND_TERMS = ['votresite', 'votre-site', 'votre site']\ndf_clean['is_brand'] = df_clean['query'].str.lower().apply(\n    lambda q: any(term in q for term in BRAND_TERMS)\n)\n\ndf_nonbrand = df_clean[~df_clean['is_brand']].copy()\nprint(f\"Non-brand combinations: {len(df_nonbrand)}\")\n```\n\n## Calculer le score d'intent gap par combinaison page/query\n\nLe cœur de la méthode repose sur la comparaison entre le CTR observé et le CTR attendu à une position donnée. Un écart négatif important signale un décalage d'intention.\n\n### Modèle de CTR attendu\n\nPlutôt que d'utiliser des benchmarks externes, construisez votre propre courbe de CTR à partir de vos données GSC. Chaque site a un profil de CTR différent selon sa présence en SERP, la nature de ses snippets, et son secteur.\n\n```python\nimport numpy as np\n\n# Calculer le CTR médian par tranche de position (sur vos propres données)\ndf_nonbrand['position_bucket'] = df_nonbrand['position'].apply(\n    lambda p: min(int(p), 20)  # Regrouper positions > 20\n)\n\n# Utiliser la médiane plutôt que la moyenne (résistante aux outliers)\nctr_benchmarks = df_nonbrand.groupby('position_bucket').agg(\n    median_ctr=('ctr', 'median'),\n    count=('ctr', 'size')\n).reset_index()\n\n# Créer un dictionnaire de lookup\nctr_expected = dict(zip(\n    ctr_benchmarks['position_bucket'],\n    ctr_benchmarks['median_ctr']\n))\n\n# Calculer le gap score pour chaque combinaison\ndf_nonbrand['expected_ctr'] = df_nonbrand['position_bucket'].map(ctr_expected)\ndf_nonbrand['ctr_gap'] = df_nonbrand['ctr'] - df_nonbrand['expected_ctr']\n\n# Pondérer par les impressions (un gap sur 5000 impressions > gap sur 15)\ndf_nonbrand['weighted_gap'] = df_nonbrand['ctr_gap'] * df_nonbrand['impressions']\n\n# Trier par impact négatif\nintent_gaps = df_nonbrand[df_nonbrand['ctr_gap'] \u003C -0.02].sort_values(\n    'weighted_gap', ascending=True\n)\n\nprint(f\"Intent gaps detected: {len(intent_gaps)}\")\nprint(intent_gaps[['page', 'query', 'position', 'ctr', 'expected_ctr', 'ctr_gap', 'impressions']].head(20))\n```\n\n### Interpréter le score\n\nUn `ctr_gap` de -0.05 en position 3 signifie que votre page obtient 5 points de CTR de moins que la médiane de votre site à cette position. Les causes possibles :\n\n- **Décalage d'intent pur** : la requête est informationnelle mais la page est transactionnelle (ou l'inverse).\n- **Snippet mal optimisé** : le title ou la meta description ne reflète pas le contenu attendu pour cette requête.\n- **Cannibalisation** : une autre page de votre site se positionne aussi, et l'utilisateur hésite ou choisit l'autre.\n- **SERP features** : un featured snippet ou un PAA capte l'attention au-dessus de votre résultat.\n\nLe `weighted_gap` permet de prioriser : un écart de 3 points de CTR sur une requête à 8 000 impressions/mois vaut plus qu'un écart de 10 points sur 20 impressions.\n\n## Classifier l'intention à l'échelle avec des heuristiques\n\nL'étape suivante consiste à attribuer un type d'intention à chaque requête pour identifier les patterns. Quatre catégories suffisent en pratique : informationnelle, transactionnelle, navigationnelle, investigation commerciale.\n\n### Classification par signaux lexicaux\n\nLes modèles de NLP sont tentants mais overkill pour un premier tri. Des heuristiques lexicales bien calibrées couvrent 70-80% des cas :\n\n```python\nINTENT_PATTERNS = {\n    'informational': [\n        r'\\b(comment|pourquoi|qu est ce|c est quoi|definition|guide|tutoriel|'\n        r'difference entre|vs|comparatif|que signifie|quand|combien)\\b',\n        r'\\b(how to|what is|why|when|tutorial|guide|explain)\\b'\n    ],\n    'transactional': [\n        r'\\b(acheter|achat|commander|prix|tarif|promo|soldes|pas cher|'\n        r'livraison|code promo|reduction|bon plan)\\b',\n        r'\\b(buy|price|cheap|deal|discount|order|shop)\\b'\n    ],\n    'commercial_investigation': [\n        r'\\b(meilleur|top \\d|avis|test|review|comparaison|alternative|'\n        r'quel choisir|lequel|recommandation)\\b',\n        r'\\b(best|top|review|comparison|versus|worth it)\\b'\n    ],\n    'navigational': [\n        r'\\b(login|connexion|mon compte|espace client|contact|'\n        r'service client|telephone|adresse)\\b'\n    ]\n}\n\nimport re\n\ndef classify_intent(query):\n    query_lower = query.lower()\n    for intent_type, patterns in INTENT_PATTERNS.items():\n        for pattern in patterns:\n            if re.search(pattern, query_lower):\n                return intent_type\n    return 'ambiguous'\n\ndf_nonbrand['query_intent'] = df_nonbrand['query'].apply(classify_intent)\n\n# Classifier aussi l'intent de la page via son URL pattern\ndef classify_page_intent(url):\n    if '/blog/' in url or '/guide/' in url or '/article/' in url:\n        return 'informational'\n    if '/produit/' in url or '/product/' in url or '/p/' in url:\n        return 'transactional'\n    if re.search(r'/categorie/|/collection/|/c/', url):\n        return 'transactional'\n    if re.search(r'/comparatif/|/avis/|/test/', url):\n        return 'commercial_investigation'\n    return 'ambiguous'\n\ndf_nonbrand['page_intent'] = df_nonbrand['page'].apply(classify_page_intent)\n\n# Identifier les mismatches\ndf_nonbrand['intent_mismatch'] = (\n    (df_nonbrand['query_intent'] != 'ambiguous') &\n    (df_nonbrand['page_intent'] != 'ambiguous') &\n    (df_nonbrand['query_intent'] != df_nonbrand['page_intent'])\n)\n\nmismatches = df_nonbrand[df_nonbrand['intent_mismatch']].sort_values(\n    'impressions', ascending=False\n)\nprint(f\"Intent mismatches: {len(mismatches)}\")\n```\n\n### Les limites de l'approche lexicale\n\nCette classification rate les requêtes ambiguës ou multi-intent. \"Nike Air Max 90\" est-elle transactionnelle ou navigationnelle ? Ça dépend du contexte utilisateur. Pour les cas ambigus, la SERP elle-même est le meilleur signal : si Google affiche 7 résultats produit et 3 résultats éditoriaux, l'intention dominante est transactionnelle.\n\nScreaming Frog permet de scraper les SERP à petite échelle pour valider la classification. Mais attention au rate limiting — Google tolère mal le scraping massif de ses résultats. Pour un volume important, des APIs tierces (SerpAPI, DataForSEO) sont plus fiables.\n\nL'article de Search Engine Land propose de croiser manuellement les données GSC avec l'analyse SERP. C'est pertinent sur 50 requêtes prioritaires. Sur 5 000, il faut automatiser.\n\n## Scénario concret : un media tech avec 3 200 articles\n\nUn site média tech publie 3 200 articles actifs (recevant au moins une impression/mois). L'extraction GSC sur 90 jours retourne 420 000 combinaisons page/query non-brand.\n\n### Les chiffres avant optimisation\n\nAprès application du modèle de CTR gap :\n\n- **12 400 combinaisons** avec un `ctr_gap` inférieur à -0.03 (intent gap significatif)\n- **Concentrées sur 380 pages** (11,8% du site)\n- **Impressions cumulées** de ces combinaisons : 2,1 millions sur 90 jours\n- **Clicks récupérables estimés** (en ramenant le CTR au niveau attendu) : ~45 000 clicks sur 90 jours\n\nLes patterns dominants identifiés :\n\n1. **Articles \"guide\" rankant sur des requêtes transactionnelles** (38% des mismatches). Exemple : un article \"Guide : comprendre les processeurs ARM\" ranke en position 4 sur \"acheter MacBook M3 Pro\". CTR : 0,8% vs 6,2% attendu. Google considère l'article pertinent thématiquement, mais l'utilisateur veut un comparateur ou une fiche produit.\n\n2. **Articles d'actualité anciens sur des requêtes evergreen** (27%). Un article de 2024 \"Apple annonce le M3\" ranke sur \"benchmark M3 vs M2\" en 2026. Le contenu est daté, l'utilisateur cherche des données actuelles.\n\n3. **Pages catégorie trop génériques sur des requêtes spécifiques** (22%). La page `/categorie/smartphones` ranke sur \"smartphone compact 2026 petit format\" — une intention très précise que la page catégorie ne satisfait pas.\n\n### Actions correctives\n\nPour le pattern 1, deux options : créer une page dédiée à l'intention transactionnelle, ou accepter que cette requête n'est pas votre cible. Si le volume justifie une page dédiée, créez-la et assurez le maillage interne. Si non, optimisez le title et la meta description de l'article existant pour mieux refléter son contenu informatif — les utilisateurs qui cherchent à acheter ne cliqueront toujours pas, mais ceux qui cherchent à comprendre (intention secondaire de la requête) cliqueront davantage.\n\nPour le pattern 2, c'est un problème de [thin content ou de contenu obsolète](/blog/thin-content-quand-vos-pages-nuisent-au-seo-global). Actualisez l'article ou redirigez-le vers un contenu frais. Le site média a actualisé 45 articles et en a redirigé 12 vers des versions plus récentes.\n\nPour le pattern 3, la solution est de créer du contenu intermédiaire. Entre la page catégorie et la fiche produit, il manque souvent une page de type \"guide d'achat\" ou \"sélection\" qui répond à l'investigation commerciale.\n\n### Résultat après 8 semaines\n\nLe site a traité les 120 pages avec le plus fort `weighted_gap` négatif. Résultat mesuré dans GSC :\n\n- CTR moyen des pages traitées : de 2,1% à 4,8%\n- Clicks organiques sur ces pages : +14 200 clicks/mois\n- Positions moyennes quasi inchangées (le contenu n'a pas bougé en ranking, c'est le CTR qui a été optimisé)\n\nCe dernier point est important : corriger un intent gap n'améliore pas nécessairement votre position. Il améliore votre taux de capture sur les positions existantes. Ce sont des clicks que vous laissiez sur la table.\n\n## Agréger les gaps au niveau page pour prioriser\n\nL'analyse par combinaison page/query donne la granularité. Mais pour prioriser les actions, il faut remonter au niveau page.\n\n```python\n# Agréger les métriques d'intent gap par page\npage_gaps = df_nonbrand.groupby('page').agg(\n    total_impressions=('impressions', 'sum'),\n    total_clicks=('clicks', 'sum'),\n    avg_ctr_gap=('ctr_gap', 'mean'),\n    total_weighted_gap=('weighted_gap', 'sum'),\n    n_queries=('query', 'count'),\n    n_mismatch=('intent_mismatch', 'sum'),\n    mismatch_impressions=('impressions', lambda x: x[df_nonbrand.loc[x.index, 'intent_mismatch']].sum()),\n    dominant_query_intent=('query_intent', lambda x: x.value_counts().index[0])\n).reset_index()\n\n# Score de priorité composite\npage_gaps['mismatch_ratio'] = page_gaps['n_mismatch'] / page_gaps['n_queries']\npage_gaps['priority_score'] = (\n    page_gaps['total_weighted_gap'].abs() *\n    (1 + page_gaps['mismatch_ratio'])\n)\n\n# Top 50 pages à traiter\npriority_pages = page_gaps.nlargest(50, 'priority_score')\npriority_pages[['page', 'total_impressions', 'avg_ctr_gap', 'mismatch_ratio', \n                'dominant_query_intent', 'priority_score']].to_csv(\n    'intent_gap_priorities.csv', index=False\n)\n```\n\nLe `mismatch_ratio` est révélateur. Une page dont 60% des requêtes sont d'un type d'intention différent de celui de la page a un problème structurel. Soit la page essaie de répondre à trop d'intentions à la fois, soit Google l'a mal catégorisée.\n\nPour visualiser ces données, Chrome DevTools n'est pas l'outil adapté — mais il reste indispensable pour [diagnostiquer les problèmes techniques](/blog/chrome-devtools-pour-le-seo-astuces-avancees) qui peuvent aggraver les intent gaps (temps de chargement, rendering JS incomplet).\n\n## Automatiser la détection continue des intent gaps\n\nL'analyse ponctuelle est utile. L'analyse continue est transformatrice. Les intent gaps évoluent : Google reteste constamment le positionnement, les intentions des utilisateurs changent avec les saisons, et vos modifications de contenu créent de nouveaux décalages.\n\n### Pipeline d'alertes automatisé\n\nUn cron hebdomadaire qui extrait les données GSC, calcule les gaps, et alerte sur les nouvelles dégradations :\n\n```python\n# alert_intent_gaps.py — à exécuter chaque lundi\nimport json\nfrom pathlib import Path\n\nHISTORY_FILE = 'gap_history.json'\nALERT_THRESHOLD = -0.05  # 5 points de CTR en dessous de l'attendu\nMIN_IMPRESSIONS = 50      # Minimum pour que le signal soit fiable\n\ndef load_history():\n    if Path(HISTORY_FILE).exists():\n        return json.loads(Path(HISTORY_FILE).read_text())\n    return {}\n\ndef detect_new_gaps(current_df, history):\n    alerts = []\n    current_gaps = current_df[\n        (current_df['ctr_gap'] \u003C ALERT_THRESHOLD) &\n        (current_df['impressions'] >= MIN_IMPRESSIONS)\n    ]\n    \n    for _, row in current_gaps.iterrows():\n        key = f\"{row['page']}|{row['query']}\"\n        previous_gap = history.get(key, {}).get('ctr_gap', 0)\n        \n        # Alerte si le gap est nouveau ou s'est aggravé de > 2 points\n        if previous_gap == 0 or (row['ctr_gap'] - previous_gap) \u003C -0.02:\n            alerts.append({\n                'page': row['page'],\n                'query': row['query'],\n                'position': round(row['position'], 1),\n                'ctr': round(row['ctr'] * 100, 2),\n                'expected_ctr': round(row['expected_ctr'] * 100, 2),\n                'gap': round(row['ctr_gap'] * 100, 2),\n                'impressions': int(row['impressions']),\n                'previous_gap': round(previous_gap * 100, 2)\n            })\n    \n    return sorted(alerts, key=lambda x: x['impressions'], reverse=True)\n\n# Intégrer dans un workflow Slack / email\ndef format_alert(alerts, top_n=15):\n    lines = [f\"🔍 {len(alerts)} nouveaux intent gaps détectés\\n\"]\n    for a in alerts[:top_n]:\n        lines.append(\n            f\"  {a['page'].split('/')[-1]}\\n\"\n            f\"    Query: {a['query']}\\n\"\n            f\"    Pos: {a['position']} | CTR: {a['ctr']}% vs {a['expected_ctr']}% attendu \"\n            f\"(gap: {a['gap']}pp) | {a['impressions']} impr.\"\n        )\n    return '\\n'.join(lines)\n```\n\nCette approche de monitoring continu rejoint la philosophie de détection de régressions. Si vous modifiez un title tag ou restructurez une page, un outil de monitoring comme Seogard détecte la modification technique. Le pipeline ci-dessus détecte l'impact sur l'alignement d'intention. Les deux sont complémentaires.\n\n### Croiser avec les données de crawl\n\nLes intent gaps ont parfois une cause technique invisible dans GSC. Une page qui charge son contenu principal en JavaScript côté client peut afficher un contenu différent au crawler et à l'utilisateur. Le résultat : Google indexe un contenu partiel, le positionne sur des requêtes qui matchent ce contenu partiel, et l'utilisateur qui arrive voit un contenu différent.\n\nPour détecter ce type de décalage, croisez vos données d'intent gap avec un crawl Screaming Frog configuré en mode rendu JavaScript. Comparez le title, le H1 et les 500 premiers caractères du body entre le rendu HTML brut et le rendu JS. Toute différence significative est un candidat pour expliquer un intent gap technique.\n\nLes [rapports GSC sous-utilisés](/blog/google-search-console-les-rapports-que-vous-ignorez) peuvent aussi révéler des anomalies : le rapport de couverture montre si Google indexe bien toutes vos pages, et le rapport d'amélioration signale les problèmes de données structurées qui affectent l'apparence en SERP.\n\n## Au-delà des données GSC : enrichir l'analyse avec des signaux externes\n\nGSC est la source de vérité pour les données de positionnement et de CTR. Mais elle a des angles morts.\n\n### Ce que GSC ne vous dit pas\n\n- **Le comportement post-clic** : un utilisateur qui clique et revient en 3 secondes sur la SERP (pogo-sticking) confirme l'intent gap, mais GSC n'enregistre que le click. Croisez avec vos données Analytics pour mesurer le taux de rebond par landing page × source organique.\n- **Les requêtes que vous ne voyez pas** : GSC masque les requêtes à très faible volume et les requêtes sensibles. Sur certains sites, 30-40% des impressions tombent dans la catégorie \"other\" non détaillée.\n- **L'évolution de l'intention** : une requête comme \"IA générative\" était informationnelle en 2023 (\"qu'est-ce que c'est ?\"), puis est devenue transactionnelle en 2025 (\"quel outil acheter ?\"). GSC montre le CTR qui baisse, pas le pourquoi.\n\nPour aller plus loin, les données de contenu elles-mêmes comptent. Si vous produisez du [contenu conçu pour les systèmes d'IA](/blog/how-to-design-content-that-ai-systems-prefer-and-promote), la question de l'alignement d'intention se pose aussi pour les AI Overviews de Google. Un intent gap dans les résultats classiques se manifeste différemment quand un AI Overview capte une partie du trafic en haut de SERP.\n\n### Enrichir via les données structurées de la SERP\n\nPour les 50 requêtes prioritaires, une analyse manuelle de la SERP reste le gold standard. Notez :\n\n- Le type de résultats affichés (PAA, featured snippet, images, shopping, vidéos)\n- Le ratio pages informationnelles / transactionnelles dans le top 10\n- La présence d'un AI Overview et son contenu\n\nSi 8 résultats sur 10 sont des guides d'achat et votre page est un article de blog technique, l'intent dominant est commercial. Votre page est en décalage structurel, et aucune optimisation de title ne corrigera le problème — il faut un contenu différent, ou il faut accepter que cette requête n'est pas votre bataille.\n\n## Transformer les intent gaps en roadmap produit\n\nLes intent gaps ne sont pas qu'un problème de SEO. Ce sont des signaux produit. Quand votre site e-commerce ranke sur \"alternative [produit concurrent]\" et que vous n'avez pas de page de comparaison, ce n'est pas juste un gap de contenu — c'est un gap dans votre stratégie d'acquisition.\n\nStructurez les résultats de l'analyse en trois buckets :\n\n1. **Quick wins** : gap de CTR corrigeable par une optimisation de title/meta description. La page répond à l'intention, mais le snippet ne le communique pas. Effort : 30 minutes par page. Impact typique : +2 à 4 points de CTR.\n\n2. **Content gaps** : la page existe mais ne couvre pas l'angle attendu. Il faut restructurer ou enrichir le contenu. Effort : 2-4 heures par page. Impact : variable, mais peut doubler le CTR sur les requêtes cibles.\n\n3. **Structural gaps** : il manque un type de page entier dans votre architecture. Pages comparatives, guides d'achat, pages FAQ dédiées. Effort : création de contenu + intégration dans le maillage interne. Impact : capture de trafic entièrement nouveau.\n\nPour les [KPIs à suivre](/blog/mesurer-l-impact-seo-technique-quels-kpis-suivre) après correction, ne vous limitez pas au CTR. Mesurez le taux de conversion par landing page organique. Un intent gap corrigé devrait améliorer à la fois le CTR (plus de clicks) et le taux de conversion (des clicks mieux qualifiés).\n\nLes intent gaps mesurés via GSC sont la donnée la plus sous-exploitée du SEO technique. Chaque site perd du trafic qualifié non pas parce qu'il manque de positions, mais parce que ses positions existantes sont mal alignées avec la demande réelle. L'extraction systématique via l'API, la classification automatisée, et le monitoring continu transforment ce diagnostic en avantage concurrentiel mesurable. Des outils comme Seogard complètent cette approche en détectant les régressions techniques qui créent de nouveaux intent gaps — un title modifié par erreur, un SSR cassé qui altère le contenu crawlé, un canonical mal configuré qui oriente Google vers la mauvaise page.\n```","https://seogard.io/blog/how-to-measure-intent-gaps-using-google-search-console-data","Actualités SEO","2026-04-09T23:50:30.959Z","2026-04-09","Méthode technique pour détecter les décalages entre l'intention de recherche réelle et le positionnement de vos pages via les données GSC.","\u003Cp>Une page catégorie d'un e-commerce mode se positionne sur 340 requêtes dans Google Search Console. CTR moyen : 1,2%. En isolant les 40 requêtes alignées avec l'intent réel de la page, le CTR monte à 8,7%. Les 300 autres requêtes sont des intent gaps — des signaux de décalage entre ce que Google pense que votre page répond et ce que les utilisateurs cherchent réellement.\u003C/p>\n\u003Ch2>Ce qu'est un intent gap (et pourquoi le CTR seul ne suffit pas)\u003C/h2>\n\u003Cp>Un intent gap n'est pas simplement un mot-clé sur lequel vous ne rankez pas. C'est un décalage mesurable entre l'intention de recherche d'un utilisateur et la réponse que votre page fournit, alors même que Google vous positionne sur cette requête.\u003C/p>\n\u003Cp>Prenez une page \u003Ccode>/chaussures-running-homme\u003C/code> qui reçoit des impressions sur la requête \"avis chaussures running pronateur\". Google considère la page suffisamment pertinente pour l'afficher. L'utilisateur, lui, cherche un contenu éditorial comparatif. Il voit un listing produit, ne clique pas (ou clique et rebondit). C'est un intent gap.\u003C/p>\n\u003Cp>Le CTR brut par requête est un indicateur, mais il masque le problème. Un CTR de 2% en position 8 est normal. Un CTR de 2% en position 2, c'est un signal d'alerte. La métrique qui révèle les intent gaps, c'est le \u003Cstrong>ratio CTR/position attendu\u003C/strong> — l'écart entre le CTR que vous devriez avoir à une position donnée et celui que vous obtenez réellement.\u003C/p>\n\u003Cp>Les courbes de CTR par position varient selon les industries et les types de SERP (présence de featured snippets, de PAA, de résultats shopping). Mais les études de référence comme celles d'Advanced Web Ranking montrent des patterns stables : position 1 entre 25% et 35% de CTR, position 3 entre 8% et 15%, position 5 entre 4% et 7%. Tout écart significatif à la baisse pour une position donnée mérite investigation.\u003C/p>\n\u003Cp>L'approche proposée par Search Engine Land dans leur article récent va dans le bon sens en utilisant GSC comme source de données brutes. Mais elle reste en surface. Ce qui suit est une méthode technique complète pour extraire, classifier et prioriser les intent gaps à l'échelle.\u003C/p>\n\u003Ch2>Extraire les données brutes via l'API GSC\u003C/h2>\n\u003Cp>L'interface web de Google Search Console est limitée à 1 000 lignes par export et ne permet pas de croiser facilement pages et requêtes. Pour une analyse sérieuse sur un site de plus de 500 pages, l'API est indispensable.\u003C/p>\n\u003Ch3>Configuration de l'extraction\u003C/h3>\n\u003Cp>Voici un script Python qui extrait les données page + query pour les 90 derniers jours, avec gestion de la pagination :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> google.oauth2 \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> service_account\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> googleapiclient.discovery \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pandas \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> datetime \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> datetime, timedelta\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">SCOPES\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://www.googleapis.com/auth/webmasters.readonly'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">SERVICE_ACCOUNT_FILE\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'credentials.json'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">SITE_URL\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'https://www.votresite-ecommerce.fr'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">credentials \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> service_account.Credentials.from_service_account_file(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    SERVICE_ACCOUNT_FILE\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">scopes\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">SCOPES\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">service \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> build(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'searchconsole'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'v1'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">credentials\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">credentials)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">end_date \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> datetime.now() \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> timedelta(\u003C/span>\u003Cspan style=\"color:#FFAB70\">days\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">3\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)  \u003C/span>\u003Cspan style=\"color:#6A737D\"># GSC a ~3j de latence\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">start_date \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> end_date \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> timedelta(\u003C/span>\u003Cspan style=\"color:#FFAB70\">days\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">90\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">all_rows \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">start_row \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">ROW_LIMIT\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 25000\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">while\u003C/span>\u003Cspan style=\"color:#79B8FF\"> True\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    request \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'startDate'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: start_date.strftime(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'%Y-%m-\u003C/span>\u003Cspan style=\"color:#79B8FF\">%d\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'endDate'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: end_date.strftime(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'%Y-%m-\u003C/span>\u003Cspan style=\"color:#79B8FF\">%d\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'dimensions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'rowLimit'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">ROW_LIMIT\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'startRow'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: start_row,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'dimensionFilterGroups'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'filters'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'dimension'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'country'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'expression'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'fra'\u003C/span>\u003Cspan style=\"color:#6A737D\">  # Filtrer par pays cible\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            }]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        }]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    response \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> service.searchanalytics().query(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">        siteUrl\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">SITE_URL\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">body\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">request\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ).execute()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    rows \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> response.get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'rows'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, [])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#F97583\"> not\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> rows:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        break\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> row \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> rows:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        all_rows.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'keys'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">][\u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'keys'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">][\u003C/span>\u003Cspan style=\"color:#79B8FF\">1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'clicks'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'clicks'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    start_row \u003C/span>\u003Cspan style=\"color:#F97583\">+=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ROW_LIMIT\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#79B8FF\"> len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(rows) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ROW_LIMIT\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        break\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pd.DataFrame(all_rows)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df.to_csv(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'gsc_page_query_raw.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">index\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">False\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Extracted \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(df)\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> page/query combinations\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Sur un site e-commerce de 12 000 pages actives, cette extraction retourne typiquement entre 200 000 et 800 000 combinaisons page/query. C'est cette granularité qui permet l'analyse des intent gaps — impossible à obtenir via l'interface web.\u003C/p>\n\u003Cp>Pour automatiser cette extraction, vous pouvez consulter notre guide sur \u003Ca href=\"/blog/search-console-api-automatiser-le-reporting-seo\">l'automatisation du reporting SEO via l'API Search Console\u003C/a>.\u003C/p>\n\u003Ch3>Nettoyage et filtrage\u003C/h3>\n\u003Cp>Filtrez impérativement les données avant analyse. Les requêtes avec moins de 10 impressions sur 90 jours sont du bruit statistique. Les requêtes de marque doivent être isolées — elles faussent les moyennes de CTR.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Filtrer le bruit et segmenter\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_clean \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df[df[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">>=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 10\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].copy()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Séparer requêtes brand / non-brand\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">BRAND_TERMS\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'votresite'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'votre-site'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'votre site'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_clean[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'is_brand'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_clean[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].str.lower().apply(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    lambda\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> q: \u003C/span>\u003Cspan style=\"color:#79B8FF\">any\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(term \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> q \u003C/span>\u003Cspan style=\"color:#F97583\">for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> term \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#79B8FF\"> BRAND_TERMS\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_clean[\u003C/span>\u003Cspan style=\"color:#F97583\">~\u003C/span>\u003Cspan style=\"color:#E1E4E8\">df_clean[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'is_brand'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]].copy()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Non-brand combinations: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(df_nonbrand)\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch2>Calculer le score d'intent gap par combinaison page/query\u003C/h2>\n\u003Cp>Le cœur de la méthode repose sur la comparaison entre le CTR observé et le CTR attendu à une position donnée. Un écart négatif important signale un décalage d'intention.\u003C/p>\n\u003Ch3>Modèle de CTR attendu\u003C/h3>\n\u003Cp>Plutôt que d'utiliser des benchmarks externes, construisez votre propre courbe de CTR à partir de vos données GSC. Chaque site a un profil de CTR différent selon sa présence en SERP, la nature de ses snippets, et son secteur.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> numpy \u003C/span>\u003Cspan style=\"color:#F97583\">as\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> np\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Calculer le CTR médian par tranche de position (sur vos propres données)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position_bucket'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].apply(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    lambda\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> p: \u003C/span>\u003Cspan style=\"color:#79B8FF\">min\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(p), \u003C/span>\u003Cspan style=\"color:#79B8FF\">20\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)  \u003C/span>\u003Cspan style=\"color:#6A737D\"># Regrouper positions > 20\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Utiliser la médiane plutôt que la moyenne (résistante aux outliers)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">ctr_benchmarks \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand.groupby(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position_bucket'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).agg(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    median_ctr\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'median'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    count\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'size'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">).reset_index()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Créer un dictionnaire de lookup\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">ctr_expected \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> dict\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#79B8FF\">zip\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ctr_benchmarks[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position_bucket'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ctr_benchmarks[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'median_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Calculer le gap score pour chaque combinaison\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'expected_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position_bucket'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].map(ctr_expected)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'expected_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Pondérer par les impressions (un gap sur 5000 impressions > gap sur 15)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'weighted_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Trier par impact négatif\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">intent_gaps \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#F97583\"> -\u003C/span>\u003Cspan style=\"color:#79B8FF\">0.02\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].sort_values(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'weighted_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">ascending\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">True\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Intent gaps detected: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(intent_gaps)\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(intent_gaps[[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'expected_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]].head(\u003C/span>\u003Cspan style=\"color:#79B8FF\">20\u003C/span>\u003Cspan style=\"color:#E1E4E8\">))\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>Interpréter le score\u003C/h3>\n\u003Cp>Un \u003Ccode>ctr_gap\u003C/code> de -0.05 en position 3 signifie que votre page obtient 5 points de CTR de moins que la médiane de votre site à cette position. Les causes possibles :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Décalage d'intent pur\u003C/strong> : la requête est informationnelle mais la page est transactionnelle (ou l'inverse).\u003C/li>\n\u003Cli>\u003Cstrong>Snippet mal optimisé\u003C/strong> : le title ou la meta description ne reflète pas le contenu attendu pour cette requête.\u003C/li>\n\u003Cli>\u003Cstrong>Cannibalisation\u003C/strong> : une autre page de votre site se positionne aussi, et l'utilisateur hésite ou choisit l'autre.\u003C/li>\n\u003Cli>\u003Cstrong>SERP features\u003C/strong> : un featured snippet ou un PAA capte l'attention au-dessus de votre résultat.\u003C/li>\n\u003C/ul>\n\u003Cp>Le \u003Ccode>weighted_gap\u003C/code> permet de prioriser : un écart de 3 points de CTR sur une requête à 8 000 impressions/mois vaut plus qu'un écart de 10 points sur 20 impressions.\u003C/p>\n\u003Ch2>Classifier l'intention à l'échelle avec des heuristiques\u003C/h2>\n\u003Cp>L'étape suivante consiste à attribuer un type d'intention à chaque requête pour identifier les patterns. Quatre catégories suffisent en pratique : informationnelle, transactionnelle, navigationnelle, investigation commerciale.\u003C/p>\n\u003Ch3>Classification par signaux lexicaux\u003C/h3>\n\u003Cp>Les modèles de NLP sont tentants mais overkill pour un premier tri. Des heuristiques lexicales bien calibrées couvrent 70-80% des cas :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">INTENT_PATTERNS\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'informational'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">comment\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">pourquoi\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">qu est ce\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">c est quoi\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">definition\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">guide\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">tutoriel\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">difference entre\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">vs\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">comparatif\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">que signifie\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">quand\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">combien)\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">how to\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">what is\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">why\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">when\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">tutorial\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">guide\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">explain\u003C/span>\u003Cspan style=\"color:#79B8FF\">)\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'transactional'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">acheter\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">achat\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">commander\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">prix\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">tarif\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">promo\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">soldes\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">pas cher\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">livraison\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">code promo\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">reduction\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">bon plan)\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">buy\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">price\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">cheap\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">deal\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">discount\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">order\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">shop\u003C/span>\u003Cspan style=\"color:#79B8FF\">)\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'commercial_investigation'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">meilleur\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">top \u003C/span>\u003Cspan style=\"color:#79B8FF\">\\d\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">avis\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">test\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">review\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">comparaison\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">alternative\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">quel choisir\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">lequel\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">recommandation)\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">best\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">top\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">review\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">comparison\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">versus\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">worth it\u003C/span>\u003Cspan style=\"color:#79B8FF\">)\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'navigational'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b(\u003C/span>\u003Cspan style=\"color:#DBEDFF\">login\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">connexion\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">mon compte\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">espace client\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">contact\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">service client\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">telephone\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">adresse)\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\b\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> re\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> classify_intent\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(query):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    query_lower \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> query.lower()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> intent_type, patterns \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#79B8FF\"> INTENT_PATTERNS\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.items():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pattern \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> patterns:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> re.search(pattern, query_lower):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">                return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> intent_type\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'ambiguous'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].apply(classify_intent)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Classifier aussi l'intent de la page via son URL pattern\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> classify_page_intent\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(url):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '/blog/'\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '/guide/'\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '/article/'\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'informational'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '/produit/'\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '/product/'\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url \u003C/span>\u003Cspan style=\"color:#F97583\">or\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '/p/'\u003C/span>\u003Cspan style=\"color:#F97583\"> in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> url:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'transactional'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> re.search(\u003C/span>\u003Cspan style=\"color:#F97583\">r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">/categorie/\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">/collection/\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">/c/\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, url):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'transactional'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> re.search(\u003C/span>\u003Cspan style=\"color:#F97583\">r\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#DBEDFF\">/comparatif/\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">/avis/\u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003Cspan style=\"color:#DBEDFF\">/test/\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, url):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'commercial_investigation'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'ambiguous'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].apply(classify_page_intent)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Identifier les mismatches\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'intent_mismatch'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'ambiguous'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'ambiguous'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">!=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">mismatches \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand[df_nonbrand[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'intent_mismatch'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]].sort_values(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">ascending\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">False\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"Intent mismatches: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(mismatches)\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>Les limites de l'approche lexicale\u003C/h3>\n\u003Cp>Cette classification rate les requêtes ambiguës ou multi-intent. \"Nike Air Max 90\" est-elle transactionnelle ou navigationnelle ? Ça dépend du contexte utilisateur. Pour les cas ambigus, la SERP elle-même est le meilleur signal : si Google affiche 7 résultats produit et 3 résultats éditoriaux, l'intention dominante est transactionnelle.\u003C/p>\n\u003Cp>Screaming Frog permet de scraper les SERP à petite échelle pour valider la classification. Mais attention au rate limiting — Google tolère mal le scraping massif de ses résultats. Pour un volume important, des APIs tierces (SerpAPI, DataForSEO) sont plus fiables.\u003C/p>\n\u003Cp>L'article de Search Engine Land propose de croiser manuellement les données GSC avec l'analyse SERP. C'est pertinent sur 50 requêtes prioritaires. Sur 5 000, il faut automatiser.\u003C/p>\n\u003Ch2>Scénario concret : un media tech avec 3 200 articles\u003C/h2>\n\u003Cp>Un site média tech publie 3 200 articles actifs (recevant au moins une impression/mois). L'extraction GSC sur 90 jours retourne 420 000 combinaisons page/query non-brand.\u003C/p>\n\u003Ch3>Les chiffres avant optimisation\u003C/h3>\n\u003Cp>Après application du modèle de CTR gap :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>12 400 combinaisons\u003C/strong> avec un \u003Ccode>ctr_gap\u003C/code> inférieur à -0.03 (intent gap significatif)\u003C/li>\n\u003Cli>\u003Cstrong>Concentrées sur 380 pages\u003C/strong> (11,8% du site)\u003C/li>\n\u003Cli>\u003Cstrong>Impressions cumulées\u003C/strong> de ces combinaisons : 2,1 millions sur 90 jours\u003C/li>\n\u003Cli>\u003Cstrong>Clicks récupérables estimés\u003C/strong> (en ramenant le CTR au niveau attendu) : ~45 000 clicks sur 90 jours\u003C/li>\n\u003C/ul>\n\u003Cp>Les patterns dominants identifiés :\u003C/p>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Articles \"guide\" rankant sur des requêtes transactionnelles\u003C/strong> (38% des mismatches). Exemple : un article \"Guide : comprendre les processeurs ARM\" ranke en position 4 sur \"acheter MacBook M3 Pro\". CTR : 0,8% vs 6,2% attendu. Google considère l'article pertinent thématiquement, mais l'utilisateur veut un comparateur ou une fiche produit.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Articles d'actualité anciens sur des requêtes evergreen\u003C/strong> (27%). Un article de 2024 \"Apple annonce le M3\" ranke sur \"benchmark M3 vs M2\" en 2026. Le contenu est daté, l'utilisateur cherche des données actuelles.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Pages catégorie trop génériques sur des requêtes spécifiques\u003C/strong> (22%). La page \u003Ccode>/categorie/smartphones\u003C/code> ranke sur \"smartphone compact 2026 petit format\" — une intention très précise que la page catégorie ne satisfait pas.\u003C/p>\n\u003C/li>\n\u003C/ol>\n\u003Ch3>Actions correctives\u003C/h3>\n\u003Cp>Pour le pattern 1, deux options : créer une page dédiée à l'intention transactionnelle, ou accepter que cette requête n'est pas votre cible. Si le volume justifie une page dédiée, créez-la et assurez le maillage interne. Si non, optimisez le title et la meta description de l'article existant pour mieux refléter son contenu informatif — les utilisateurs qui cherchent à acheter ne cliqueront toujours pas, mais ceux qui cherchent à comprendre (intention secondaire de la requête) cliqueront davantage.\u003C/p>\n\u003Cp>Pour le pattern 2, c'est un problème de \u003Ca href=\"/blog/thin-content-quand-vos-pages-nuisent-au-seo-global\">thin content ou de contenu obsolète\u003C/a>. Actualisez l'article ou redirigez-le vers un contenu frais. Le site média a actualisé 45 articles et en a redirigé 12 vers des versions plus récentes.\u003C/p>\n\u003Cp>Pour le pattern 3, la solution est de créer du contenu intermédiaire. Entre la page catégorie et la fiche produit, il manque souvent une page de type \"guide d'achat\" ou \"sélection\" qui répond à l'investigation commerciale.\u003C/p>\n\u003Ch3>Résultat après 8 semaines\u003C/h3>\n\u003Cp>Le site a traité les 120 pages avec le plus fort \u003Ccode>weighted_gap\u003C/code> négatif. Résultat mesuré dans GSC :\u003C/p>\n\u003Cul>\n\u003Cli>CTR moyen des pages traitées : de 2,1% à 4,8%\u003C/li>\n\u003Cli>Clicks organiques sur ces pages : +14 200 clicks/mois\u003C/li>\n\u003Cli>Positions moyennes quasi inchangées (le contenu n'a pas bougé en ranking, c'est le CTR qui a été optimisé)\u003C/li>\n\u003C/ul>\n\u003Cp>Ce dernier point est important : corriger un intent gap n'améliore pas nécessairement votre position. Il améliore votre taux de capture sur les positions existantes. Ce sont des clicks que vous laissiez sur la table.\u003C/p>\n\u003Ch2>Agréger les gaps au niveau page pour prioriser\u003C/h2>\n\u003Cp>L'analyse par combinaison page/query donne la granularité. Mais pour prioriser les actions, il faut remonter au niveau page.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Agréger les métriques d'intent gap par page\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">page_gaps \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> df_nonbrand.groupby(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).agg(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    total_impressions\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sum'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    total_clicks\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'clicks'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sum'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    avg_ctr_gap\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'mean'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    total_weighted_gap\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'weighted_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sum'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    n_queries\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'count'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    n_mismatch\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'intent_mismatch'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sum'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    mismatch_impressions\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#F97583\">lambda\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> x: x[df_nonbrand.loc[x.index, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'intent_mismatch'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]].sum()),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#FFAB70\">    dominant_query_intent\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#F97583\">lambda\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> x: x.value_counts().index[\u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">).reset_index()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Score de priorité composite\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">page_gaps[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'mismatch_ratio'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> page_gaps[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'n_mismatch'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">/\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> page_gaps[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'n_queries'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">page_gaps[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'priority_score'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    page_gaps[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'total_weighted_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].abs() \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    (\u003C/span>\u003Cspan style=\"color:#79B8FF\">1\u003C/span>\u003Cspan style=\"color:#F97583\"> +\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> page_gaps[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'mismatch_ratio'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">])\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Top 50 pages à traiter\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">priority_pages \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> page_gaps.nlargest(\u003C/span>\u003Cspan style=\"color:#79B8FF\">50\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'priority_score'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">priority_pages[[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'total_impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'avg_ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'mismatch_ratio'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'dominant_query_intent'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'priority_score'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]].to_csv(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'intent_gap_priorities.csv'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">index\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">False\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le \u003Ccode>mismatch_ratio\u003C/code> est révélateur. Une page dont 60% des requêtes sont d'un type d'intention différent de celui de la page a un problème structurel. Soit la page essaie de répondre à trop d'intentions à la fois, soit Google l'a mal catégorisée.\u003C/p>\n\u003Cp>Pour visualiser ces données, Chrome DevTools n'est pas l'outil adapté — mais il reste indispensable pour \u003Ca href=\"/blog/chrome-devtools-pour-le-seo-astuces-avancees\">diagnostiquer les problèmes techniques\u003C/a> qui peuvent aggraver les intent gaps (temps de chargement, rendering JS incomplet).\u003C/p>\n\u003Ch2>Automatiser la détection continue des intent gaps\u003C/h2>\n\u003Cp>L'analyse ponctuelle est utile. L'analyse continue est transformatrice. Les intent gaps évoluent : Google reteste constamment le positionnement, les intentions des utilisateurs changent avec les saisons, et vos modifications de contenu créent de nouveaux décalages.\u003C/p>\n\u003Ch3>Pipeline d'alertes automatisé\u003C/h3>\n\u003Cp>Un cron hebdomadaire qui extrait les données GSC, calcule les gaps, et alerte sur les nouvelles dégradations :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># alert_intent_gaps.py — à exécuter chaque lundi\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> json\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> pathlib \u003C/span>\u003Cspan style=\"color:#F97583\">import\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Path\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">HISTORY_FILE\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> 'gap_history.json'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">ALERT_THRESHOLD\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#F97583\"> -\u003C/span>\u003Cspan style=\"color:#79B8FF\">0.05\u003C/span>\u003Cspan style=\"color:#6A737D\">  # 5 points de CTR en dessous de l'attendu\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">MIN_IMPRESSIONS\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 50\u003C/span>\u003Cspan style=\"color:#6A737D\">      # Minimum pour que le signal soit fiable\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> load_history\u003C/span>\u003Cspan style=\"color:#E1E4E8\">():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> Path(\u003C/span>\u003Cspan style=\"color:#79B8FF\">HISTORY_FILE\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).exists():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> json.loads(Path(\u003C/span>\u003Cspan style=\"color:#79B8FF\">HISTORY_FILE\u003C/span>\u003Cspan style=\"color:#E1E4E8\">).read_text())\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> detect_new_gaps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(current_df, history):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    alerts \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> []\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    current_gaps \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> current_df[\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        (current_df[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#79B8FF\"> ALERT_THRESHOLD\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x26;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        (current_df[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">>=\u003C/span>\u003Cspan style=\"color:#79B8FF\"> MIN_IMPRESSIONS\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    ]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> _, row \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> current_gaps.iterrows():\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        key \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#F97583\"> f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">|\u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        previous_gap \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> history.get(key, {}).get(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">0\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        # Alerte si le gap est nouveau ou s'est aggravé de > 2 points\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        if\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> previous_gap \u003C/span>\u003Cspan style=\"color:#F97583\">==\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 0\u003C/span>\u003Cspan style=\"color:#F97583\"> or\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> previous_gap) \u003C/span>\u003Cspan style=\"color:#F97583\">&#x3C;\u003C/span>\u003Cspan style=\"color:#F97583\"> -\u003C/span>\u003Cspan style=\"color:#79B8FF\">0.02\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            alerts.append({\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">round\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">], \u003C/span>\u003Cspan style=\"color:#79B8FF\">1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">round\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'expected_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">round\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'expected_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">round\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">] \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">int\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(row[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]),\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">                'previous_gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">round\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(previous_gap \u003C/span>\u003Cspan style=\"color:#F97583\">*\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 100\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">            })\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    \u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#79B8FF\"> sorted\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(alerts, \u003C/span>\u003Cspan style=\"color:#FFAB70\">key\u003C/span>\u003Cspan style=\"color:#F97583\">=lambda\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> x: x[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">], \u003C/span>\u003Cspan style=\"color:#FFAB70\">reverse\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">True\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Intégrer dans un workflow Slack / email\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C/span>\u003Cspan style=\"color:#B392F0\"> format_alert\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(alerts, top_n\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#79B8FF\">15\u003C/span>\u003Cspan style=\"color:#E1E4E8\">):\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    lines \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> [\u003C/span>\u003Cspan style=\"color:#F97583\">f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"🔍 \u003C/span>\u003Cspan style=\"color:#79B8FF\">{len\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(alerts)\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> nouveaux intent gaps détectés\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> a \u003C/span>\u003Cspan style=\"color:#F97583\">in\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> alerts[:top_n]:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        lines.append(\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"  \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'page'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">].split(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'/'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">)[\u003C/span>\u003Cspan style=\"color:#F97583\">-\u003C/span>\u003Cspan style=\"color:#79B8FF\">1\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"    Query: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'query'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"    Pos: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'position'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> | CTR: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">% vs \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'expected_ctr'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">% attendu \"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">            f\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"(gap: \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'gap'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\">pp) | \u003C/span>\u003Cspan style=\"color:#79B8FF\">{\u003C/span>\u003Cspan style=\"color:#E1E4E8\">a[\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'impressions'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003Cspan style=\"color:#79B8FF\">}\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> impr.\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        )\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> '\u003C/span>\u003Cspan style=\"color:#79B8FF\">\\n\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.join(lines)\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Cette approche de monitoring continu rejoint la philosophie de détection de régressions. Si vous modifiez un title tag ou restructurez une page, un outil de monitoring comme Seogard détecte la modification technique. Le pipeline ci-dessus détecte l'impact sur l'alignement d'intention. Les deux sont complémentaires.\u003C/p>\n\u003Ch3>Croiser avec les données de crawl\u003C/h3>\n\u003Cp>Les intent gaps ont parfois une cause technique invisible dans GSC. Une page qui charge son contenu principal en JavaScript côté client peut afficher un contenu différent au crawler et à l'utilisateur. Le résultat : Google indexe un contenu partiel, le positionne sur des requêtes qui matchent ce contenu partiel, et l'utilisateur qui arrive voit un contenu différent.\u003C/p>\n\u003Cp>Pour détecter ce type de décalage, croisez vos données d'intent gap avec un crawl Screaming Frog configuré en mode rendu JavaScript. Comparez le title, le H1 et les 500 premiers caractères du body entre le rendu HTML brut et le rendu JS. Toute différence significative est un candidat pour expliquer un intent gap technique.\u003C/p>\n\u003Cp>Les \u003Ca href=\"/blog/google-search-console-les-rapports-que-vous-ignorez\">rapports GSC sous-utilisés\u003C/a> peuvent aussi révéler des anomalies : le rapport de couverture montre si Google indexe bien toutes vos pages, et le rapport d'amélioration signale les problèmes de données structurées qui affectent l'apparence en SERP.\u003C/p>\n\u003Ch2>Au-delà des données GSC : enrichir l'analyse avec des signaux externes\u003C/h2>\n\u003Cp>GSC est la source de vérité pour les données de positionnement et de CTR. Mais elle a des angles morts.\u003C/p>\n\u003Ch3>Ce que GSC ne vous dit pas\u003C/h3>\n\u003Cul>\n\u003Cli>\u003Cstrong>Le comportement post-clic\u003C/strong> : un utilisateur qui clique et revient en 3 secondes sur la SERP (pogo-sticking) confirme l'intent gap, mais GSC n'enregistre que le click. Croisez avec vos données Analytics pour mesurer le taux de rebond par landing page × source organique.\u003C/li>\n\u003Cli>\u003Cstrong>Les requêtes que vous ne voyez pas\u003C/strong> : GSC masque les requêtes à très faible volume et les requêtes sensibles. Sur certains sites, 30-40% des impressions tombent dans la catégorie \"other\" non détaillée.\u003C/li>\n\u003Cli>\u003Cstrong>L'évolution de l'intention\u003C/strong> : une requête comme \"IA générative\" était informationnelle en 2023 (\"qu'est-ce que c'est ?\"), puis est devenue transactionnelle en 2025 (\"quel outil acheter ?\"). GSC montre le CTR qui baisse, pas le pourquoi.\u003C/li>\n\u003C/ul>\n\u003Cp>Pour aller plus loin, les données de contenu elles-mêmes comptent. Si vous produisez du \u003Ca href=\"/blog/how-to-design-content-that-ai-systems-prefer-and-promote\">contenu conçu pour les systèmes d'IA\u003C/a>, la question de l'alignement d'intention se pose aussi pour les AI Overviews de Google. Un intent gap dans les résultats classiques se manifeste différemment quand un AI Overview capte une partie du trafic en haut de SERP.\u003C/p>\n\u003Ch3>Enrichir via les données structurées de la SERP\u003C/h3>\n\u003Cp>Pour les 50 requêtes prioritaires, une analyse manuelle de la SERP reste le gold standard. Notez :\u003C/p>\n\u003Cul>\n\u003Cli>Le type de résultats affichés (PAA, featured snippet, images, shopping, vidéos)\u003C/li>\n\u003Cli>Le ratio pages informationnelles / transactionnelles dans le top 10\u003C/li>\n\u003Cli>La présence d'un AI Overview et son contenu\u003C/li>\n\u003C/ul>\n\u003Cp>Si 8 résultats sur 10 sont des guides d'achat et votre page est un article de blog technique, l'intent dominant est commercial. Votre page est en décalage structurel, et aucune optimisation de title ne corrigera le problème — il faut un contenu différent, ou il faut accepter que cette requête n'est pas votre bataille.\u003C/p>\n\u003Ch2>Transformer les intent gaps en roadmap produit\u003C/h2>\n\u003Cp>Les intent gaps ne sont pas qu'un problème de SEO. Ce sont des signaux produit. Quand votre site e-commerce ranke sur \"alternative [produit concurrent]\" et que vous n'avez pas de page de comparaison, ce n'est pas juste un gap de contenu — c'est un gap dans votre stratégie d'acquisition.\u003C/p>\n\u003Cp>Structurez les résultats de l'analyse en trois buckets :\u003C/p>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Quick wins\u003C/strong> : gap de CTR corrigeable par une optimisation de title/meta description. La page répond à l'intention, mais le snippet ne le communique pas. Effort : 30 minutes par page. Impact typique : +2 à 4 points de CTR.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Content gaps\u003C/strong> : la page existe mais ne couvre pas l'angle attendu. Il faut restructurer ou enrichir le contenu. Effort : 2-4 heures par page. Impact : variable, mais peut doubler le CTR sur les requêtes cibles.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>Structural gaps\u003C/strong> : il manque un type de page entier dans votre architecture. Pages comparatives, guides d'achat, pages FAQ dédiées. Effort : création de contenu + intégration dans le maillage interne. Impact : capture de trafic entièrement nouveau.\u003C/p>\n\u003C/li>\n\u003C/ol>\n\u003Cp>Pour les \u003Ca href=\"/blog/mesurer-l-impact-seo-technique-quels-kpis-suivre\">KPIs à suivre\u003C/a> après correction, ne vous limitez pas au CTR. Mesurez le taux de conversion par landing page organique. Un intent gap corrigé devrait améliorer à la fois le CTR (plus de clicks) et le taux de conversion (des clicks mieux qualifiés).\u003C/p>\n\u003Cp>Les intent gaps mesurés via GSC sont la donnée la plus sous-exploitée du SEO technique. Chaque site perd du trafic qualifié non pas parce qu'il manque de positions, mais parce que ses positions existantes sont mal alignées avec la demande réelle. L'extraction systématique via l'API, la classification automatisée, et le monitoring continu transforment ce diagnostic en avantage concurrentiel mesurable. Des outils comme Seogard complètent cette approche en détectant les régressions techniques qui créent de nouveaux intent gaps — un title modifié par erreur, un SSR cassé qui altère le contenu crawlé, un canonical mal configuré qui oriente Google vers la mauvaise page.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,12,[18,19,20,21,22],"intent gaps","Google Search Console","search intent","GSC API","SEO technique","Mesurer les intent gaps avec Google Search Console","Thu Apr 09 2026 23:50:30 GMT+0000 (Coordinated Universal Time)",[26,41,54],{"_id":27,"slug":28,"__v":6,"author":7,"canonical":29,"category":10,"createdAt":30,"date":31,"description":32,"image":15,"imageAlt":15,"readingTime":16,"tags":33,"title":39,"updatedAt":40},"69d85a33aa6b273b0cefb2bb","dell-agentic-ai-is-growing-but-search-still-wins","https://seogard.io/blog/dell-agentic-ai-is-growing-but-search-still-wins","2026-04-10T02:02:27.814Z","2026-04-10","Dell révèle que l'IA agentique génère du trafic mais pas de ventes. Analyse technique : pourquoi optimiser le search reste critique pour l'e-commerce.",[34,35,36,37,38],"dell","agentic ai","search","e-commerce seo","ai traffic","Dell, agentic AI et search : pourquoi le SEO reste roi","Fri Apr 10 2026 02:02:27 GMT+0000 (Coordinated Universal Time)",{"_id":42,"slug":43,"__v":6,"author":7,"canonical":44,"category":10,"createdAt":45,"date":12,"description":46,"image":15,"imageAlt":15,"readingTime":16,"tags":47,"title":52,"updatedAt":53},"69d724dbaa6b273b0cf88f96","how-ai-search-defines-market-relevance-beyond-hreflang","https://seogard.io/blog/how-ai-search-defines-market-relevance-beyond-hreflang","2026-04-09T04:02:35.927Z","Hreflang ne suffit plus. Découvrez les signaux que l'IA utilise pour sélectionner vos pages locales dans les réponses générées par marché.",[36,48,49,50,51],"market relevance","hreflang","AI search","international SEO","AI Search : comment la pertinence locale se joue au-delà de hreflang","Thu Apr 09 2026 04:02:35 GMT+0000 (Coordinated Universal Time)",{"_id":55,"slug":56,"__v":6,"author":7,"canonical":57,"category":10,"createdAt":58,"date":12,"description":59,"image":15,"imageAlt":15,"readingTime":16,"tags":60,"title":66,"updatedAt":67},"69d75d2baa6b273b0c25874d","google-confirms-march-2026-core-update-is-complete-via-sejournal-mattgsouthern","https://seogard.io/blog/google-confirms-march-2026-core-update-is-complete-via-sejournal-mattgsouthern","2026-04-09T08:02:51.680Z","Le core update de mars 2026 est terminé. Méthodologie d'analyse, signaux à surveiller, requêtes GSC et scénarios concrets pour mesurer l'impact réel.",[61,62,63,64,65],"google","core update","march 2026","search console","analyse SEO","March 2026 Core Update : analyse technique post-rollout","Thu Apr 09 2026 08:02:51 GMT+0000 (Coordinated Universal Time)"]