Scraper Google People Also Ask et Autocomplete pour la recherche de mots-clés (Guide Python)

Apprenez à scraper Google People Also Ask, Autocomplete et Related Searches avec Python et des proxies résidentiels. Code complet, expansion de mots-clés, et stratégies anti-blocage incluses.

Scrape Google People Also Ask and Autocomplete for Keyword Research (Python Guide)

La recherche de mots-clés ne se limite plus aux outils payants. Si vous savez scraper Google People Also Ask et l'endpoint Autocomplete, vous accédez à trois sources gratuites de données d'intention de recherche que la plupart des SEO ignorent. Ce guide vous montre comment construire un pipeline Python complet — du seed keyword à un fichier CSV de centaines de questions exploitable pour votre contenu et vos FAQ.

Pourquoi scraper Google People Also Ask et Autocomplete change la donne

Google expose publiquement trois mines d'or pour la recherche de mots-clés : l'Autocomplete (suggestions de recherche), les People Also Ask (PAA) et les Related Searches (recherches associées). Chacune révèle un niveau différent d'intention de recherche :

  • Autocomplete — reflète l'intention de recherche avant le clic. Les suggestions sont triées par popularité et fraîcheur. Idéal pour découvrir des variantes long-tail.
  • People Also Ask — révèle les questions que les utilisateurs se posent après une première recherche. Chaque accordéon contient une question, un snippet de réponse et une source citée.
  • Related Searches — montre les requêtes adjacentes en bas de page. Utile pour élargir un thème ou identifier des cooccurrences sémantiques.

L'endpoint Autocomplete de Google est particulièrement intéressant car il renvoie du JSON natif via https://suggestqueries.google.com/complete/search?client=chrome&q=VOTRE_MOT. Pas de HTML à parser, pas de JavaScript à exécuter. Selon la documentation MDN sur CORS, cet endpoint accepte les requêtes cross-origin, ce qui le rend encore plus accessible.

Le contexte technique : pourquoi Google limite ces endpoints

L'endpoint Autocomplete de Google est conçu pour répondre en moins de 200ms à chaque frappe de touche dans la barre de recherche. Pour maintenir cette latence, Google applique des limites strictes par adresse IP. Si vous envoyez plus de ~100 requêtes/min depuis une seule IP, vous commencez à recevoir des réponses vides, des CAPTCHA ou des erreurs 429.

Le problème est encore plus subtil avec la locale bias : les suggestions Autocomplete et les questions PAA varient selon la géolocalisation de l'IP qui fait la requête. Une recherche « pizza » depuis une IP à Paris ne renverra pas les mêmes suggestions que depuis une IP à Berlin. Si vous voulez des suggestions localisées pour un marché allemand, vous devez utiliser une IP allemande.

C'est là que les proxies résidentiels entrent en jeu. Contrairement aux proxies datacenter qui sont facilement détectés par les systèmes anti-bot de Google, les proxies résidentiels utilisent de vraies adresses IP d'ISP. Avec ProxyHat, vous pouvez cibler un pays et une ville précis via le nom d'utilisateur :

# Proxy résidentiel allemand, Berlin
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080

# Proxy résidentiel français, Paris
http://user-country-FR-city-paris:pass@gate.proxyhat.com:8080

# Session sticky pour réutiliser la même IP
http://user-country-US-session-abc123:pass@gate.proxyhat.com:8080

Pour en savoir plus sur les localisations disponibles et le tarification, consultez les pages dédiées.

Construire un expandeur de mots-clés : du seed au long-tail

Un seul mot-clé seed peut générer des centaines de variantes long-tail. La technique consiste à ajouter des préfixes alphabétiques (a-z) et des modificateurs sémantiques (comment, pourquoi, meilleur, prix, etc.) devant votre mot-clé, puis à interroger l'endpoint Autocomplete pour chaque combinaison.

Exemple : expansion avec préfixes a-z et modificateurs

import httpx
import string
import json
import time
from urllib.parse import quote

AUTOCOMPLETE_URL = "https://suggestqueries.google.com/complete/search"

