PDF-Text extrahieren in Python
Text aus PDFs zu extrahieren gehört zu den häufigsten Aufgaben in der Dokumentverarbeitung — vom Aufbau eines Suchindex über das Einspeisen in ein RAG-System bis hin zu Data Mining und Compliance-Workflows. Diese Anleitung zeigt Ihnen Schritt für Schritt, wie Sie mit PDF Oxide Text aus PDFs in Python, JavaScript und Rust extrahieren: als reiner Text, mit zeichengenauen Koordinaten, als formatierte Spans, per OCR für gescannte Seiten, bei verschlüsselten Dateien und mit einem Performance-Tuning für große Stapelverarbeitung.
Drei Zeilen genügen, um Text aus einem beliebigen PDF zu lesen:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("document.pdf")
text = doc.extract_text(0) # Seite 0
print(text)
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const bytes = new Uint8Array(buffer);
const doc = new WasmPdfDocument(bytes);
const text = doc.extractText(0); // Seite 0
console.log(text);
doc.free();
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("document.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
doc, err := pdfoxide.Open("document.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
text, err := doc.ExtractText(0) // Seite 0
if err != nil { log.Fatal(err) }
fmt.Println(text)
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("document.pdf");
var text = doc.ExtractText(0); // Seite 0
Console.WriteLine(text);
PDF Oxide extrahiert Text mit einem Durchschnitt von 0,8 ms pro Seite — fünfmal schneller als PyMuPDF, fünfzehnmal schneller als pypdf — bei einer Erfolgsquote von 100 Prozent auf 3.830 getesteten PDFs.
Warum PDF-Textextraktion so schwierig ist
PDF ist ein visuelles Format, kein Textformat. Anders als HTML oder Markdown speichert eine PDF-Datei keine „Absätze" oder „Sätze", sondern einzelne Zeichen an festen Koordinaten auf einer Seite. Damit aus diesen Zeichen lesbarer Text wird, müssen mehrere Schritte zusammenspielen:
- Schriftarten dekodieren — PDF-Schriften ordnen Zeichencodes über Kodierungstabellen (WinAnsi, MacRoman, Unicode-CMaps, Type 1, TrueType, CIDFont) einzelnen Glyphen zu. Der Code
0x41kann in einer Schrift „A" bedeuten, in einer anderen „α". - Text-Streams parsen — Operatoren wie
Tj,TJ,'und"positionieren Zeichen auf der Seite. Kerning-Korrekturen inTJ-Arrays verschieben Zeichen um Bruchteile eines Punkts, und fehlende Leerzeichen müssen aus den Abständen zwischen den Zeichenpositionen abgeleitet werden. - Layout rekonstruieren — Zeichen auf einer Seite haben keine explizite Leserichtung. Zweispaltige Layouts, Kopf- und Fußzeilen, Tabellen und Seitenleisten müssen räumlich analysiert werden, um einen linearen Textfluss zu erzeugen.
- Kodierungs-Sonderfälle — CJK-Text (Chinesisch, Japanisch, Koreanisch) verwendet CIDFont/CMap-Kodierungen mit Tausenden von Glyphen. Arabisch und Hebräisch benötigen Rechts-nach-links-Umordnung. Ligaturen (fi, fl, ffi) müssen zerlegt werden.
- Eingebettete Teilmengen — Viele PDFs betten nur die tatsächlich genutzten Glyphen mit eigenen Kodierungsvektoren ein. Eine Schrift kann Glyphen-Index 1→„T", 2→„h", 3→„e" zuordnen, ohne einer Standardkodierung zu folgen.
Genau deshalb liefern verschiedene PDF-Bibliotheken für dieselbe Datei unterschiedliche Textergebnisse — und manche scheitern bei komplexen Dokumenten komplett. PDF Oxide deckt all diese Fälle mit einem in Rust geschriebenen Parser ab, der auf 3.830 realen PDFs mit einer Erfolgsquote von 100 Prozent getestet wurde.
Installation
Python (PyPI):
pip install pdf_oxide
Vorgefertigte Wheels für Linux (x86_64, aarch64), macOS (Intel und Apple Silicon) sowie Windows (x86_64). Python 3.8 oder neuer. Keine Systemabhängigkeiten — der Rust-Core wird direkt in das Wheel kompiliert, Poppler, MuPDF oder andere C-Bibliotheken müssen Sie also nicht installieren.
JavaScript (npm):
npm install pdf-oxide-wasm
Läuft in Node.js 18 und neuer sowie in modernen Browsern. Das WASM-Binary ist im Paket enthalten.
Rust (Cargo):
cargo add pdf_oxide
Benötigt Rust 1.70 oder neuer. Ausser einer Standard-Rust-Toolchain sind keine Systemabhängigkeiten erforderlich.
Alle Seiten extrahieren
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
full_text = []
for i in range(doc.page_count()):
text = doc.extract_text(i)
full_text.append(text)
print("\n".join(full_text))
WASM
const doc = new WasmPdfDocument(bytes);
const fullText = doc.extractAllText();
console.log(fullText);
doc.free();
Rust
let mut doc = PdfDocument::open("report.pdf")?;
let mut full_text = Vec::new();
for i in 0..doc.page_count()? {
full_text.push(doc.extract_text(i)?);
}
println!("{}", full_text.join("\n"));
Go
doc, err := pdfoxide.Open("report.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
full, err := doc.ExtractAllText()
if err != nil { log.Fatal(err) }
fmt.Println(full)
C#
using var doc = PdfDocument.Open("report.pdf");
var parts = new List<string>();
for (int i = 0; i < doc.PageCount; i++)
parts.Add(doc.ExtractText(i));
Console.WriteLine(string.Join("\n", parts));
Text mit Zeichenpositionen extrahieren
Holen Sie sich präzise Koordinaten, Schriftnamen und Schriftgrößen für jedes einzelne Zeichen:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
chars = doc.extract_chars(0)
for ch in chars[:20]:
print(f"'{ch.char}' at ({ch.x:.1f}, {ch.y:.1f}) "
f"font={ch.font_name} size={ch.font_size:.1f}")
WASM
const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);
for (const ch of chars.slice(0, 20)) {
console.log(`'${ch.char}' at (${ch.x.toFixed(1)}, ${ch.y.toFixed(1)}) font=${ch.fontName} size=${ch.fontSize.toFixed(1)}`);
}
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let chars = doc.extract_chars(0)?;
for ch in chars.iter().take(20) {
println!("'{}' at ({:.1}, {:.1}) font={} size={:.1}",
ch.char, ch.x, ch.y, ch.font_name, ch.font_size);
}
Go
doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)
for _, ch := range chars[:20] {
fmt.Printf("%q at (%.1f, %.1f) font=%s size=%.1f\n",
ch.Char, ch.X, ch.Y, ch.FontName, ch.FontSize)
}
C#
using var doc = PdfDocument.Open("paper.pdf");
var chars = doc.ExtractChars(0);
foreach (var ch in chars.Take(20))
Console.WriteLine($"'{ch.Char}' at ({ch.X:F1}, {ch.Y:F1}) font={ch.FontName} size={ch.FontSize:F1}");
Zu jedem Zeichen erhalten Sie:
| Feld | Typ | Beschreibung |
|---|---|---|
char |
str |
Das Unicode-Zeichen |
x, y |
float |
Position in Punkten |
font_size |
float |
Schriftgröße in Punkten |
font_name |
str |
PostScript-Schriftname |
bbox |
tuple |
Begrenzungsrahmen (x0, y0, x1, y1) |
Die zeichengenaue Extraktion ist ideal, um Tabellen zu rekonstruieren, Überschriften anhand der Schriftgröße zu erkennen oder Bounding Boxes um Textregionen zu legen. Sie können Zeichen beispielsweise über die y-Koordinate in Zeilen gruppieren und Spaltengrenzen anhand größerer Lücken in den x-Positionen bestimmen.
Formatierte Text-Spans extrahieren
Fassen Sie zusammenhängende Zeichen mit gleicher Schrift und Größe zu Spans zusammen:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
spans = doc.extract_spans(0)
for span in spans:
print(f"'{span.text}' font={span.font_name} size={span.font_size:.1f}")
WASM
const doc = new WasmPdfDocument(bytes);
const spans = doc.extractSpans(0);
for (const span of spans) {
console.log(`'${span.text}' font=${span.fontName} size=${span.fontSize.toFixed(1)}`);
}
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;
for span in &spans {
println!("'{}' font={} size={:.1}", span.text, span.font_name, span.font_size);
}
Praktisch, um Überschriften oder fetten Text zu erkennen oder daraus eine strukturierte Ausgabe zu erzeugen.
Stapelverarbeitung
Verarbeiten Sie Hunderte oder Tausende PDFs in einem Durchlauf:
from pdf_oxide import PdfDocument, PdfError
from pathlib import Path
pdf_dir = Path("documents/")
for pdf_path in pdf_dir.glob("*.pdf"):
try:
doc = PdfDocument(str(pdf_path))
for i in range(doc.page_count()):
text = doc.extract_text(i)
# Text weiterverarbeiten ...
except PdfError as e:
print(f"Uebersprungen {pdf_path.name}: {e}")
Bei 0,8 ms pro Seite benötigt die Verarbeitung von 3.830 PDFs rund 3,1 Sekunden. Für produktive Pipelines finden Sie im Leitfaden zur Stapelverarbeitung Muster für Parallelverarbeitung mit Multiprocessing und asynchronem I/O.
Gescannte PDFs verarbeiten (OCR)
Enthält ein PDF eingescannte Bilder statt echtem Text, liefert extract_text() nur eine leere oder sehr kurze Zeichenkette zurück. Nutzen Sie in diesem Fall die eingebaute OCR von PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
text = doc.extract_text(0)
if not text.strip():
# Seite ist vermutlich ein Scan — OCR verwenden
text = doc.extract_text_ocr(0)
print(text)
PDF Oxide nutzt PaddleOCR über die ONNX Runtime — eine Tesseract-Installation ist nicht nötig. Details zu Modellauswahl und Konfiguration stehen im OCR-Leitfaden.
Verschlüsselte PDFs verarbeiten
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("protected.pdf", password="secret")
text = doc.extract_text(0)
print(text)
WASM
const doc = new WasmPdfDocument(bytes);
doc.authenticate("secret");
const text = doc.extractText(0);
console.log(text);
doc.free();
Rust
let mut doc = PdfDocument::open_with_password("protected.pdf", "secret")?;
let text = doc.extract_text(0)?;
println!("{}", text);
Go
doc, _ := pdfoxide.Open("protected.pdf")
defer doc.Close()
if _, err := doc.Authenticate("secret"); err != nil { log.Fatal(err) }
text, _ := doc.ExtractText(0)
fmt.Println(text)
C#
using var doc = PdfDocument.OpenWithPassword("protected.pdf", "secret");
Console.WriteLine(doc.ExtractText(0));
Unterstützt werden PDFs mit AES-256-, AES-128- und RC4-Verschlüsselung. Während pdfplumber verschlüsselte Dateien gar nicht öffnen kann und pdfminer an AES-256 scheitert, verarbeitet PDF Oxide alle gängigen PDF-Verschlüsselungen transparent.
Ausgabe als Markdown
Für eine strukturierte Ausgabe mit Überschriften und Formatierungen:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
WASM
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
doc.free();
Rust
let mut doc = PdfDocument::open("paper.pdf")?;
let md = doc.to_markdown(0, true)?;
println!("{}", md);
Go
doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
md, _ := doc.ToMarkdown(0)
fmt.Println(md)
C#
using var doc = PdfDocument.Open("paper.pdf");
Console.WriteLine(doc.ToMarkdown(0));
Weitere Integrationsmuster für RAG und LLMs finden Sie im Leitfaden PDF zu Markdown.
Im PDF suchen
Finden Sie Textstellen seitenübergreifend, inklusive Positionsdaten:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("manual.pdf")
results = doc.search("configuration")
for r in results:
print(f"Page {r.page}: '{r.text}' at ({r.x:.0f}, {r.y:.0f})")
WASM
const doc = new WasmPdfDocument(bytes);
const results = doc.search("configuration", false);
for (const r of results) {
console.log(`Page ${r.page}: '${r.text}' at (${r.x.toFixed(0)}, ${r.y.toFixed(0)})`);
}
doc.free();
Rust
let mut pdf = Pdf::open("manual.pdf")?;
let results = pdf.search("configuration")?;
for r in &results {
println!("Page {}: '{}' at ({:.0}, {:.0})", r.page, r.text, r.bbox.x, r.bbox.y);
}
Go
doc, _ := pdfoxide.Open("manual.pdf")
defer doc.Close()
results, _ := doc.SearchAll("configuration", false)
for _, r := range results {
fmt.Printf("Page %d: %q at (%.0f, %.0f)\n", r.PageIndex, r.Text, r.X, r.Y)
}
C#
using var doc = PdfDocument.Open("manual.pdf");
foreach (var r in doc.SearchAll("configuration", caseSensitive: false))
Console.WriteLine($"Page {r.PageIndex}: '{r.Text}' at ({r.X:F0}, {r.Y:F0})");
Vergleich mit anderen Python-Bibliotheken für PDFs
Für die PDF-Textextraktion in Python gibt es mehrere Optionen. So schneiden sie im Vergleich ab:
- pypdf — reines Python, keine C-Abhängigkeiten. Einfach zu installieren, aber langsam (12 ms pro Seite) und scheitert an 1,6 Prozent der PDFs, weil Schrift- und Kodierungsunterstützung nicht ausreicht. Keine Zeichenpositionen. Brauchbar für einfache PDFs, wenn Geschwindigkeit keine Rolle spielt.
- pdfplumber — baut auf pdfminer auf und liefert ausführliche Zeichen- und Tabelleninformationen. Sehr langsam (23 ms pro Seite) und kann verschlüsselte PDFs nicht öffnen. Sinnvoll für Tabellenextraktion, wenn Sie Zellen-Details benötigen und Performance zweitrangig ist.
- PyMuPDF (fitz) — Python-Bindings zur C-Bibliothek MuPDF. Schnell (4,6 ms pro Seite) und zuverlässig (99,3 Prozent Erfolgsquote). Erfordert eine C-Bibliothek und steht unter AGPL. Eine solide Wahl, wenn die Lizenz zu Ihrem Projekt passt.
- pypdfium2 — Python-Bindings zur PDFium-Engine von Google. Schnell (4,1 ms pro Seite), aber mit hoher p99-Latenz (42 ms) bei komplexen Dokumenten. Die API ist deutlich schmaler als bei PyMuPDF.
- pdfminer.six — reines Python mit detaillierter Layoutanalyse. Sehr langsam und kaum noch gepflegt. Scheitert an AES-256-verschlüsselten PDFs. Wurde grösstenteils von pdfplumber abgelöst.
- PDF Oxide — Rust-Kern mit Python-Bindings über PyO3. Die schnellste Option (0,8 ms pro Seite), 100 Prozent Erfolgsquote, unterstützt alle gängigen Verschlüsselungen und bringt OCR direkt mit. MIT-lizenziert, ohne Systemabhängigkeiten.
PDF Oxide wurde gezielt gebaut, um die Lücken der vorhandenen Bibliotheken zu schließen: die Performancegrenzen reiner Python-Parser, die Lizenzeinschränkungen von MuPDF und die Stabilitätsprobleme, an denen andere Bibliotheken bei realen PDFs mit ungewöhnlichen Schriften, beschädigten Cross-Reference-Tabellen oder exotischen Kodierungen scheitern.
Performance: Wie schnell ist PDF Oxide?
Gemessen auf 3.830 PDFs aus drei unabhängigen öffentlichen Testkorpora:
| Bibliothek | Mittel | p99 | Erfolgsquote |
|---|---|---|---|
| PDF Oxide | 0,8 ms | 9 ms | 100 % |
| PyMuPDF | 4,6 ms | 28 ms | 99,3 % |
| pypdfium2 | 4,1 ms | 42 ms | 99,2 % |
| pypdf | 12,1 ms | 97 ms | 98,4 % |
| pdfplumber | 23,2 ms | 189 ms | 98,8 % |
Für eine Pipeline mit 10.000 PDFs bedeutet das:
- PDF Oxide: 8 Sekunden
- PyMuPDF: 46 Sekunden
- pypdf: 2 Minuten
- pdfplumber: 3,9 Minuten
Methodik und Anleitung zum Nachstellen finden Sie in den ausführlichen Benchmarks.
Häufige Probleme und Lösungen
Leere Textausgabe
Liefert extract_text() eine leere Zeichenkette zurück, enthält die Seite wahrscheinlich eingescannte Bilder statt echtem Text. Verwenden Sie in diesem Fall extract_text_ocr(). Die Einrichtung ist unter Gescannte PDFs mit OCR beschrieben.
Fehlerhafte oder unleserliche Zeichen
Das deutet meist auf eine Schriftart mit nicht standardkonformem Kodierungsvektor oder ohne ToUnicode-CMap hin. PDF Oxide fängt die meisten dieser Fälle ab, aber bei absichtlich verschleierten PDFs (DRM-geschützter Inhalt) kann die Ausgabe dennoch falsch sein.
Fehlende Leerzeichen oder zusammengeklebte Wörter
PDF-Textoperatoren platzieren Zeichen einzeln. Leerzeichen müssen aus dem Abstand zwischen Zeichen relativ zur Leerzeichenbreite der Schrift abgeleitet werden. Kleben die Wörter zusammen, nutzen Sie extract_chars() und implementieren Sie eine eigene Abstandslogik auf Basis der Koordinaten.
Abweichende Ausgabe gegenüber anderen Bibliotheken
Jede Bibliothek verwendet eigene Heuristiken für Leerzeichen-Erkennung, Zeilenumbrüche und Leserichtung. PDF Oxide erreicht auf 3.830 PDFs eine Textübereinstimmung von 99,5 Prozent mit PyMuPDF. Die restlichen 0,5 Prozent betreffen vor allem die Whitespace-Normalisierung und den Umgang mit Ligaturen.
Typische Einsatzgebiete
Volltextsuche — Extrahieren Sie den Text jeder Seite aller PDFs in einem Dokumentenarchiv und speisen Sie das Ergebnis in Elasticsearch, Typesense oder eine Vektordatenbank ein. Dank der Geschwindigkeit von PDF Oxide können Sie Tausende Dokumente bei Bedarf neu indizieren.
RAG-Pipelines (Retrieval-Augmented Generation) — Extrahieren und zerlegen Sie PDF-Text in Chunks, die Sie anschließend mit OpenAI, Cohere oder Open-Source-Modellen einbetten. Mit extract_spans() bleibt die Überschriftenstruktur erhalten, sodass Chunks sauber an den Dokumentabschnitten ausgerichtet sind. Siehe den Leitfaden PDF zu Markdown für LLM-optimierte Ausgaben.
Compliance und Audit — Durchsuchen Sie Verträge, Rechnungen und regulatorische Einreichungen nach bestimmten Klauseln oder Schlüsselwörtern. doc.search() liefert Treffer über alle Seiten hinweg mit präzisen Positionen, oder Sie extrahieren den Volltext für eine NLP-basierte Klauselerkennung.
Datenextraktion — Holen Sie strukturierte Daten aus Rechnungen, Belegen, Kontoauszügen und Formularen. Kombinieren Sie extract_chars() mit fachspezifischen Regeln, um Felder wie „Gesamtbetrag" oder „Rechnungsdatum" zu finden und die Werte daneben auszulesen.
Wissenschaftliche Arbeit — Verarbeiten Sie Tausende Forschungsarbeiten für Literaturrecherchen, Zitationsextraktionen oder Meta-Analysen. PDF Oxide kommt mit der gesamten Bandbreite an PDF-Produzenten (LaTeX, Word, InDesign, Quark) und den in der Wissenschaft üblichen Schriftkodierungen zurecht.
Verwandte Seiten
- API zur Textextraktion — vollständige API-Referenz
- PDF zu Markdown — strukturierte Konvertierung
- Stapelverarbeitung — Muster für Parallelverarbeitung
- Gescannte PDFs mit OCR — OCR einrichten und nutzen
- Performance-Benchmarks — Methodik und Ergebnisse