Scraping People Also Ask e Autocomplete di Google per Ricerca Keyword (Guida Python)

Guida pratica per sviluppatori e SEO: come estrarre suggerimenti Autocomplete, domande People Also Ask e Related Searches con Python, proxy residenziali ed esempi di codice pronti per la produzione.

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

Cos'è lo scraping di People Also Ask e Autocomplete di Google per la ricerca keyword

Se fai keyword research, sai che i dati più preziosi spesso non si trovano nei tool costosi ma direttamente nelle SERP di Google. Lo scraping di People Also Ask e Autocomplete di Google per ricerca keyword (guida Python) è la pratica di estrarre i suggerimenti di ricerca di Google — Autocomplete, domande PAA e Related Searches — per costruire liste di long-tail keyword, cluster di intent e contenuti FAQ a costo zero. In questa guida mostreremo come farlo con Python, proxy residenziali e codice production-ready.

Google offre tre fonti gratuite di dati di intent che i tool commerciali spesso riempacchettano: l'endpoint Autocomplete (suggestqueries.google.com), i blocchi People Also Ask nelle SERP organiche e i Related Searches a fondo pagina. Combinandole, un singolo seed come "scarpe da running" può generare centinaia di long-tail in pochi minuti — ma richiede gestione degli IP, rotazione intelligente e rispetto dei limiti di frequenza.

Ogni fonte mappa a un diverso livello di intent di ricerca:

FonteEndpoint / posizioneTipo di intentVolume tipico per query
Autocomplete (Suggest)suggestqueries.google.com/complete/search?client=chrome&q=Long-tail esplorativo, discovery10 suggerimenti per richiesta
People Also AskBlocco accordion nelle SERP organicheDomande informative, intent interrogativo4–8 domande iniziali, espandibili ricorsivamente
Related SearchesFondo pagina risultatiIntent adiacenti, varianti semantiche8–10 termini per pagina

L'endpoint Autocomplete di Chrome restituisce JSON puro con suggerimenti in tempo reale basati su popolarità reale. Le domande PAA, invece, vivono nel DOM della SERP come accordion cliccabili che rivelano ulteriori domande annidate — ogni click genera nuove domande figlio. I Related Searches, infine, offrono varianti semantiche che Google stesso associa alla query originaria.

Perché queste fonti battono i tool commerciali (a volte)

I dati di Autocomplete riflettono volumi reali di ricerca corrente, non stime campionate. Una query che spunta in Autocomplete ha effettivamente traffico. I tool come Ahrefs o SEMrush campionano i loro indici; Autocomplete è aggiornato quotidianamente da Google stesso. Lo svantaggio? Non hai volumi numerici esatti — ma per il discovery di long-tail, la presenza/assenza in Autocomplete è già un segnale forte.

Costruire un espansore da seed a long-tail

La tecnica classica per moltiplicare un seed consiste nel combinare prefissi alfabetici e modificatori. Per ogni seed, generi varianti anteponendo ogni lettera a–z, numeri 0–9 e modificatori comuni come "come", "perché", "migliore", "vs", "economico". Questo trasforma un singolo termine in centinaia di query da passare ad Autocomplete.

Espansione con prefissi a–z e modificatori

Il pattern è semplice: per ogni combinazione seed + prefisso, chiami l'endpoint Autocomplete e raccogli i suggerimenti. Con 26 lettere + 10 cifre + 10 modificatori = 46 richieste per seed, ottieni fino a 460 suggerimenti grezzi (con duplicati).

import string
import itertools

def generate_prefixed_seeds(seed: str) -> list[str]:
    """Genera tutte le varianti con prefissi a-z, 0-9 e modificatori."""
    prefixes = list(string.ascii_lowercase) + list(string.digits)
    modifiers = [
        "come", "perché", "quando", "dove", "quanto",
        "migliore", "migliori", "vs", "economico", "gratis",
        "recensione", "alternative", "differenza", "guida", "tutorial"
    ]
    all_prefixes = prefixes + [f"{m} " for m in modifiers]
    return [f"{p}{seed}" for p in all_prefixes]

