Cómo obtener resultados de búsqueda de Google en JSON (sin parsear HTML)
Google sirve markup ofuscado y rotativo. Aquí está el contrato JSON de /serp en su lugar: campos garantizados, bloques opcionales, probado en agosto de 2026.

Hay dos formas de sacar datos de una página de resultados de Google: parsear el HTML, o pedir el JSON que describe esa misma página. Solo una de las dos sobrevive a una actualización de markup.
Por qué parsear el HTML no aguanta
Abre el view-source de una página de resultados y busca un nombre de clase sobre el que quieras construir un selector CSS. No vas a encontrar uno que se lea como result-title. Vas a encontrar algo como MjjYud o g6HRUb, generado por máquina, y distinto de la cadena que viste la semana pasada. El front end de Google se compila y ofusca por build, y el build rota.
Aunque fijes un selector a algo estable, como un patrón de href, el layout de debajo cambia según el tipo de consulta. Una consulta informativa simple se renderiza distinto a una que dispara un panel de conocimiento, un local pack o un carrusel de compras. Acabas escribiendo un parser por cada forma de consulta, y luego reescribiéndolos todos la próxima vez que Google lance un rediseño.
Tampoco es esto un problema específico de Google: JavaScript a menudo renderiza partes de la página después de la carga, así que un simple requests.get con BeautifulSoup puede perderse contenido que un navegador real sí mostraría. Ese es un coste aparte, cubierto en nuestro artículo sobre cuándo el scraping necesita renderizado JS.
Cómo es en realidad una respuesta JSON
Envía la misma consulta a una API que devuelve el resultado parseado en vez de la página, y obtienes un conjunto fijo de claves en vez de un DOM que recorrer:
import requests
API_KEY = "faf_live_..." # tu clave de 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])
Los campos con los que puedes contar
organic_results es la única parte de la respuesta contra la que puedes construir sin cubrirte las espaldas: es una lista, y cada entrada lleva rank, title, link y snippet. Ese es el contrato, sea cual sea la consulta.
| Campo | Qué es |
|---|---|
rank |
Posición en la página, indexada desde 1 |
title |
El texto del enlace azul |
link |
La URL de destino |
snippet |
El texto de descripción bajo el resultado |
Todo lo demás en la respuesta es condicional a lo que la consulta realmente produzca, del mismo modo que es condicional en la página en vivo. Bloques como people_also_ask, un panel de conocimiento, anuncios o búsquedas relacionadas aparecen como claves extra de primer nivel cuando Google los renderiza para esa consulta, y simplemente están ausentes si no. Escribe código que compruebe la clave antes de leerla (data.get("people_also_ask")), no código que asuma que siempre está ahí.
Los parámetros que dan forma a la solicitud
La lista completa de parámetros está en la documentación, pero estos son los que usarás en casi todas las solicitudes:
q(aliasquery): el término de búsqueda.country: país de dos letras desde el que buscar, cambia qué dominio de Google y qué conjunto de resultados obtienes.language(aliaslang): idioma de la interfaz y de los resultados.device:mobileodesktop. Los rankings y el layout difieren entre ambos, y mobile es la mayoría del tráfico real en la mayoría de verticales, así que trae los dos si la posición te importa.page: para profundidad más allá de la primera página.location: una señal geográfica más precisa quecountrysolo, útil para consultas con intención local.safe: SafeSearch activado o desactivado.tbs: el propio parámetro de fecha y filtro de Google, para búsquedas restringidas por fecha.
Alternar device entre solicitudes es la forma más barata de detectar una diferencia de ranking que una configuración solo de escritorio pasaría por alto por completo:
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"])
Lo que cuesta
Una solicitud /serp cuesta 5 créditos sin importar cuántos bloques produzca la consulta. Hicimos una solicitud mientras escribíamos este post (agosto de 2026) y devolvió 10 resultados orgánicos por esos 5 créditos. Google ya no respeta num=100, así que si necesitas los 100 primeros resultados de una palabra clave, presupuesta 10 solicitudes paginadas, no una; el desglose de coste por 1.000 búsquedas explica en detalle lo que eso hace con tu factura.
Las solicitudes fallidas no se facturan: los créditos se reservan cuando empieza la solicitud y se devuelven si no vuelve nada.
La versión corta
organic_results con rank, title, link y snippet es la parte del contrato con la que siempre puedes contar. Todo lo demás en la respuesta, people_also_ask incluido, está ahí cuando Google lo renderiza y ausente cuando no, así que condiciona tu código a la clave, no a la consulta. Referencia completa en la documentación, o empieza con el nivel gratuito, 500 créditos, sin tarjeta.