La boîte "Les gens demandent aussi" (PAA) de Google apparaît dans une large proportion des recherches, juste en dessous ou entre les résultats organiques, sous la forme d'une pile de paires question-réponse extensibles. Pour quiconque fait de la recherche de contenu ou de SEO, c'est l'un des signaux les plus directs de l'intention réelle des utilisateurs que vous pouvez lire sur une page de résultats : la formulation exacte que les gens utilisent, les questions de suivi qu'ils posent, et les lacunes que les concurrents n'ont pas encore comblées. Cliquez sur une entrée et Google charge d'autres questions connexes en dessous, si bien qu'une seule requête peut se ramifier en tout un arbre d'intentions.
Ce guide vous montre comment scraper Google "Les gens demandent aussi" avec Python de manière fiable et reproductible. Vous construisez un petit scraper exécutable qui récupère un SERP Google rendu via la Crawling API, analyse les questions et réponses PAA, développe les éléments imbriqués pour capturer les couches plus profondes que la plupart des scrapers manquent, et exporte des paires Q et R propres en JSON. L'ensemble du tutoriel se limite aux données de résultats de recherche publics que tout le monde peut voir sans compte, et la section sur la légalité, vers la fin, n'est pas un texte générique, lisez-la avant de viser un volume réel.
Ce que vous allez construire
Un script Python qui prend une requête de recherche Google publique, récupère le HTML rendu via la Crawling API, et extrait un enregistrement structuré pour chaque élément PAA sur la page, y compris les questions révélées en développant la première couche. Nous utiliserons un exemple de requête comme fil conducteur et extrairons ces champs de chaque entrée PAA :
- Question le texte exact de la question PAA tel que Google le formule, ce qui élargit votre couverture de mots-clés et de sujets.
- Réponse la réponse en extrait que Google affiche lorsque l'élément est développé, utile pour la recherche sur les featured snippets.
- URL source la page que Google cite pour la réponse, ce qui soutient l'analyse concurrentielle.
- Enfants les questions imbriquées révélées lorsque l'élément est développé, capturant des niveaux plus profonds de l'arbre de développement.
Pourquoi une requête simple échoue sur Google
Si vous envoyez une requête HTTP brute à une URL de recherche Google depuis un script, la section PAA n'est généralement pas là. Deux obstacles se dressent contre vous. Premièrement, la boîte PAA est rendue par JavaScript après l'initialisation de la page et se met à jour à chaque fois qu'une question est cliquée, donc le HTML brut qu'une requête simple renvoie omet soit entièrement la boîte, soit ne contient qu'une coquille vide. Deuxièmement, Google surveille le trafic automatisé : les requêtes qui ne ressemblent pas à un vrai navigateur sont mises au défi, reçoivent une page de consentement ou de vérification, ou sont bloquées avant d'atteindre les résultats.
Un scraper PAA fonctionnel a donc besoin de deux choses en une seule requête : une IP que la plateforme lit comme un vrai visiteur, et un navigateur qui rend réellement la page et exécute ses scripts. Vous pouvez assembler cela vous-même avec un navigateur sans interface graphique plus un pool de proxies résidentiels rotatifs, mais les maintenir en bonne santé représente l'essentiel du travail. La Crawling API combine les deux en un seul appel : vous lui envoyez l'URL de recherche, elle la récupère depuis une IP résidentielle de confiance et rend la page dans un vrai navigateur, attend le chargement du contenu dynamique, et vous renvoie le HTML final à analyser.
Le contenu PAA se charge après l'initialisation de la page et change à l'interaction, donc une récupération sans rendu renvoie une boîte incomplète ou vide. Rendez la page, donnez-lui un court délai pour que les scripts s'établissent, et la section PAA est dans le HTML. La Crawling API effectue le rendu et la rotation d'IP côté serveur, et vous commencez avec jusqu'à 20 000 requêtes gratuites, sans carte bancaire.
Prérequis
Vous avez besoin de quelques éléments en place avant d'écrire du code. Aucun ne prend longtemps.
Python de base. Vous devez être à l'aise pour écrire et exécuter un script Python et installer des paquets avec pip. Si BeautifulSoup est nouveau pour vous, notre guide d'utilisation de BeautifulSoup en Python couvre les bases d'analyse que ce tutoriel suppose.
Python 3.8 ou version ultérieure. Confirmez votre version avec python --version. Si vous ne l'avez pas, installez-le depuis python.org ou via une distribution comme Anaconda.
Un compte Crawlbase et un token JavaScript. Inscrivez-vous, ouvrez votre tableau de bord et copiez votre token depuis la page docs du compte. Les SERPs Google nécessitent un rendu, utilisez donc votre token JavaScript (aussi appelé clé activée pour le navigateur) plutôt que le token simple. Vous obtenez jusqu'à 20 000 requêtes gratuites : 1 000 à l'inscription, et davantage à mesure que vous complétez les étapes d'onboarding. Traitez le token comme un mot de passe : il authentifie vos requêtes, gardez-le hors du contrôle de version.
Configurer le projet
Créez un environnement virtuel pour garder les dépendances du projet isolées, puis installez les deux bibliothèques dont le scraper a besoin.
python --version python -m venv paa_env source paa_env/bin/activate pip install requests beautifulsoup4
Sous Windows, activez l'environnement avec paa_env\Scripts\activate à la place de la ligne source. Deux dépendances font le travail : requests envoie l'appel HTTP à la Crawling API, et beautifulsoup4 analyse le HTML renvoyé pour que vous puissiez extraire des champs individuels par sélecteur CSS.
Étape 1 : Récupérer le SERP rendu via la Crawling API
Commencez par obtenir le HTML rendu. Écrivez une petite fonction crawl() qui envoie votre URL de recherche Google à la Crawling API avec votre token JavaScript, lui demande de rendre et d'attendre, vérifie que la page sous-jacente est revenue avec un statut 200, et renvoie le corps HTML. Les paramètres gl et hl dans l'URL définissent le pays et la langue, et page_wait laisse aux scripts PAA le temps de se terminer avant que le HTML soit capturé.
import json import requests from urllib.parse import urlencode JS_TOKEN = "YOUR_CRAWLBASE_TOKEN" # use your JavaScript token API_ENDPOINT = "https://api.crawlbase.com/" def build_serp_url(query, gl="us", hl="en"): base = "https://www.google.com/search?" return base + urlencode({"q": query, "gl": gl, "hl": hl}) def crawl(url, page_wait=2000): params = { "token": JS_TOKEN, "url": url, "page_wait": page_wait, } response = requests.get(API_ENDPOINT, params=params, timeout=90) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] if __name__ == "__main__": url = build_serp_url("how to scrape google", gl="us", hl="en") html = crawl(url) print(html[:500])
L'API renvoie une enveloppe JSON, vous chargez donc la réponse avec json.loads et lisez deux champs : original_status est le statut renvoyé par Google lui-même, et body est le HTML de la page rendue. Vérifier original_status signifie qu'une passerelle de consentement ou un blocage remonte comme exception au lieu d'alimenter le parseur avec des données erronées. Un page_wait d'environ 2000 millisecondes est généralement suffisant pour que la boîte PAA se charge ; un timeout de requête de 90 secondes laisse de la place au rendu pour se terminer sans que l'appel reste suspendu indéfiniment. Lancez le script avec python crawling.py et vous devriez voir du vrai balisage SERP dans les 500 premiers caractères, ce qui confirme que la récupération et le rendu fonctionnent avant d'écrire un seul sélecteur. Si la section PAA semble mince, augmenter page_wait est presque toujours le premier correctif.
Ce page_wait ne remplit sa fonction que parce que la requête a atteint Google comme un vrai navigateur sur une IP de confiance au départ. La Crawling API récupère le SERP depuis une adresse résidentielle rotative, le rend dans un vrai navigateur sans interface graphique, et attend que les scripts PAA s'établissent avant de vous remettre le HTML final, vous évitant de gérer vous-même une flotte sans interface et de sourcer un pool de proxies résidentiels. Pointez-la vers une URL de recherche publique sur le niveau gratuit d'abord.
Étape 2 : Analyser les questions et réponses PAA
Le HTML rendu en main, chargez-le dans BeautifulSoup et extrayez chaque élément PAA. Google ne donne pas à la boîte PAA un seul nom de classe stable, donc l'approche fiable est celle des sélecteurs de repli en cascade : chaque élément PAA porte un attribut data-q contenant la question, et la réponse visible se trouve dans le bloc extensible à côté. Le parseur ci-dessous lit la question depuis data-q, la réponse depuis le texte rendu, et la source citée depuis le premier lien sortant de l'élément.
from bs4 import BeautifulSoup # Layered fallbacks: Google rotates these class names, so try # the stable data-q attribute first, then known container classes. PAA_ITEM_SELECTORS = [ "div[data-q]", "div.related-question-pair", "div[jsname='Cpkphb']", ] def find_paa_items(soup): for selector in PAA_ITEM_SELECTORS: items = soup.select(selector) if items: return items return [] def parse_paa(html): soup = BeautifulSoup(html, "html.parser") questions = [] for item in find_paa_items(soup): question = item.get("data-q") if not question: heading = item.select_one("div[role='heading'], span") question = heading.get_text(strip=True) if heading else None if not question: continue answer_el = item.select_one("div[data-attrid], div.wDYxhc, span.hgKElc") answer = answer_el.get_text(" ", strip=True) if answer_el else None link = item.select_one("a[href^='http']") source_url = link["href"] if link else None questions.append({ "question": question, "answer": answer, "source_url": source_url, "children": [], }) return questions
La forme de l'enregistrement correspond exactement à la structure attendue : question élargit la couverture de mots-clés, answer aide pour le travail sur les featured snippets, source_url soutient l'analyse concurrentielle, et children est réservé aux développements imbriqués que vous capturez à l'étape suivante. Lire la question depuis l'attribut stable data-q en premier, puis revenir au texte du titre, est ce qui maintient le parseur fonctionnel lorsque Google réorganise ses noms de classes. La garde if not question: continue ignore les coquilles vides pour que seuls les vrais éléments PAA atteignent votre sortie.
Google fait tourner les noms de classes obscurcis dans son balisage SERP, donc un sélecteur qui fonctionne aujourd'hui peut ne rien renvoyer le mois prochain. C'est pourquoi le parseur essaie l'attribut stable data-q avant tout nom de classe et garde une liste de replis. Quand tous les champs PAA reviennent vides, réinspectez une page de résultats en direct dans les outils de développement de votre navigateur et mettez à jour la liste. Enregistrez quel sélecteur s'est déclenché à chaque exécution pour qu'une baisse soudaine des correspondances soit facile à repérer.
Étape 3 : Développer les éléments PAA imbriqués
Jusqu'ici vous n'extrayez que l'ensemble initial de questions PAA. C'est déjà utile, mais incomplet : la vraie valeur réside plus profondément dans l'arbre de développement. Quand un utilisateur clique sur une question PAA, Google charge dynamiquement deux à quatre questions connexes supplémentaires en dessous, et chacune peut déclencher d'autres développements. Pour capturer cela, vous demandez à la Crawling API de simuler les clics avant de capturer le HTML, en utilisant le paramètre css_click_selector pour que les questions supplémentaires se chargent dans le DOM que vous analysez ensuite.
def crawl_expanded(url, page_wait=3000): # css_click_selector clicks each PAA question so Google loads # the nested questions before the HTML is captured. params = { "token": JS_TOKEN, "url": url, "page_wait": page_wait, "css_click_selector": "div[data-q]", } response = requests.get(API_ENDPOINT, params=params, timeout=90) response.raise_for_status() data = json.loads(response.text) if data["original_status"] != 200: raise Exception(f"Unable to crawl '{url}'") return data["body"] def scrape_with_expansions(query, gl="us", hl="en"): url = build_serp_url(query, gl, hl) # First pass: the visible PAA questions. base_items = parse_paa(crawl(url)) seen = {item["question"] for item in base_items} # Second pass: click to load the nested questions, then diff. expanded_items = parse_paa(crawl_expanded(url)) for item in expanded_items: if item["question"] not in seen: base_items[0]["children"].append(item) seen.add(item["question"]) return base_items
Le flux est le suivant : construire l'URL SERP avec votre requête et vos paramètres géographiques, récupérer une fois l'ensemble PAA visible, puis récupérer à nouveau avec css_click_selector défini sur le sélecteur d'élément PAA pour que l'API clique sur chaque question et charge les nouvelles dans le DOM. Analyser les deux passes et ne garder que les questions non encore vues vous donne la couche plus profonde sans dupliquer les originaux. En pratique, une seule requête peut passer de trois ou quatre questions visibles à douze à vingt au total après quelques tours d'expansion. Cette étape est optionnelle du point de vue de l'implémentation, mais c'est là que réside la plupart de la valeur manquante.
Étape 4 : Assembler et exporter les paires Q et R
Maintenant câblez la construction, la récupération, l'expansion et l'analyse dans un script exécutable unique qui écrit les données PAA structurées en JSON. Définir ensure_ascii=False garde les caractères non-ASCII lisibles dans le fichier au lieu de les échapper en séquences \u, ce qui compte dès que vous lancez des requêtes dans d'autres langues.
import sys def main(): query = sys.argv[1] if len(sys.argv) > 1 else "how to scrape google" country = sys.argv[2] if len(sys.argv) > 2 else "us" paa = scrape_with_expansions(query, gl=country) outfile = f"paa_{country}.json" with open(outfile, "w", encoding="utf-8") as f: json.dump(paa, f, ensure_ascii=False, indent=2) total = len(paa) + sum(len(q["children"]) for q in paa) print(f"Saved {total} PAA questions to {outfile}") if __name__ == "__main__": main()
Lancez le script complet avec python main.py "content gap analysis" uk. Il construit l'URL SERP Google pour cette requête dans le pays choisi, récupère le HTML rendu, développe les éléments PAA, et écrit les paires question-réponse dans paa_uk.json. La même poignée de fonctions suffit : échangez la requête ou le code pays et le parseur gère ce qui revient. Si les résultats semblent incomplets, augmentez page_wait en premier, car un rendu lent est la cause la plus fréquente d'une liste PAA courte.
À quoi ressemble la sortie
Vous obtenez une liste propre d'objets question, chacun avec sa réponse, la source citée et toutes les questions imbriquées capturées lors du développement, prêts à être écrits en JSON, alimenter un brief de contenu, ou chargés dans une base de données pour le clustering.
[ { "question": "Is it legal to scrape Google?", "answer": "Scraping public search results is generally permitted, but it can conflict with Google's terms of service.", "source_url": "https://example.com/is-scraping-google-legal", "children": [ { "question": "Can Google detect scraping?", "answer": "Yes, Google uses rate limits and behavioral signals to flag automated traffic.", "source_url": "https://example.com/google-bot-detection", "children": [] } ] }, { "question": "What is the best tool to scrape Google?", "answer": "A rendering API that handles proxies and JavaScript is the most reliable approach.", "source_url": "https://example.com/google-scraping-tools", "children": [] } ]
Chaque question devient un nœud, et chaque développement ajoute d'autres nœuds en dessous sous children. À partir de là, l'export en CSV pour un tableur, ou l'aplatissement de l'arbre dans un brief de contenu, n'est qu'à quelques lignes. Comme chaque enregistrement porte son URL source, vous pouvez aussi regrouper les questions par les domaines que Google cite pour voir qui possède déjà les réponses.
Comparer les PAA entre pays
Les résultats PAA ne sont pas universels : ils varient selon le lieu et la langue car Google les personnalise selon le marché du chercheur. Pour comparer, lancez la même requête avec différentes valeurs gl et comparez les résultats.
import time query = "best running shoes" markets = ["us", "uk", "de"] by_market = {} for gl in markets: items = scrape_with_expansions(query, gl=gl) by_market[gl] = {q["question"] for q in items} time.sleep(3) # Questions unique to the UK market. uk_only = by_market["uk"] - by_market["us"] print(f"UK-only PAA questions: {len(uk_only)}")
Comparer les questions uniques, les sujets qui se chevauchent et les différences de réponses entre les marchés est particulièrement utile lorsque vous vous développez dans une nouvelle région ou localisez du contenu. Le time.sleep entre les requêtes espace l'exécution pour que vous n'envoyiez pas des requêtes consécutives. Pour une montée en charge bien au-delà d'une poignée de requêtes, le Crawler asynchrone vous permet d'envoyer des URL en masse et de recevoir les résultats via webhook au lieu d'attendre chaque appel.
Rester non bloqué
Même avec le rendu et une IP de confiance gérés pour vous, Google surveille le trafic à l'allure d'un scraper. Quelques habitudes maintiennent une exécution en bonne santé.
- Espacez vos requêtes. Marteler le SERP dans une boucle serrée est le moyen le plus rapide d'être mis au défi. Espacez les requêtes et variez vos queries au lieu de parcourir un terme à plein régime.
- Misez sur la rotation. Un pool d'IP résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels pour qu'aucune seule ne déclenche une limite. La Crawling API s'en charge pour vous ; si vous gérez votre propre infrastructure, c'est la partie à bien configurer.
- Surveillez les statuts et les comptes. Une exécution qui commence à renvoyer des défis, ou un compte PAA qui tombe à zéro, vous indique que le débit, le niveau d'IP ou les sélecteurs nécessitent une attention. Traitez cela comme un signal, pas du bruit.
- Réinspectez quand les champs sont vides. Google remanie périodiquement son balisage. Si l'analyse PAA cesse de renvoyer des éléments, ouvrez une page en direct dans les outils de développement et mettez à jour la liste des sélecteurs.
Pour le guide complet, consultez comment scraper des sites web sans être bloqué. Si vous voulez la vue d'ensemble de toutes les fonctionnalités SERP au-delà des PAA, notre guide sur le scraping des pages de recherche Google couvre les résultats organiques, les annonces et le knowledge panel, et le tutoriel Python sur les résultats de recherche Google est le guide pratique compagnon de celui-ci. Une fois les données PAA en main, l'extraction et l'analyse des données SEO Google et l'utilisation des données scrapées pour améliorer le SEO montrent ce qu'en faire.
Est-il légal de scraper Google PAA ?
La question de savoir si le scraping des données "Les gens demandent aussi" de Google est autorisé dépend des conditions d'utilisation de Google, de votre juridiction et de ce que vous faites des données. Le scraping des résultats de recherche publiquement visibles se situe dans une zone juridique grise : les questions et réponses dans la boîte PAA sont visibles par tous sans compte, mais les conditions de Google limitent l'accès automatisé, donc un scraper peut aller à l'encontre de ces conditions quelle que soit la prudence de l'outillage. Rien dans ce code ne change cela ; il rend simplement la partie technique fonctionnelle. Lisez les conditions de Google et son robots.txt, et traitez les deux comme la limite de ce que vous collectez.
Quelques lignes à respecter. Ne collectez que les données PAA publiques : les questions, réponses et URL sources citées qui apparaissent sur une page de résultats sans connexion. Maintenez votre volume de requêtes suffisamment bas pour ne pas mettre à rude épreuve les serveurs de Google, et espacez votre crawl plutôt que de le lancer à fond. Ne collectez pas de données personnelles, ne redistribuez pas le texte de réponse protégé par le droit d'auteur en gros, et ne touchez rien derrière une connexion. Les URL sources citées pointent vers les pages d'autres personnes, traitez donc le contenu là-bas sous sa propre licence, pas comme le vôtre à republier.
Lorsqu'une voie officielle existe, préférez-la. Google propose Programmable Search et d'autres API officielles pour un accès sanctionné aux données de recherche, et pour les besoins à l'échelle de production un accord officiel de données est la bonne route, pas un scraper plus astucieux. Ce guide est délibérément limité aux pages PAA publiques car c'est la ligne qui rend le travail défendable : des paires question-réponse que tout le monde peut voir, utilisées pour la recherche et la planification de contenu, rien de plus.
Points clés
- PAA nécessite un rendu. La boîte se charge via JavaScript après l'initialisation de la page, donc une requête simple renvoie une section vide ou manquante ; rendez la page et donnez-lui un court délai.
-
La Crawling API rend derrière une vraie IP. Envoyez-lui l'URL de recherche avec un token JavaScript et
page_wait, elle fait tourner les IP résidentielles, exécute un vrai navigateur et renvoie le HTML final. -
Analysez avec des sélecteurs de repli en cascade. Lisez la question depuis l'attribut stable
data-qen premier, puis revenez aux noms de classes, car Google fait tourner son balisage obscurci. - Développez l'arbre avec css_click_selector. Cliquez les questions visibles pour que Google charge les imbriquées, puis comparez les passes pour passer de trois ou quatre questions à douze à vingt.
- Restez sur les données publiques. Respectez les CGU et le robots.txt de Google, espacez vos requêtes, préférez les API officielles à l'échelle, et ne touchez jamais les connexions ou données personnelles.
Foire aux questions
Qu'est-ce qu'une boîte "Les gens demandent aussi" ?
Une boîte PAA est une fonctionnalité du SERP Google affichant une pile de paires question-réponse extensibles liées à la requête de recherche. Elle apparaît dans une large proportion des recherches et se développe dynamiquement au clic, chargeant deux à quatre questions connexes supplémentaires à chaque fois, ce qui en fait une source si riche de données d'intention utilisateur pour le SEO et la recherche de contenu.
Pourquoi une requête simple manque-t-elle la section PAA ?
La boîte PAA est rendue par JavaScript après le chargement du HTML initial et se met à jour à l'interaction, donc une requête HTTP brute renvoie une coquille vide ou rien du tout. Récupérer via la Crawling API avec un token JavaScript rend la page dans un vrai navigateur et attend les scripts, donc le contenu PAA est présent dans le HTML que vous analysez.
Comment capturer les questions PAA imbriquées ?
Utilisez le paramètre css_click_selector pour que l'API clique sur chaque question PAA visible avant de capturer le HTML, ce qui fait que Google charge les questions connexes dans le DOM. Analysez ce HTML développé et comparez-le avec la première passe pour collecter les nouvelles questions sous le children de chaque élément. Un développement à 3 niveaux produit typiquement douze à vingt questions au total par requête.
Puis-je scraper Google PAA avec Python ?
Oui. Avec requests et BeautifulSoup vous récupérez le SERP rendu et extrayez la question, la réponse, l'URL source et les enfants imbriqués de chaque élément PAA. La Crawling API est le pont qui amène votre requête vers Google depuis une IP de confiance avec rendu activé. Pour une introduction plus large, consultez notre guide sur le scraping de sites web avec Python.
Pourquoi les PAA varient-ils selon le pays ?
Google personnalise les résultats PAA selon le pays et la langue du chercheur, donc la même requête aux États-Unis et au Royaume-Uni renvoie souvent des questions différentes car le comportement des utilisateurs, les patterns linguistiques et le contenu disponible diffèrent selon le marché. Lancez la même requête avec différentes valeurs gl et comparez les ensembles de questions pour voir les différences, ce qui est utile pour localiser du contenu.
Mes sélecteurs ne renvoient rien. Qu'est-ce qui a changé ?
Très probablement le balisage de Google. Google fait tourner les noms de classes obscurcis dans son SERP, donc les sélecteurs qui fonctionnaient le mois dernier peuvent être cassés et le parseur renverra silencieusement une liste vide. Lisez la question depuis l'attribut stable data-q en premier, gardez une liste de sélecteurs de repli, enregistrez lequel se déclenche à chaque exécution, et réinspectez une page en direct dans les outils de développement quand le compte baisse.
Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.
Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