# Esempio
seed = "scarpe da running"
variants = generate_prefixed_seeds(seed)
print(f"Generati {len(variants)} seed da '{seed}'")
# Output: Generati 51 seed da 'scarpe da running'

Espansione ricorsiva delle domande PAA

Le domande People Also Ask sono accordion: cliccandone una, Google carica dinamicamente nuove domande figlio. Questo crea una struttura ad albero che puoi attraversare ricorsivamente. Partendo da 4 domande iniziali, ogni click ne rivela altre 3–4, portando rapidamente a decine di domande con un solo seed.

from dataclasses import dataclass

@dataclass
class PAANode:
    question: str
    answer_snippet: str = ""
    source_url: str = ""
    children: list['PAANode'] = None

    def __post_init__(self):
        if self.children is None:
            self.children = []

def expand_paa_tree(root_questions: list[str], max_depth: int = 3, max_children: int = 4) -> list[PAANode]:
    """Espande ricorsivamente l'albero PAA fino a max_depth."""
    tree = [PAANode(question=q) for q in root_questions]
    
    def _expand(nodes: list[PAANode], depth: int):
        if depth >= max_depth:
            return
        for node in nodes[:max_children]:
            # Qui inserirai la logica di click con Playwright
            # che genera nuove domande figlio
            child_questions = fetch_child_questions(node.question)
            node.children = [PAANode(question=cq) for cq in child_questions]
            _expand(node.children, depth + 1)
    
    _expand(tree, 0)
    return tree

Perché le chiamate rapide ad Autocomplete e PAA innescano rate limit e bias locale

Google applica rate limit per IP su suggestqueries.google.com e sulle SERP. Se invii decine di richieste al secondo dallo stesso IP, riceverai errori 429 (Too Many Requests) o risposte vuote. Inoltre, Autocomplete è fortemente localizzato: una query da un IP tedesco restituisce suggerimenti diversi rispetto allo stesso IP statunitense, perché Google inferisce la posizione dell'utente dall'IP per personalizzare i suggerimenti.

Questo significa che per ottenere suggerimenti localizzati e puliti hai bisogno di proxy residenziali con geo-targeting paese/città. Un datacenter IP viene spesso flaggato come traffico automatizzato, mentre un IP residenziale appare come un utente reale. Secondo la RFC 7231, il codice 429 indica proprio questo scenario di limitazione.

Con ProxyHat puoi specificare paese e città nel username del proxy:

# Proxy residenziale con geo-targeting Germania/Berlino
PROXY_HTTP = "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"

# Proxy residenziale con geo-targeting Italia/Milano
PROXY_HTTP_IT = "http://user-country-IT-city-milan:pass@gate.proxyhat.com:8080"

# Proxy SOCKS5 con sessione sticky (stesso IP per tutte le richieste)
PROXY_SOCKS5 = "socks5://user-country-US-session-abc123:pass@gate.proxyhat.com:1080"

La geo-targeting è cruciale: se fai keyword research per il mercato italiano, devi usare IP italiani per ottenere suggerimenti rilevanti. Un IP statunitense restituirebbe suggerimenti anglofoni o con intent diverso.

Esempio Python: Autocomplete con httpx e proxy ProxyHat

Vediamo un esempio completo che usa httpx con proxy residenziali ProxyHat per estrarre suggerimenti Autocomplete in JSON, con gestione degli errori, retry e logging.

import httpx
import asyncio
import logging
import json
from typing import Optional

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

