Paper, Autoren und Zitationszahlen mit Python aus Google Scholar ziehen
Google Scholar nach einem Thema abfragen und Paper, Jahre und Zitationszahlen als JSON bekommen, gebaut um einen Endpoint, der so langsam ist, dass er als Batch-Job laufen muss. Getestet im August 2026.

Du baust eine Literaturübersicht, oder ein Dashboard, das ein Forschungsthema über die Zeit verfolgt, und du brauchst die Paper: Titel, Autoren, Jahr, wie oft jedes zitiert wurde. Der Standardweg dahin ist ein Browser-Tab auf Google Scholar und eine Tabelle, die du von Hand ausfüllst, eine Ergebnisseite nach der anderen. Google Scholar hat keine offizielle öffentliche API, also ist das meiste Tooling drumherum genau das: eine Person, die Zeilen abtippt.
Der Request
Ein Call an /v1/scholar nimmt dieselbe Query, die du in die Suchbox tippen würdest, plus ein paar optionale Parameter: language, page, results, und as_ylo / as_yhi, wenn du willst, dass Scholar selbst die Antwort auf eine Jahresspanne einschränkt, bevor überhaupt etwas zu dir zurückkommt.
Er braucht fast acht Sekunden, also nicht darauf warten
In Messungen, die wir am 27. August 2026 gemacht haben, lag die mediane Antwortzeit von /v1/scholar bei etwa 7,9 Sekunden, der langsamste Endpoint, den wir verkaufen. Das ist kein Bug, um den man herumroutet, das kostet die Abfrage eines großen Literaturindex nun mal. Bau deinen Workflow entsprechend: Hol einen Batch von Themen in einem Background-Job oder einer Queue, schreib das JSON in deinen eigenen Speicher, und lass dein Dashboard oder deinen Export aus diesem Speicher lesen. Setz diesen Endpoint nicht in einen Request-Pfad, in dem jemand einem Spinner zuschaut.
Paper, Jahre und Zitationen aus demselben JSON
Hier ist ein Request für “web scraping api”, der auf Paper ab 2020 filtert und den Rest nach Zitationszahl sortiert:
import re
import requests
API_KEY = "faf_live_..." # dein FastAF-Key
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"]}')
Wir haben genau das beim Schreiben des Posts laufen lassen (August 2026). Zehn Paper kamen für 5 Credits zurück; sechs waren von 2020 oder später, und nach Zitationen sortiert landete ein Survey von 2021 zu Web-Scraping-Techniken mit 651 Zitationen oben, vor einem Paper von 2024 mit 14 und einem Paper von 2025 ganz ohne Zitationszahl, was der Code oben korrekt als null wertet, statt daran abzustürzen.
Was zuverlässig ist, und was du selbst berechnen musst
Jeder Eintrag in scholar_results gibt dir title, title_link, ein snippet, und eine authors-Liste aus {name, link, author_id}, wenn Scholar dem Ergebnis überhaupt benannte Autoren zuordnet (Bücher und ein paar der Ergebnisse in unserem Sample kamen mit einer leeren Liste zurück). Zwei Dinge, die man erwartet, gibt es nicht als saubere Felder. Es gibt kein eigenständiges year: Es steckt im Freitext-String displayed_link zusammen mit der Fundstelle, weshalb der Code oben es mit einer Regex herauszieht statt einen Key zu lesen. Und es gibt auch keine reine Integer-Zitationszahl, nur inline_links.cited_by.total, ein String, der bei den meisten Ergebnissen “Cited by 418” liest, aber bei einem Eintrag in unserem Sample “Related articles” las, was bedeutet, dass dieses Ergebnis überhaupt keine Zitationszahl zum Anzeigen hat. Behandle beides als Best-Effort-Text, gezogen aus einem Zitationsstring, nicht als garantiert strukturierte Felder, und schreib den Fallback für „fehlend” bevor du den Happy Path schreibst. Wenn zu einem Ergebnis ein PDF verfügbar ist, taucht es unter resources auf.
Dieselbe Query liefert nicht immer dieselbe Anzahl Paper
Das hier rächt sich, wenn du es wegdenkst. Am 27. August 2026 haben wir dieselbe Query, graph neural networks, sechsmal hintereinander durch /v1/scholar geschickt. Die Ergebniszahlen kamen als 5, 10, 10, 8, 0 und 10 zurück. Ein Call von sechs lieferte überhaupt nichts, und er wurde wie jeder andere Call berechnet, weil der Request selbst erfolgreich war: Scholar hatte in dem Moment einfach nichts zu geben.
Behandle eine leere Liste also nicht als Antwort, und behandle die Anzahl nicht als stabil. Wiederhol ein leeres Ergebnis einmal nach einer kurzen Pause, bevor du „keine Paper gefunden” festhältst, und wenn du die heutigen Ergebnisse gegen die von letzter Woche diffst, vergleiche nach Paper statt nach Anzahl, sonst erzeugst du Alerts über Paper, die auftauchen und verschwinden, obwohl sie nie wirklich weg waren.
import time
def search_with_retry(query, attempts=2):
for attempt in range(attempts):
papers = search(query) # die Funktion aus dem vorherigen Abschnitt
if papers:
return papers
time.sleep(2)
return []
Kalkuliere die Retries mit ein: Bei 5 Credits pro Call erhöht eine Leerquote von eins zu sechs die Kosten eines Laufs, der jeden Leerlauf wiederholt, um etwa 17 Prozent.
Was es kostet
/v1/scholar kostet 5 Credits pro Call, unabhängig davon, wie viele Paper zurückkommen. Die 500 einmaligen Credits des Free Tiers decken 100 Calls ab, genug, um die Feldabdeckung an deinen eigenen Themen zu prüfen. Mit dem Scale-Paket ($99 für 300.000 Credits) sind das 60.000 Calls, $1,65 pro 1.000.
Kurzversion
Behandle /v1/scholar als Batch-Job, nicht als Live-Lookup: Stell die Themen in eine Queue, cache das JSON auf deiner Seite, und lass deine eigene UI das sofortige Filtern und Sortieren übernehmen, das der Endpoint selbst zu langsam macht, um es auf Abruf zu tun. Hol dir 500 kostenlose Credits, keine Karte und lass es gegen ein Thema laufen, das du wirklich verfolgst.