Come ottenere i risultati di ricerca Google in JSON (senza parsing HTML)
Google serve markup offuscato e rotante. Ecco il contratto JSON di /serp invece: campi garantiti, blocchi opzionali, testato agosto 2026.

Ci sono due modi per estrarre dati da una pagina di risultati Google: fare il parsing dell’HTML, o chiedere il JSON che descrive la stessa pagina. Solo uno dei due sopravvive a un aggiornamento del markup.
Perché il parsing dell’HTML non regge
Apri il view-source di una pagina di risultati e cerca un nome di classe su cui vorresti costruire un selettore CSS. Non ne troverai uno che si legga come result-title. Troverai qualcosa come MjjYud o g6HRUb, generato macchina, e diverso dalla stringa che hai visto la settimana scorsa. Il front end di Google è compilato e offuscato per build, e la build ruota.
Anche se fissi un selettore a qualcosa di stabile, come un pattern di href, il layout sottostante cambia in base al tipo di query. Una query informativa semplice si renderizza diversamente da una che attiva un knowledge panel, un local pack, o un carosello shopping. Finisci per scrivere un parser per ogni forma di query, per poi riscriverli tutti al prossimo redesign di Google.
Nessuno di questi è un problema specifico di Google, peraltro: JavaScript spesso renderizza parti della pagina dopo il caricamento, quindi un semplice requests.get con un passaggio BeautifulSoup può perdere contenuto che un browser vero mostrerebbe. Questo è un costo separato, coperto nel nostro articolo su quando lo scraping richiede rendering JS.
Come si presenta davvero una risposta JSON
Manda la stessa query a un’API che restituisce il risultato già parsato invece della pagina, e ottieni un insieme fisso di chiavi invece di un DOM da attraversare:
import requests
API_KEY = "faf_live_..." # la tua chiave FastAF
resp = requests.get(
"https://api.fastaf.co/v1/serp",
params={
"query": "best noise cancelling headphones",
"country": "us",
"language": "en",
"device": "mobile",
},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
resp.raise_for_status()
data = resp.json()
for result in data["organic_results"]:
print(result["rank"], result["title"], result["link"], result["snippet"][:60])
I campi su cui puoi contare
organic_results è l’unica parte della risposta su cui puoi costruire senza riserve: è una lista, e ogni voce porta rank, title, link e snippet. Questo è il contratto, a prescindere dalla query.
| Campo | Cos’è |
|---|---|
rank |
Posizione in pagina, a partire da 1 |
title |
Il testo del link blu |
link |
L’URL di destinazione |
snippet |
Il testo descrittivo sotto il risultato |
Tutto il resto della risposta è condizionale a cosa produce davvero la query, allo stesso modo in cui è condizionale alla pagina live. Blocchi come people_also_ask, un knowledge panel, annunci o ricerche correlate compaiono come chiavi extra di primo livello quando Google li renderizza per quella query, e sono semplicemente assenti altrimenti. Scrivi codice che controlla la presenza della chiave prima di leggerla (data.get("people_also_ask")), non codice che assume sia sempre lì.
I parametri che modellano la richiesta
La lista completa dei parametri è nella documentazione, ma quelli che userai in quasi ogni richiesta:
q(aliasquery): il termine di ricerca.country: paese di due lettere da cui cercare, cambia quale dominio Google e quale set di risultati ottieni.language(aliaslang): lingua dell’interfaccia e dei risultati.device:mobileodesktop. Ranking e layout differiscono tra i due, e mobile è la maggioranza del traffico reale per la maggior parte dei settori, quindi estrai entrambi se la posizione in classifica ti interessa.page: per la profondità oltre la prima pagina.location: un segnale geografico più preciso del solocountry, utile per query a intento locale.safe: SafeSearch attivo o disattivo.tbs: il parametro di data e filtro di Google stesso, per ricerche con vincoli temporali.
Cambiare device tra una richiesta e l’altra è il modo più economico per cogliere una differenza di ranking che una configurazione solo desktop perderebbe del tutto:
for device in ["desktop", "mobile"]:
r = requests.get(
"https://api.fastaf.co/v1/serp",
params={"query": "project management software", "country": "us", "device": device},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
top = r.json()["organic_results"][0]
print(device, "->", top["rank"], top["link"])
Quanto costa
Una richiesta /serp costa 5 crediti indipendentemente da quanti blocchi produce la query. Abbiamo eseguito una richiesta scrivendo questo post (agosto 2026) e ha restituito 10 risultati organici per quei 5 crediti. Google non rispetta più num=100, quindi se ti servono i primi 100 risultati per una keyword, metti a budget 10 richieste paginate, non una; il dettaglio del costo per 1.000 ricerche analizza cosa fa questo alla tua bolletta.
Le richieste fallite non vengono fatturate: i crediti vengono riservati quando la richiesta parte e restituiti se non torna nulla.
In breve
organic_results con rank, title, link e snippet è la parte del contratto su cui puoi sempre contare. Tutto il resto della risposta, people_also_ask incluso, c’è quando Google lo renderizza ed è assente quando non lo fa, quindi condiziona il tuo codice alla chiave, non alla query. Riferimento completo nella documentazione, oppure inizia con il piano gratuito, 500 crediti, senza carta.