Du hast eine DOI, die Seite des Verlags verlangt einen Login, und du willst nur den Methodenteil lesen. Sehr oft gibt es irgendwo eine legale, kostenlose Kopie: im Repositorium der Universität der Autoren, als Preprint, in PubMed Central. Von Hand heißt das, vier oder fünf Seiten durchzuprobieren. In Code braucht es eine Handvoll öffentlicher APIs, die in sinnvoller Reihenfolge abgefragt werden.

Die Reihenfolge unten ist die des Buttons „View“ im Paper-Workspace der osFoundry-Desktop-App. Deren Backend probiert nacheinander jede Quelle, bis eine ein echtes PDF liefert (die Seite zum Workspace für wissenschaftliches Schreiben und Recherchieren zeigt den Bildschirm drumherum). Die Beispiele nutzen zwei echte DOIs zur selben Frage, nämlich ob ein Smartphone auf dem Schreibtisch die Konzentration stört:

  • 10.1037/tmb0000123, eine Metaanalyse von 2024 (Hartanto et al.)
  • 10.1086/691462, die vielzitierte „Brain Drain“-Studie von 2017 (Ward et al.)

Ein Klick auf „View“ bei einem Suchergebnis öffnet den Text der Metaanalyse direkt in der App

OpenAlex nach allen Fundorten fragen, nicht nur nach dem Open-Access-Flag

OpenAlex listet jeden Ort, an dem ein Paper liegt, und für jeden, ob es dort ein direktes PDF gibt:

curl -s "https://api.openalex.org/works/doi:10.1037/tmb0000123?select=open_access,best_oa_location,locations" 

Für die Metaanalyse kommen drei Fundorte zurück: der PDF-Link des Verlags, ein PDF im Repositorium der James Cook University und eine Landingpage in der Bibliothek der Singapore Management University.

Beim Brain-Drain-Paper zeigt dieselbe Anfrage "is_oa": true und "oa_status": "green", aber jedes pdf_url ist null. Die einzige offene Kopie ist die Landingpage eines Repositoriums. is_oa sagt also nur, dass irgendwo eine freie Version existiert, nicht, dass du sie herunterladen kannst. Lies deshalb pdf_url an jedem Fundort.

Ohne API-Key teilen sich OpenAlex-Anfragen ein kleines kostenloses Tagesbudget mit allen anderen hinter deiner IP-Adresse, und meta.notice in der Antwort weist darauf hin. Ein kostenloser Key (api_key= in der Abfrage) gibt dir ein eigenes Budget; die App liest einen aus OPENALEX_API_KEY, wenn du ihn setzt.

Die ersten fünf Bytes prüfen

Auch ein pdf_url ist nur ein Versprechen. Verlage liefern Cookie-Wände, Bot-Prüfungen und Login-Seiten mit Status 200 und HTML im Body. Der Rust-Code der App akzeptiert einen Download nur, wenn der Body mit der PDF-Magic-Number beginnt:

if !fetched.bytes.starts_with(b"%PDF-") { self.tried.push(format!("{source}: link was a web page, not a PDF")); return None; } 

Dieselbe Idee in Python, nur mit der Standardbibliothek:

import json import urllib.error import urllib.request UA = {"User-Agent": "paper-finder/1.0"} def get(url): with urllib.request.urlopen(urllib.request.Request(url, headers=UA), timeout=30) as r: return r.read() def pdf_links(doi): try: work = json.loads(get(f"https://api.openalex.org/works/doi:{doi}" "?select=best_oa_location,locations")) except urllib.error.HTTPError as e: if e.code == 404: return [] raise links = [] for loc in [work.get("best_oa_location")] + work.get("locations", []): url = (loc or {}).get("pdf_url") if url and url not in links: links.append(url) return links def first_real_pdf(doi): tried = [] for url in pdf_links(doi)[:4]: try: body = get(url) except (urllib.error.URLError, TimeoutError) as e: tried.append(f"{url}: {e}") continue if body[:5] == b"%PDF-": return url, body tried.append(f"{url}: a web page, not a PDF") raise LookupError("no free PDF; tried: " + "; ".join(tried or ["no pdf_url at all"])) 

Bei der Metaanalyse lehnte der Verlagslink das Skript ab, und die Repositoriumskopie kam als 1,6 MB großes PDF zurück. Bei Brain Drain bricht es sofort mit „no pdf_url at all“ ab. Die Liste dessen, was versucht wurde und warum es scheiterte, ist die paar Zeilen wert; ein bloßes „kein Volltext“ schickt dich wieder zurück zur Handarbeit.

Auch die Begrenzung auf vier Links pro Abfrage und das Entfernen doppelter URLs stammen aus der App. Manche Paper haben ein Dutzend Fundorte, die alle auf dieselbe Datei zeigen.

Der Rest der Kette

Die OpenAlex-Abfrage ist nur ein Schritt. Die vollständige Reihenfolge der App:

  1. Der PDF-Link, der schon beim Suchergebnis mitkam, falls es einen gab.
  2. arXiv. Eine DOI der Form 10.48550/arXiv.<id> ist ein arXiv-Paper, und https://arxiv.org/pdf/<id> ist immer kostenlos.
  3. Offene Kopien der DOI von Unpaywall oder OpenAlex. Unpaywall (https://api.unpaywall.org/v2/<doi>?email=<your email>) liefert best_oa_location.url_for_pdf und eine Liste oa_locations und lehnt Anfragen ohne echte E-Mail-Adresse ab. Beide Dienste stammen von derselben Non-Profit-Organisation, OurResearch, deshalb fragt die App Unpaywall, wenn UNPAYWALL_EMAIL gesetzt ist, und liest sonst die Fundorte von OpenAlex, nicht beides.
  4. CORE. Die v3-API (/v3/search/works?q=doi:"<doi>", mit Bearer-Key) bündelt Kopien aus Repositorien und liefert den extrahierten Text oft direkt in fullText. Die App übernimmt diesen Text, wenn er länger als 500 Zeichen ist, und probiert sonst die downloadUrl.
  5. PubMed Central. Mit einer PMCID liefert Europe PMC den ganzen Artikel als JATS-XML unter https://www.ebi.ac.uk/europepmc/webservices/rest/<PMCID>/fullTextXML. Das ist kein PDF, enthält aber Abschnitte, Abbildungen und Literaturverzeichnis in einer Struktur, die sich sauber in HTML umwandeln lässt.

Abstracts kommen als invertierter Index

Noch ein OpenAlex-Detail, über das viele stolpern: Es gibt kein Feld abstract. Du bekommst abstract_inverted_index, eine Zuordnung von jedem Wort zu den Positionen, an denen es vorkommt, und baust den Text selbst wieder zusammen:

words = {pos: w for w, positions in idx.items() for pos in positions} abstract = " ".join(words[i] for i in sorted(words)) 

Das reicht für Suchergebnisse und die erste Sichtung. Beim Brain-Drain-Paper, dessen einzige offene Kopie eine Repositoriumsseite ist, kommt der kostenlose Weg mit Abstract plus dem Volltext der Metaanalyse ungefähr so weit, wie er kommt – und das ist meist der Punkt, an dem man prüft, ob die eigene Bibliothek Zugang hat.