PROXY = "http://user-country-FR-city-paris:pass@gate.proxyhat.com:8080"

MODIFIERS = [
    "comment", "pourquoi", "quand", "ou", "quel", "quelle",
    "meilleur", "prix", "gratuit", "avis", "difference", "guide"
]

def fetch_autocomplete(query: str, proxy: str = PROXY) -> list[str]:
    """Récupère les suggestions Autocomplete pour une requête."""
    params = {
        "client": "chrome",
        "q": query,
        "hl": "fr",
        "gl": "fr"
    }
    try:
        with httpx.Client(proxy=proxy, timeout=10.0) as client:
            resp = client.get(AUTOCOMPLETE_URL, params=params)
            resp.raise_for_status()
            data = resp.json()
            # data[1] contient la liste des suggestions
            if isinstance(data, list) and len(data) > 1:
                return data[1]
            return []
    except (httpx.HTTPError, json.JSONDecodeError) as e:
        print(f"Erreur pour '{query}': {e}")
        return []

def expand_seed(seed: str) -> list[str]:
    """Transforme un seed keyword en centaines de variantes."""
    all_suggestions = set()

    # 1. Préfixes alphabétiques : "a pizza", "b pizza", ...
    for letter in string.ascii_lowercase:
        query = f"{letter} {seed}"
        suggestions = fetch_autocomplete(query)
        all_suggestions.update(suggestions)
        time.sleep(0.5)  # throttle poli

    # 2. Modificateurs : "comment pizza", "meilleur pizza", ...
    for mod in MODIFIERS:
        query = f"{mod} {seed}"
        suggestions = fetch_autocomplete(query)
        all_suggestions.update(suggestions)
        time.sleep(0.5)

    # 3. Suffixes alphabétques : "pizza a", "pizza b", ...
    for letter in string.ascii_lowercase:
        query = f"{seed} {letter}"
        suggestions = fetch_autocomplete(query)
        all_suggestions.update(suggestions)
        time.sleep(0.5)

    return sorted(all_suggestions)

if __name__ == "__main__":
    results = expand_seed("proxy")
    print(f"{len(results)} suggestions trouvées")
    for s in results[:20]:
        print(f"  - {s}")

Avec 26 lettres × 2 (préfixe + suffixe) + 12 modificateurs = 64 requêtes, vous récupérez typiquement entre 300 et 600 suggestions uniques pour un seul seed keyword. C'est l'équivalent d'un mois de données Ubersuggest gratuit en 2 minutes.

Scraper les People Also Ask avec Playwright

Les questions PAA ne sont pas disponibles via un endpoint JSON. Elles sont rendues dans le HTML des résultats de recherche et nécessitent d'ouvrir les accordéons pour révéler les questions imbriquées. Chaque clic sur un accordéon PAA charge de nouvelles questions, ce qui permet une expansion récursive.

Voici un script Playwright qui ouvre les accordéons PAA de manière récursive et extrait les questions ainsi que leurs sources citées :

from playwright.sync_api import sync_playwright
import time
import json
import re

PROXY_SERVER = "gate.proxyhat.com:8080"
PROXY_USER = "user-country-FR-city-paris"
PROXY_PASS = "pass"

def scrape_paa(query: str, max_clicks: int = 10) -> list[dict]:
    """Scrape les questions PAA et leurs sources pour une requête."""
    questions = []

    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy={
                "server": f"http://{PROXY_SERVER}",
                "username": PROXY_USER,
                "password": PROXY_PASS
            }
        )
        context = browser.new_context(
            locale="fr-FR",
            viewport={"width": 1280, "height": 800}
        )
        page = context.new_page()

        # Naviguer vers Google Search
        page.goto(f"https://www.google.com/search?q={query}&hl=fr&gl=fr")
        page.wait_for_timeout(2000)

        clicks = 0
        while clicks < max_clicks:
            # Sélectionner les accordéons PAA non encore ouverts
            paa_items = page.query_selector_all("div[jsname='CWE2Te']")
            if not paa_items:
                break

            for item in paa_items:
                try:
                    # Extraire le texte de la question
                    q_text = item.inner_text().strip()
                    if q_text and q_text not in [q["question"] for q in questions]:
                        questions.append({"question": q_text, "source": None})

                    # Cliquer pour ouvrir l'accordéon et révéler la source
                    item.click()
                    page.wait_for_timeout(800)
                    clicks += 1

                    # Extraire la source citée (lien dans le snippet de réponse)
                    source_link = page.query_selector("div[jsname='r5aAsf'] a")
                    if source_link:
                        href = source_link.get_attribute("href")
                        if questions:
                            questions[-1]["source"] = href

                except Exception as e:
                    print(f"Erreur clic: {e}")
                    continue

            time.sleep(1)

        browser.close()

    return questions