class AutocompleteScraper:
    BASE_URL = "https://suggestqueries.google.com/complete/search"
    
    def __init__(self, proxy_url: str, timeout: float = 15.0, max_retries: int = 3):
        self.proxy_url = proxy_url
        self.timeout = timeout
        self.max_retries = max_retries
    
    async def fetch_suggestions(self, query: str, gl: str = "it", hl: str = "it") -> list[str]:
        """Recupera suggerimenti Autocomplete per una singola query."""
        params = {
            "client": "chrome",
            "q": query,
            "gl": gl,  # geo-location (paese)
            "hl": hl,  # language
        }
        
        async with httpx.AsyncClient(
            proxy=self.proxy_url,
            timeout=self.timeout,
            headers={
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
                "Accept-Language": f"{hl},{gl};q=0.9",
            },
        ) as client:
            for attempt in range(1, self.max_retries + 1):
                try:
                    resp = await client.get(self.BASE_URL, params=params)
                    if resp.status_code == 429:
                        logger.warning(f"Rate limit su '{query}', attesa {attempt * 2}s")
                        await asyncio.sleep(attempt * 2)
                        continue
                    resp.raise_for_status()
                    data = resp.json()
                    # Formato Chrome: [query, [suggestions], ...]
                    suggestions = data[1] if len(data) > 1 else []
                    logger.info(f"'{query}' → {len(suggestions)} suggerimenti")
                    return suggestions
                except (httpx.HTTPError, json.JSONDecodeError) as e:
                    logger.error(f"Errore su '{query}' (tentativo {attempt}): {e}")
                    if attempt < self.max_retries:
                        await asyncio.sleep(attempt * 2)
        return []

    async def expand_seed(self, seed: str, gl: str = "it", hl: str = "it") -> list[str]:
        """Espande un seed con prefissi a-z e modificatori."""
        import string
        prefixes = list(string.ascii_lowercase) + list(string.digits)
        modifiers = ["come", "perché", "migliore", "vs", "economico", "guida", "recensione"]
        all_queries = [f"{p}{seed}" for p in prefixes] + [f"{m} {seed}" for m in modifiers]
        
        tasks = [self.fetch_suggestions(q, gl, hl) for q in all_queries]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        all_suggestions = set()
        for r in results:
            if isinstance(r, list):
                all_suggestions.update(r)
        return sorted(all_suggestions)

# Utilizzo
PROXY = "http://user-country-IT-city-milan:pass@gate.proxyhat.com:8080"

async def main():
    scraper = AutocompleteScraper(proxy_url=PROXY)
    suggestions = await scraper.expand_seed("scarpe da running", gl="it", hl="it")
    print(f"Totale suggerimenti unici: {len(suggestions)}")
    for s in suggestions[:20]:
        print(f"  - {s}")

asyncio.run(main())

Estrarre domande PAA con Playwright e proxy residenziali

Per le domande People Also Ask serve un browser headless perché gli accordion sono interattivi. Playwright è la scelta ideale: supporta proxy nativamente e può cliccare gli accordion per rivelare domande annidate.

from playwright.async_api import async_playwright
import asyncio
import re
import logging

logger = logging.getLogger(__name__)

class PAAScraper:
    GOOGLE_URL = "https://www.google.com/search?q={query}&hl={hl}&gl={gl}"
    
    def __init__(self, proxy_server: str, proxy_username: str, proxy_password: str):
        self.proxy_server = proxy_server
        self.proxy_username = proxy_username
        self.proxy_password = proxy_password
    
    async def scrape_paa(self, query: str, hl: str = "it", gl: str = "it", max_clicks: int = 10) -> list[dict]:
        """Estrae domande PAA e fonti citate per una query."""
        questions_data = []
        url = self.GOOGLE_URL.format(query=query, hl=hl, gl=gl)
        
        async with async_playwright() as p:
            browser = await p.chromium.launch(
                headless=True,
                proxy={
                    "server": self.proxy_server,
                    "username": self.proxy_username,
                    "password": self.proxy_password,
                },
            )
            context = await browser.new_context(
                locale=f"{hl}-{gl.upper()}",
                viewport={"width": 1920, "height": 1080},
            )
            page = await context.new_page()
            
            try:
                await page.goto(url, wait_until="networkidle", timeout=30000)
                
                # Seleziona il blocco PAA
                paa_block = page.locator("[jsname='Cpkphd']").first
                if not await paa_block.count():
                    logger.info(f"Nessun blocco PAA per '{query}'")
                    return []
                
                for click_idx in range(max_clicks):
                    # Trova tutte le domande visibili nel blocco PAA
                    question_elements = page.locator("div[jsname='Cpkphd'] .related-question-pair")
                    count = await question_elements.count()
                    
                    for i in range(count):
                        try:
                            q_text = await question_elements.nth(i).inner_text()
                            q_text = q_text.strip()
                            if q_text and q_text not in [q["question"] for q in questions_data]:
                                # Cerca la fonte citata nella risposta
                                source_el = question_elements.nth(i).locator("a[data-ved]").first
                                source_url = await source_el.get_attribute("href") if await source_el.count() else ""
                                source_text = await source_el.inner_text() if await source_el.count() else ""
                                
                                questions_data.append({
                                    "question": q_text,
                                    "source_url": source_url or "",
                                    "source_name": source_text or "",
                                })
                        except Exception as e:
                            logger.debug(f"Errore estrazione domanda {i}: {e}")
                    
                    # Click sull'ultimo accordion per generare nuove domande
                    if click_idx < max_clicks - 1:
                        last_question = question_elements.nth(count - 1)
                        await last_question.click()
                        await page.wait_for_timeout(1500)
                
            except Exception as e:
                logger.error(f"Errore durante scraping PAA: {e}")
            finally:
                await browser.close()
        
        return questions_data

