Extraer papers, autores y recuentos de citas de Google Scholar con Python
Consulta Google Scholar sobre un tema y obtén papers, años y recuentos de citas como JSON, con un endpoint tan lento que hay que tratarlo como un job por lotes. Probado en agosto de 2026.

Estás montando una revisión bibliográfica, o un dashboard que sigue un tema de investigación a lo largo del tiempo, y necesitas los papers: título, autores, año, cuántas veces se ha citado cada uno. La forma por defecto de conseguir eso es una pestaña del navegador en Google Scholar y una hoja de cálculo que rellenas a mano, página de resultados a página de resultados. Google Scholar no tiene una API pública oficial, así que la mayoría de herramientas alrededor son exactamente eso: una persona copiando filas.
La solicitud
Una llamada a /v1/scholar recibe la misma consulta que escribirías en el cuadro de búsqueda, más unos cuantos parámetros opcionales: language, page, results, y as_ylo / as_yhi si quieres que sea el propio Scholar el que restrinja la respuesta a un rango de años antes de que te llegue nada.
Tarda casi ocho segundos, así que no te quedes esperando
En mediciones que hicimos el 27 de agosto de 2026, /v1/scholar tuvo un tiempo de respuesta mediano de unos 7,9 segundos, el endpoint más lento que vendemos. Eso no es un bug que haya que esquivar, es lo que cuesta consultar un índice bibliográfico tan grande. Diseña para eso: trae un lote de temas en un job en segundo plano o una cola, escribe el JSON en tu propio almacén, y haz que tu dashboard o tu exportación lean de ahí. No pongas este endpoint en una ruta de solicitud donde haya una persona mirando un spinner.
Papers, años y citas del mismo JSON
Aquí tienes una solicitud para “web scraping api” que filtra a los papers de 2020 en adelante y ordena lo que queda por número de citas:
import re
import requests
API_KEY = "faf_live_..." # tu clave de FastAF
resp = requests.get(
"https://api.fastaf.co/v1/scholar",
params={"query": "web scraping api"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=60,
)
resp.raise_for_status()
papers = resp.json()["scholar_results"]
def citation_count(paper):
total = paper.get("inline_links", {}).get("cited_by", {}).get("total", "")
match = re.search(r"\d+", total)
return int(match.group()) if match else 0
def pub_year(paper):
match = re.search(r"\b(19|20)\d{2}\b", paper.get("displayed_link", ""))
return int(match.group()) if match else None
recent = [p for p in papers if (pub_year(p) or 0) >= 2020]
recent.sort(key=citation_count, reverse=True)
for p in recent:
authors = ", ".join(a["name"] for a in p.get("authors", []))
print(f'{citation_count(p):>5} {pub_year(p)} {p["title"]}')
Ejecutamos exactamente esto mientras escribíamos el post (agosto de 2026). Volvieron diez papers por 5 créditos; seis eran de 2020 en adelante, y al ordenar por citas, un artículo de revisión de 2021 sobre técnicas de web scraping quedó primero con 651 citas, por delante de un paper de 2024 con 14 y uno de 2025 que no tenía ningún recuento de citas, algo que el código de arriba puntúa correctamente como cero en vez de fallar.
Qué es sólido, y qué tienes que calcular tú mismo
Cada entrada de scholar_results te da title, title_link, un snippet, y un listado authors de {name, link, author_id} cuando Scholar llega a asignar autores con nombre al resultado (los libros y algunos resultados de nuestra muestra venían con un listado vacío). Hay dos cosas que la gente espera y que no están como campos limpios. No hay un year independiente: está incrustado en la cadena de texto libre displayed_link junto con la publicación, que es por lo que el código de arriba lo extrae con una regex en vez de leer una clave. Y tampoco hay un recuento de citas como entero plano, solo inline_links.cited_by.total, una cadena que dice “Cited by 418” en la mayoría de resultados pero que en una entrada de nuestra muestra decía “Related articles”, lo que significa que ese resultado no tiene ningún número de citas que mostrar. Trata ambos casos como texto extraído de una cadena de citas a base de heurística, no como campos estructurados garantizados, y escribe primero el caso de dato ausente antes que el camino feliz. Cuando un resultado sí tiene un PDF disponible, aparece bajo resources.
La misma consulta no devuelve el mismo número de papers
Esta te puede morder si das por hecho que no pasa. El 27 de agosto de 2026 enviamos la misma consulta, graph neural networks, seis veces seguidas por /v1/scholar. Los recuentos de resultados volvieron 5, 10, 10, 8, 0 y 10. Una de cada seis llamadas no devolvió nada en absoluto, y se cobró como cualquier otra llamada, porque la solicitud en sí tuvo éxito: Scholar simplemente no tenía nada que dar en ese momento.
Así que no trates un listado vacío como una respuesta, ni trates el recuento como algo estable. Reintenta una vez un resultado vacío tras una pausa corta antes de anotar que no hay papers, y si estás comparando los resultados de hoy con los de la semana pasada, compara por paper y no por recuento, o vas a generar alertas de papers que aparecen y desaparecen sin haberse ido nunca de verdad.
import time
def search_with_retry(query, attempts=2):
for attempt in range(attempts):
papers = search(query) # la función de la sección anterior
if papers:
return papers
time.sleep(2)
return []
Ten en cuenta los reintentos en el presupuesto: a 5 créditos la llamada, una tasa de una vacía de cada seis añade aproximadamente un 17 por ciento al coste de una ejecución que reintenta cada resultado en blanco.
Cuánto cuesta
/v1/scholar cuesta 5 créditos por llamada, sea cual sea el número de papers que vuelvan. Los 500 créditos únicos del plan gratuito cubren 100 llamadas, suficiente para comprobar la cobertura de campos con tus propios temas. En el pack Scale ($99 por 300.000 créditos) son 60.000 llamadas, $1,65 por cada 1.000.
En resumen
Trata /v1/scholar como un job por lotes, no como una consulta en vivo: encola los temas, guarda el JSON en caché de tu lado, y deja que tu propia interfaz haga el filtrado y ordenado instantáneo que el endpoint por sí solo es demasiado lento para hacer al momento. Consigue 500 créditos gratis, sin tarjeta y pruébalo con un tema que sigas de verdad.