if __name__ == "__main__":
    paa_results = scrape_paa("proxy résidentiel", max_clicks=15)
    print(f"{len(paa_results)} questions PAA trouvées")
    for q in paa_results[:10]:
        print(f"  Q: {q['question']}")
        print(f"  Source: {q['source']}")
        print()

Chaque accordéon ouvert révèle généralement 3 à 4 nouvelles questions, ce qui signifie qu'avec 15 clics vous pouvez récupérer 40 à 60 questions uniques pour une seule requête. Les sources citées sont précieuses : elles vous indiquent quels sites Google considère comme faisant autorité sur le sujet.

Scraper les Related Searches

Les Related Searches sont les plus simples à extraire — elles se trouvent en bas de la page de résultats dans une section identifiable. Voici comment les récupérer avec httpx :

import httpx
from selectolax.parser import HTMLParser

PROXY = "http://user-country-FR-city-paris:pass@gate.proxyhat.com:8080"

def scrape_related_searches(query: str) -> list[str]:
    """Récupère les Related Searches depuis une page de résultats Google."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
        "Accept-Language": "fr-FR,fr;q=0.9"
    }
    url = f"https://www.google.com/search?q={query}&hl=fr&gl=fr"

    try:
        with httpx.Client(proxy=PROXY, headers=headers, timeout=15.0) as client:
            resp = client.get(url)
            resp.raise_for_status()

        tree = HTMLParser(resp.text)
        related = []

        # Les Related Searches sont dans des liens avec /search?q= en bas de page
        for node in tree.css("a"):
            href = node.attributes.get("href", "")
            if "/search?q=" in href and "spell=" not in href:
                text = node.text(strip=True)
                if text and len(text) > 3 and text not in related:
                    related.append(text)

        return related[:20]  # Google affiche généralement ~8-12

    except httpx.HTTPError as e:
        print(f"Erreur: {e}")
        return []

if __name__ == "__main__":
    related = scrape_related_searches("proxy résidentiel")
    print(f"{len(related)} Related Searches:")
    for r in related:
        print(f"  - {r}")

Pipeline complet : asynchrone avec rotation de proxies

Pour traiter un grand nombre de seed keywords en parallèle sans déclencher de blocages, il faut combiner async/await avec une rotation de sessions proxy. Voici un pipeline production-ready avec httpx async et gestion d'erreurs :

import asyncio
import httpx
import json
import csv
import random
import string
from dataclasses import dataclass, asdict

AUTOCOMPLETE_URL = "https://suggestqueries.google.com/complete/search"
GATEWAY = "gate.proxyhat.com:8080"
BASE_USER = "user-country-{country}-session-{session}"
PASSWORD = "pass"

@dataclass
class KeywordResult:
    seed: str
    suggestion: str
    source: str  # "autocomplete" | "paa" | "related"
    country: str

def build_proxy_url(country: str, session_id: str) -> str:
    """Construit une URL proxy avec géo-targeting et session sticky."""
    username = BASE_USER.format(country=country, session=session_id)
    return f"http://{username}:{PASSWORD}@{GATEWAY}"

async def fetch_suggestions_async(
    client: httpx.AsyncClient,
    query: str,
    country: str,
    session_id: str,
    max_retries: int = 3
) -> list[str]:
    """Récupère les suggestions Autocomplete avec retry et circuit breaker."""
    proxy_url = build_proxy_url(country, session_id)
    params = {
        "client": "chrome",
        "q": query,
        "hl": country.lower(),
        "gl": country.lower()
    }

    for attempt in range(max_retries):
        try:
            # Rotation de proxy à chaque retry
            proxy = build_proxy_url(country, f"{session_id}-r{attempt}")
            async with httpx.AsyncClient(proxy=proxy, timeout=10.0) as c:
                resp = await c.get(AUTOCOMPLETE_URL, params=params)
                if resp.status_code == 429:
                    await asyncio.sleep(2 ** attempt)  # backoff exponentiel
                    continue
                resp.raise_for_status()
                data = resp.json()
                if isinstance(data, list) and len(data) > 1:
                    return data[1]
                return []
        except (httpx.HTTPError, json.JSONDecodeError) as e:
            if attempt == max_retries - 1:
                print(f"Échec définitif pour '{query}': {e}")
                return []
            await asyncio.sleep(1.5 ** attempt)

    return []

async def expand_keyword_pipeline(
    seeds: list[str],
    countries: list[str],
    output_csv: str = "keywords.csv"
) -> None:
    """Pipeline asynchrone complet : seed → suggestions → CSV."""
    all_results: list[KeywordResult] = []
    seen = set()  # pour déduplication

    async with httpx.AsyncClient(timeout=15.0) as client:
        tasks = []

        for seed in seeds:
            for country in countries:
                # Préfixes a-z
                for letter in string.ascii_lowercase:
                    query = f"{letter} {seed}"
                    session_id = f"{seed}-{country}-{letter}-pre"
                    tasks.append((query, country, session_id, seed))

                # Modificateurs courants
                for mod in ["comment", "meilleur", "prix", "avis", "gratuit"]:
                    query = f"{mod} {seed}"
                    session_id = f"{seed}-{country}-{mod}"
                    tasks.append((query, country, session_id, seed))

        # Semaphore pour limiter la concurrence (évite de saturer)
        semaphore = asyncio.Semaphore(20)

        async def bounded_fetch(query, country, session_id, seed):
            async with semaphore:
                suggestions = await fetch_suggestions_async(
                    client, query, country, session_id
                )
                for s in suggestions:
                    key = (s, country)
                    if key not in seen:
                        seen.add(key)
                        all_results.append(KeywordResult(
                            seed=seed,
                            suggestion=s,
                            source="autocomplete",
                            country=country
                        ))
                await asyncio.sleep(0.3)  # throttle poli

        await asyncio.gather(*[
            bounded_fetch(q, c, s, seed)
            for q, c, s, seed in tasks
        ])

    # Export CSV
    with open(output_csv, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=["seed", "suggestion", "source", "country"])
        writer.writeheader()
        for r in all_results:
            writer.writerow(asdict(r))

    print(f"{len(all_results)} mots-clés uniques exportés vers {output_csv}")

if __name__ == "__main__":
    seeds = ["proxy", "vpn", "scraping"]
    countries = ["FR", "DE", "US"]
    asyncio.run(expand_keyword_pipeline(seeds, countries))

Avec un semaphore à 20 requêtes concurrentes et un throttle de 300ms, ce pipeline traite environ 60 requêtes/seconde tout en restant sous les seuils de détection de Google. Pour 3 seeds × 3 pays × 31 combinaisons = 279 requêtes, le pipeline complet s'exécute en moins de 5 secondes.

Déduplication et clustering par intention de recherche

Une fois vos suggestions collectées, vous aurez des doublons et des variantes proches. Le clustering par intention vous aide à regrouper les questions similaires pour planifier votre contenu :

import re
import csv
from collections import defaultdict

def normalize_keyword(kw: str) -> str:
    """Normalise un mot-clé pour la déduplication."""
    kw = kw.lower().strip()
    kw = re.sub(r"[^a-z0-9\sàâäéèêëïîôöùûüç-]", "", kw)
    kw = re.sub(r"\s+", " ", kw)
    return kw

def cluster_by_intent(keywords: list[str]) -> dict[str, list[str]]:
    """Regroupe les mots-clés par intention de recherche détectée."""
    clusters = defaultdict(list)

    intent_patterns = {
        "informationnel": [r"^comment", r"^pourquoi", r"^quand", r"^ou ", r"^quel", r"^quelle", r"^c'est quoi"],
        "transactionnel": [r"prix", r"acheter", r"gratuit", r"tarif", r"abonnement", r"devis"],
        "comparatif": [r"vs", r"ou ", r"meilleur", r"alternative", r"difference"],
        "navigationnel": [r"login", r"site officiel", r"connexion", r"avis"],
    }

    for kw in keywords:
        norm = normalize_keyword(kw)
        matched = False
        for intent, patterns in intent_patterns.items():
            for p in patterns:
                if re.search(p, norm):
                    clusters[intent].append(kw)
                    matched = True
                    break
            if matched:
                break
        if not matched:
            clusters["autre"].append(kw)

    return dict(clusters)

if __name__ == "__main__":
    # Charger les mots-clés depuis le CSV
    keywords = []
    with open("keywords.csv", "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            keywords.append(row["suggestion"])

    # Dédupliquer
    unique = list(set(normalize_keyword(k) for k in keywords))
    print(f"{len(keywords)} mots-clés → {len(unique)} uniques après normalisation")

    # Clustering
    clusters = cluster_by_intent(unique)
    for intent, kws in clusters.items():
        print(f"\n{intent} ({len(kws)} mots-clés):")
        for k in kws[:5]:
            print(f"  - {k}")

    # Export par cluster
    with open("keywords_clustered.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["intent", "keyword"])
        for intent, kws in clusters.items():
            for kw in kws:
                writer.writerow([intent, kw])

Erreurs courantes et cas limites

1. Réponses vides sans erreur HTTP

Google peut renvoyer un statut 200 avec un JSON vide ["votre requête", []] lorsqu'il détecte un comportement automatisé. Ce n'est pas une erreur — c'est un blocage silencieux. Solution : surveillez le ratio de réponses vides et changez d'IP si ce ratio dépasse 20%.

2. Locale bias non maîtrisée

Si vous utilisez une IP américaine avec gl=fr, Google peut ignorer le paramètre et renvoyer des suggestions US. La géolocalisation IP prime sur le paramètre URL. Utilisez toujours un proxy du pays cible.

3. Accordéons PAA qui ne se chargent pas

Parfois, la section PAA n'apparaît pas du tout pour certaines requêtes très commerciales. Vérifiez la présence de la section avant de tenter des clics. Ajoutez un timeout et un fallback gracieux.

4. Rate limiting sur l'endpoint Autocomplete

L'endpoint suggestqueries.google.com a un rate limit plus strict que google.com/search. Si vous scrapez les deux en parallèle, séparez vos pools de proxies.

Tableau comparatif : Autocomplete vs PAA vs Related Searches

CritèreAutocompletePeople Also AskRelated Searches
Format de réponseJSON natifHTML (Playwright requis)HTML (parsing simple)
Latence typique~200ms~2-5s (rendu JS)~1-2s
Volume par requête10 suggestions3-4 questions/clic8-12 suggestions
Expansion récursiveNon (statique)Oui (accordéons imbriqués)Non
Source citéeNonOui (URL de la réponse)Non
Rate limit (1 IP)~100 req/min~30 req/min~50 req/min
Besoin de proxyOui (locale bias)Oui (anti-bot)Recommandé

Éthique et bonnes pratiques

Les données Autocomplete et PAA sont publiques — elles sont affichées à tout utilisateur de Google. Cependant, le scraping à grande échelle doit respecter certaines règles :

  • Throttlez poliment : 0.3-0.5s entre les requêtes minimum. N'envoyez jamais de bursts de centaines de requêtes sans délai.
  • Respectez robots.txt : vérifiez https://www.google.com/robots.txt pour les règles actuelles. L'endpoint Autocomplete n'est pas explicitement interdit, mais cela peut changer.
  • Préférez les APIs officielles à grande échelle : pour des volumes importants, l'API Custom Search de Google ou l'API Search Console sont plus fiables et conformes.
  • Conformité RGPD : les suggestions Autocomplete ne contiennent pas de données personnelles, mais si vous croisez avec d'autres données, assurez-vous de respecter le RGPD.

Pour aller plus loin sur les bonnes pratiques de scraping, consultez notre guide sur le web scraping avec proxies et le SERP tracking. La documentation ProxyHat détaille toutes les options de configuration disponibles.

Points clés à retenir

1. Trois sources complémentaires. Autocomplete pour le volume, PAA pour les questions et les sources, Related Searches pour l'élargissement thématique. Utilisez les trois en combinaison.

2. L'expansion a-z + modificateurs transforme 1 seed en 300-600 variantes. L'expansion récursive PAA ajoute 40-60 questions par seed.

3. Les proxies résidentiels sont indispensables pour le géo-targeting des suggestions et l'évitement des blocages. Utilisez gate.proxyhat.com:8080 avec country-XX-city-yyy dans le nom d'utilisateur.

4. Le clustering par intention transforme une liste brute en plan de contenu actionnable : pages informationnelles, comparatifs, pages transactionnelles.

5. Throttlez et préférez les APIs officielles pour les volumes importants. Le scraping est un complément, pas un remplacement.

FAQ

Qu'est-ce que le scraping Google People Also Ask et Autocomplete pour la recherche de mots-clés ?

C'est la collecte automatisée des suggestions de recherche Google (Autocomplete), des questions People Also Ask et des Related Searches pour découvrir des mots-clés long-tail et des questions utilisateur. Ces trois sources sont gratuites et publiques, accessibles respectivement via un endpoint JSON pour l'Autocomplete et via le rendu HTML pour les PAA et Related Searches.

Pourquoi le scraping PAA et Autocomplete nécessite-t-il des proxies ?

Google applique des limites de débit par IP (~100 req/min pour Autocomplete, ~30 req/min pour les PAA) et localise les suggestions selon l'IP de l'appelant. Sans proxy résidentiel géo-ciblé, vous obtenez soit des blocages (429, CAPTCHA), soit des suggestions non pertinentes pour votre marché cible. Les proxies résidentiels avec géo-targeting pays/ville résolvent ces deux problèmes.

Quel type de proxy fonctionne le mieux pour scraper Google PAA et Autocomplete ?

Les proxies résidentiels sont le meilleur choix car ils utilisent de vraies adresses IP d'ISP, non détectées par les systèmes anti-bot de Google. Les proxies datacenter sont rapidement identifiés et bloqués. Pour l'Autocomplete, des sessions rotatives suffisent ; pour les PAA avec Playwright, des sessions sticky (même IP pendant toute la session) sont préférables pour éviter de réinitialiser le contexte du navigateur.

Comment éviter les blocages en scrapant Google People Also Ask et Autocomplete ?

Combinez quatre stratégies : (1) utilisez des proxies résidentiels avec rotation de sessions, (2) throttlez à 0.3-0.5s entre les requêtes, (3) implémentez un backoff exponentiel sur les erreurs 429, et (4) surveillez le ratio de réponses vides — un ratio supérieur à 20% indique un blocage silencieux qui nécessite un changement d'IP. Limitez la concurrence à 20 requêtes parallèles maximum.

Peut-on utiliser l'endpoint Autocomplete de Google sans proxy ?

Oui pour de petits volumes (moins de 100 requêtes), mais les suggestions seront biaisées par votre géolocalisation IP. Pour obtenir des suggestions localisées pour un marché spécifique (ex : Allemagne, Japon), un proxy résidentiel du pays cible est nécessaire car Google priorise la géolocalisation IP sur les paramètres URL gl et hl.

Prêt à commencer ?

Accédez à plus de 50M d'IPs résidentielles dans plus de 148 pays avec filtrage IA.

Voir les tarifsProxies résidentiels
← Retour au Blog