# Utilizzo
async def run_paa():
    scraper = PAAScraper(
        proxy_server="http://gate.proxyhat.com:8080",
        proxy_username="user-country-IT-city-milan",
        proxy_password="pass",
    )
    results = await scraper.scrape_paa("scarpe da running", hl="it", gl="it", max_clicks=8)
    print(f"Estratte {len(results)} domande PAA")
    for r in results[:10]:
        print(f"  Q: {r['question']}")
        print(f"  Fonte: {r['source_name']} ({r['source_url'][:60]})")

asyncio.run(run_paa())

Dedup, clustering per intent ed export CSV

Dopo aver raccolto centinaia di suggerimenti e domande, il passo successivo è deduplicare, clusterizzare per intent e preparare i dati per la pianificazione dei contenuti e delle FAQ.

import csv
import re
from collections import defaultdict
from typing import Optional

def normalize_keyword(kw: str) -> str:
    """Normalizza keyword: lowercase, rimuove spazi multipli e punteggiatura."""
    kw = kw.lower().strip()
    kw = re.sub(r'\s+', ' ', kw)
    kw = re.sub(r'[^\w\sàèéìòù-]', '', kw)
    return kw

def classify_intent(kw: str) -> str:
    """Classifica l'intent di ricerca basato su pattern linguistici."""
    kw_lower = kw.lower()
    informational_patterns = [r'^come\b', r'^perché\b', r'^quando\b', r'^dove\b', r'^cosa\b', r'\?$', r'^guida\b', r'^tutorial\b']
    transactional_patterns = [r'compra', r'acquista', r'prezzo', r'offerta', r'sconto', r'economico', r'gratis']
    navigational_patterns = [r'login', r'sito ufficiale', r'recensione', r'opinioni']
    
    for pattern in informational_patterns:
        if re.search(pattern, kw_lower):
            return "informational"
    for pattern in transactional_patterns:
        if re.search(pattern, kw_lower):
            return "transactional"
    for pattern in navigational_patterns:
        if re.search(pattern, kw_lower):
            return "navigational"
    return "commercial"

def cluster_keywords(keywords: list[str]) -> dict[str, list[str]]:
    """Clusterizza keyword per intent e per root term."""
    clusters = defaultdict(list)
    seen = set()
    
    for kw in keywords:
        normalized = normalize_keyword(kw)
        if not normalized or normalized in seen:
            continue
        seen.add(normalized)
        
        intent = classify_intent(normalized)
        clusters[intent].append(normalized)
    
    return dict(clusters)

def export_to_csv(keywords: list[str], paa_questions: list[dict], output_path: str = "keyword_research.csv"):
    """Esporta keyword e domande PAA in CSV per content planning."""
    clusters = cluster_keywords(keywords)
    
    with open(output_path, "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["tipo", "keyword", "intent", "source"])
        
        for intent, kws in clusters.items():
            for kw in kws:
                writer.writerow(["autocomplete", kw, intent, "google_suggest"])
        
        for paa in paa_questions:
            kw = normalize_keyword(paa["question"])
            intent = classify_intent(kw)
            writer.writerow(["paa", kw, intent, paa.get("source_url", "")])
    
    logger.info(f"Esportate {len(keywords)} keyword + {len(paa_questions)} domande PAA in {output_path}")
    return output_path

# Esempio di utilizzo completo
async def full_pipeline():
    # 1. Autocomplete scraping
    ac_scraper = AutocompleteScraper(proxy_url=PROXY)
    autocomplete_kws = await ac_scraper.expand_seed("scarpe da running", gl="it", hl="it")
    
    # 2. PAA scraping
    paa_scraper = PAAScraper(
        proxy_server="http://gate.proxyhat.com:8080",
        proxy_username="user-country-IT-city-milan",
        proxy_password="pass",
    )
    paa_results = await paa_scraper.scrape_paa("scarpe da running", hl="it", gl="it")
    
    # 3. Export CSV
    csv_path = export_to_csv(autocomplete_kws, paa_results, "scarpe_running_research.csv")
    print(f"CSV salvato: {csv_path}")
    
    # Statistiche
    clusters = cluster_keywords(autocomplete_kws)
    for intent, kws in clusters.items():
        print(f"  {intent}: {len(kws)} keyword")

asyncio.run(full_pipeline())

ProxyHat: configurazione e best practice

Per scalare lo scraping di Autocomplete e PAA, la scelta del proxy è determinante. I proxy residenziali di ProxyHat offrono IP reali di provider ISP, con geo-targeting per paese e città. Ecco la configurazione raccomandata:

ParametroValoreUso
Gateway HTTPgate.proxyhat.com:8080Autocomplete, richieste API-like
Gateway SOCKS5gate.proxyhat.com:1080Playwright, browser headless
Geo-targeting paeseuser-country-ITSuggerimenti localizzati Italia
Geo-targeting cittàuser-country-IT-city-milanSuggerimenti Milano-specifici
Sessione stickyuser-session-abc123Stesso IP per sessione Playwright

Per il browser Playwright, usa una sessione sticky per mantenere lo stesso IP durante l'intera sessione (necessario per evitare che Google rilevi cambio-IP mid-session). Per Autocomplete, usa rotazione per-request per distribuire il carico.

Puoi esplorare tutte le locazioni disponibili e consultare il documentazione ufficiale ProxyHat per dettagli su autenticazione e parametri avanzati.

Best practice per la produzione

  • Throttling: mantieni 2–5 richieste concurrent per IP, con delay di 1–3 secondi tra batch.
  • Retry con backoff esponenziale: al 429, aspetta 2^n secondi dove n è il tentativo.
  • Circuit breaker: dopo 5 errori consecutivi su un IP, cambia sessione proxy.
  • Logging strutturato: registra query, timestamp, status code, proxy IP per debugging.
  • Cache locale: salva i risultati in JSON/SQLite per evitare re-scraping delle stesse query.

Errori comuni e casi limite

1. Locale bias non gestito

Se non specifichi gl e hl nei parametri della richiesta, Google inferisce la locale dall'IP del proxy. Questo può portare a suggerimenti in lingua sbagliata. Usa sempre gl=it e hl=it per l'Italia, combinati con un IP italiano.

2. CAPTCHA nelle SERP

Con proxy datacenter o troppe richieste, Google può servire una pagina CAPTCHA invece della SERP. I proxy residenziali riducono drasticamente questo rischio, ma non lo eliminano. Implementa un rilevamento: se la risposta HTML contiene "captcha o "unusual traffic", fermati e cambia IP.

3. Struttura DOM di Google che cambia

I selettori CSS di Google cambiano frequentemente. Non hardcodare jsname='Cpkphd' senza fallback. Usa selettori multipli e test regolari. Un approccio robusto combina selettori CSS con match testuali sul contenuto.

4. Dedup incompleto

Suggerimenti Autocomplete possono apparire identici ma con spaziatura o capitalizzazione diversa. Normalizza sempre prima di deduplicare. Usa normalize_keyword() come mostrato sopra.

Considerazioni etiche

I dati di Autocomplete e PAA sono pubblicamente accessibili a qualsiasi utente che digiti su Google. Tuttavia, lo scraping su larga scala impone un carico sui server di Google. Linee guida:

  • Throttle polite: non superare 1 richiesta al secondo per IP, preferibilmente meno.
  • Preferisci API ufficiali: per uso commerciale su larga scala, valuta il Custom Search API di Google che offre 100 query/giorno gratuite.
  • Rispetta robots.txt: controlla https://www.google.com/robots.txt per le direttive correnti.
  • GDPR: i dati di suggerimento non contengono dati personali, ma evita di associare query a individui.
  • Cache dei risultati: salva localmente per ridurre richieste ripetute.

Key Takeaways

Riepilogo dei punti chiave:

  • Autocomplete, PAA e Related Searches sono tre fonti gratuite di dati di intent con caratteristiche complementari.
  • Un singolo seed può generare 400+ long-tail combinando prefissi a–z e modificatori con Autocomplete.
  • Le domande PAA si espandono ricorsivamente cliccando gli accordion, creando alberi di domande informative.
  • I proxy residenziali con geo-targeting paese/città sono essenziali per suggerimenti localizzati e per evitare rate limit.
  • Usa sessioni sticky per Playwright (stesso IP per sessione) e rotazione per-request per Autocomplete.
  • Dedup, clustering per intent e export CSV trasformano dati grezzi in un piano contenuti actionable.
  • Throttle polite (≤1 req/s/IP) e preferenza per API ufficiali su scala commerciale sono principi etici fondamentali.

Pronto a costruire il tuo pipeline di keyword research? Esplora i piani ProxyHat, consulta le use case di web scraping e i casi d'uso per SERP tracking per approfondire.

Domande frequenti

Cos'è lo scraping di People Also Ask e Autocomplete di Google per ricerca keyword?

È la pratica di estrarre i suggerimenti di ricerca di Google — Autocomplete (suggestqueries.google.com), domande People Also Ask nelle SERP e Related Searches — per costruire liste di long-tail keyword e cluster di intent. Si usa Python con proxy residenziali per evitare rate limit e ottenere suggerimenti localizzati. È una fonte gratuita di dati di intent reale, complementare ai tool SEO commerciali.

Perché lo scraping di People Also Ask e Autocomplete importa agli utenti di proxy?

Google applica rate limit per IP su suggestqueries.google.com e sulle SERP. Inoltre, Autocomplete è fortemente localizzato: l'IP determina i suggerimenti ricevuti. I proxy residenziali con geo-targeting paese/città permettono di ottenere suggerimenti localizzati corretti, distribuire il carico su più IP e ridurre il rischio di CAPTCHA. Senza proxy, poche decine di richieste dallo stesso IP innescano errori 429.

Quale tipo di proxy funziona meglio per lo scraping di People Also Ask e Autocomplete?

I proxy residenziali sono la scelta ottimale perché usano IP reali di provider ISP, che Google riconosce come traffico legittimo. I proxy datacenter vengono spesso flaggati come automatizzati e innescano CAPTCHA più frequentemente. Per Autocomplete, usa rotazione per-request con geo-targeting paese. Per Playwright e scraping PAA, usa sessioni sticky per mantenere lo stesso IP durante l'intera sessione browser.

Come evitare i blocchi quando si implementa lo scraping di People Also Ask e Autocomplete?

Usa proxy residenziali con rotazione, mantieni max 2–5 richieste concurrent per IP con delay di 1–3 secondi tra batch. Implementa retry con backoff esponenziale al 429. Usa header User-Agent realistici e Accept-Language coerente con la locale del proxy. Per Playwright, usa sessioni sticky. Rileva CAPTCHA controllando se la risposta contiene 'captcha' o 'unusual traffic' e cambia IP immediatamente.

È legale fare scraping di Autocomplete e People Also Ask di Google?

I dati di suggerimento sono pubblicamente accessibili a chiunque digiti su Google. Tuttavia, lo scraping su larga scala può violare i Terms of Service di Google. Per uso commerciale, valuta il Custom Search API ufficiale (100 query/giorno gratuite). Rispetta sempre robots.txt, throttle polite (≤1 req/s/IP), non associare query a individui (GDPR) e preferisci API ufficiali quando disponibili.

Monitoraggio delle posizioni che non viene bloccato

Dati SERP accurati con proxy residenziali. Crea un account e avvia il monitoraggio.

Avvia il monitoraggio
← Torna al Blog