Text aus PDF in Python extrahieren
Die Textextraktion aus PDF-Dateien gehört zu den häufigsten Aufgaben in Dokumentenverarbeitungs-Pipelines — vom Aufbau von Suchindizes und der Datenversorgung von RAG-Systemen bis hin zu Data Mining und Compliance-Workflows. Dieser Leitfaden behandelt alles, was Sie benötigen, um Text aus PDFs in Python, JavaScript und Rust mit PDF Oxide zu extrahieren — von einfacher Textextraktion über zeichengenaue Positionierung und stilisierte Spans bis zu OCR für gescannte Dokumente, der Handhabung verschlüsselter Dateien und Performance-Tuning für Batch-Pipelines.
Text aus jedem PDF in drei Zeilen extrahieren:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("document.pdf")
text = doc.extract_text(0) # page 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); // page 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) // page 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); // page 0
Console.WriteLine(text);
Java
import fyi.oxide.pdf.PdfDocument;
import java.nio.file.Path;
try (PdfDocument doc = PdfDocument.open(Path.of("document.pdf"))) {
String text = doc.extractText(0); // page 0
System.out.println(text);
}
Kotlin
import fyi.oxide.pdf.PdfDocument
import java.nio.file.Path
PdfDocument.open(Path.of("document.pdf")).use { doc ->
val text = doc.extractText(0) // page 0
println(text)
}
Scala
import fyi.oxide.pdf.PdfDocument
import scala.util.Using
Using.resource(PdfDocument.open("document.pdf")) { doc =>
val text = doc.extractText(0) // page 0
println(text)
}
Clojure
(require '[pdf-oxide.core :as pdf])
(with-open [doc (pdf/open "document.pdf")]
(println (pdf/extract-text doc 0))) ; page 0
PHP
use PdfOxide\PdfDocument;
$doc = PdfDocument::open('document.pdf');
$text = $doc->extractText(0); // page 0
echo $text;
$doc->close();
Ruby
require 'pdf_oxide'
PdfOxide::PdfDocument.open('document.pdf') do |doc|
text = doc.extract_text(0) # page 0
puts text
end
C++
#include <pdf_oxide/pdf_oxide.hpp>
#include <iostream>
auto doc = pdf_oxide::Document::open("document.pdf");
auto text = doc.extract_text(0); // page 0
std::cout << text << '\n';
Swift
import PdfOxide
let doc = try Document.open("document.pdf")
let text = try doc.extractText(0) // page 0
print(text)
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('document.pdf');
final text = doc.extractText(0); // page 0
print(text);
doc.close();
R
library(pdfoxide)
doc <- pdf_open("document.pdf")
text <- pdf_extract_text(doc, 0) # page 0
cat(text)
Julia
using PdfOxide
doc = open_document("document.pdf")
text = extract_text(doc, 0) # page 0
println(text)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("document.pdf");
const text = try doc.extractText(a, 0); // page 0
std.debug.print("{s}\n", .{text});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"document.pdf" error:&err];
NSString *text = [doc extractText:0 error:&err]; // page 0
NSLog(@"%@", text);
Elixir
{:ok, doc} = PdfOxide.open("document.pdf")
{:ok, text} = PdfOxide.extract_text(doc, 0) # page 0
IO.puts(text)
PDF Oxide extrahiert Text mit durchschnittlich 0,8 ms pro Seite — 5-mal schneller als PyMuPDF, 15-mal schneller als pypdf — mit einer Erfolgsquote von 100 % bei 3.830 Test-PDFs.
Warum PDF-Textextraktion schwierig ist
PDF ist ein visuelles Format, kein Textformat. Anders als HTML oder Markdown speichert eine PDF-Datei keine „Absätze" oder „Sätze" — sie speichert einzelne Zeichen, die an bestimmten Koordinaten auf einer Seite positioniert sind. Um lesbaren Text zu extrahieren, sind folgende Schritte erforderlich:
- Schrift-Dekodierung — PDF-Schriften ordnen Zeichencodes mithilfe von Kodierungstabellen (WinAnsi, MacRoman, Unicode CMap, Type 1, TrueType, CIDFont) Glyphen zu. Ein Zeichencode von
0x41kann in einer Schrift „A" und in einer anderen „α" bedeuten. - Textstrom-Parsing — Textoperatoren wie
Tj,TJ,',"platzieren Zeichen auf der Seite. Kerning-Anpassungen inTJ-Arrays verschieben Zeichen um Bruchteile eines Punktes. Fehlende Leerzeichen müssen aus den Abständen zwischen Zeichenpositionen erschlossen werden. - Layout-Rekonstruktion — 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-Kodierung mit Tausenden von Glyphen. Arabisch und Hebräisch erfordern eine Umkehrung von rechts nach links. Ligaturen (fi, fl, ffi) müssen zerlegt werden.
- Eingebettete Teilmengen — Viele PDFs betten nur die verwendeten Glyphen mit benutzerdefinierten Kodierungsvektoren ein. Eine Schrift kann Glyphen-Index 1→„T", 2→„h", 3→„e" ohne Standardkodierung zuordnen.
Das ist der Grund, warum verschiedene PDF-Bibliotheken für dieselbe Datei unterschiedlichen Text ausgeben — und warum manche bei komplexen Dokumenten vollständig versagen. PDF Oxide behandelt all diese Fälle mit einem Rust-basierten Parser, der an 3.830 realen PDFs mit einer Erfolgsquote von 100 % getestet wurde.
Installation
Python (PyPI):
pip install pdf_oxide
Vorgefertigte Wheels für Linux (x86_64, aarch64), macOS (Intel und Apple Silicon) und Windows (x86_64). Python 3.8+. Keine Systemabhängigkeiten — der Rust-Kern ist in das Wheel kompiliert, sodass Poppler, MuPDF oder C-Bibliotheken nicht installiert werden müssen.
JavaScript (npm):
npm install pdf-oxide-wasm
Läuft in Node.js 18+ und modernen Browsern. Das WASM-Binary ist im Paket enthalten.
Rust (Cargo):
cargo add pdf_oxide
Erfordert Rust 1.70+. Keine Systemabhängigkeiten außer einem Standard-Rust-Toolchain.
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));
Java
try (PdfDocument doc = PdfDocument.open(Path.of("report.pdf"))) {
StringBuilder all = new StringBuilder();
for (int i = 0; i < doc.pageCount(); i++)
all.append(doc.extractText(i));
System.out.println(all);
}
Kotlin
PdfDocument.open(Path.of("report.pdf")).use { doc ->
val all = (0 until doc.pageCount()).joinToString("") { doc.extractText(it) }
println(all)
}
Scala
Using.resource(PdfDocument.open("report.pdf")) { doc =>
val all = (0 until doc.pageCount()).map(doc.extractText).mkString
println(all)
}
Clojure
(with-open [doc (pdf/open "report.pdf")]
(println (apply str (map #(pdf/extract-text doc %)
(range (pdf/page-count doc))))))
PHP
$doc = PdfDocument::open('report.pdf');
$all = '';
for ($i = 0; $i < $doc->pageCount(); $i++) { $all .= $doc->extractText($i); }
echo $all;
$doc->close();
Ruby
PdfOxide::PdfDocument.open('report.pdf') do |doc|
all = (0...doc.page_count).map { |i| doc.extract_text(i) }.join
puts all
end
C++
auto doc = pdf_oxide::Document::open("report.pdf");
auto all = doc.extract_all_text();
std::cout << all << '\n';
Swift
let doc = try Document.open("report.pdf")
let all = try doc.extractAllText()
print(all)
Dart
final doc = PdfDocument.open('report.pdf');
final all = doc.extractAllText();
print(all);
doc.close();
R
doc <- pdf_open("report.pdf")
all <- pdf_extract_all_text(doc)
cat(all)
Julia
doc = open_document("report.pdf")
all = extract_all_text(doc)
println(all)
Zig
var doc = try pdf_oxide.Document.open("report.pdf");
const all = try doc.extractAllText(a);
std.debug.print("{s}\n", .{all});
Objective-C
POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
NSString *all = [doc extractAllTextWithError:&err];
NSLog(@"%@", all);
Elixir
{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, n} = PdfOxide.page_count(doc)
all = 0..(n - 1)
|> Enum.map(fn i -> {:ok, t} = PdfOxide.extract_text(doc, i); t end)
|> Enum.join()
IO.puts(all)
Text mit Zeichenpositionen extrahieren
Genaue Koordinaten, Schriftnamen und -größen für jedes Zeichen abrufen:
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}");
Java
import fyi.oxide.pdf.text.TextChar;
try (PdfDocument doc = PdfDocument.open(Path.of("paper.pdf"))) {
for (TextChar ch : doc.page(0).chars().subList(0, 20)) {
System.out.printf("'%s' at (%.1f, %.1f)%n",
ch.asString(), ch.bbox().x0(), ch.bbox().y0());
}
}
Kotlin
PdfDocument.open(Path.of("paper.pdf")).use { doc ->
doc.page(0).chars().take(20).forEach { ch ->
println("'${ch.asString()}' at (${ch.bbox().x0()}, ${ch.bbox().y0()})")
}
}
Scala
import fyi.oxide.pdf.charsSeq
Using.resource(PdfDocument.open("paper.pdf")) { doc =>
doc.page(0).charsSeq.take(20).foreach { ch =>
println(f"'${ch.asString}' at (${ch.bbox.x0}%.1f, ${ch.bbox.y0}%.1f)")
}
}
Clojure
(with-open [doc (pdf/open "paper.pdf")]
(doseq [ch (take 20 (pdf/chars (pdf/page doc 0)))]
(let [b (.bbox ch)]
(println (format "'%s' at (%.1f, %.1f)"
(.asString ch) (.x0 b) (.y0 b))))))
C++
auto doc = pdf_oxide::Document::open("paper.pdf");
auto chars = doc.extract_chars(0);
int shown = 0;
for (const auto& ch : chars) {
if (shown++ >= 20) break;
std::printf("U+%04X at (%.1f, %.1f) font=%s size=%.1f\n",
ch.character, ch.bbox.x, ch.bbox.y,
ch.font_name.c_str(), ch.font_size);
}
Swift
let doc = try Document.open("paper.pdf")
let chars = try doc.extractChars(0)
for ch in chars.prefix(20) {
let s = String(UnicodeScalar(ch.character) ?? " ")
print("'\(s)' at (\(ch.bbox.x), \(ch.bbox.y)) font=\(ch.fontName) size=\(ch.fontSize)")
}
Dart
final doc = PdfDocument.open('paper.pdf');
final chars = doc.extractChars(0);
for (final ch in chars.take(20)) {
final s = String.fromCharCode(ch.character);
print("'$s' at (${ch.bbox.x}, ${ch.bbox.y}) "
"font=${ch.fontName} size=${ch.fontSize}");
}
doc.close();
R
doc <- pdf_open("paper.pdf")
chars <- pdf_extract_chars(doc, 0)
for (ch in head(chars, 20)) {
cat(sprintf("'%s' at (%.1f, %.1f) font=%s size=%.1f\n",
intToUtf8(ch$character), ch$bbox$x, ch$bbox$y,
ch$font_name, ch$font_size))
}
Julia
doc = open_document("paper.pdf")
chars = extract_chars(doc, 0)
for ch in chars[1:min(20, end)]
println("'$(Char(ch.character))' at ($(ch.bbox.x), $(ch.bbox.y)) ",
"font=$(ch.font_name) size=$(ch.font_size)")
end
Zig
var doc = try pdf_oxide.Document.open("paper.pdf");
const chars = try doc.extractChars(a, 0);
defer pdf_oxide.Document.freeChars(a, chars);
for (chars[0..@min(20, chars.len)]) |ch| {
std.debug.print("U+{X:0>4} at ({d:.1}, {d:.1}) font={s} size={d:.1}\n",
.{ ch.character, ch.bbox.x, ch.bbox.y, ch.fontName, ch.fontSize });
}
Objective-C
POXDocument *doc = [POXDocument openPath:@"paper.pdf" error:&err];
NSArray<POXChar*> *chars = [doc extractChars:0 error:&err];
for (POXChar *ch in [chars subarrayWithRange:NSMakeRange(0, MIN(20, chars.count))]) {
NSLog(@"U+%04X at (%.1f, %.1f) font=%@ size=%.1f",
ch.character, ch.bbox.x, ch.bbox.y, ch.fontName, ch.fontSize);
}
Elixir
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, chars} = PdfOxide.extract_chars(doc, 0)
chars
|> Enum.take(20)
|> Enum.each(fn ch ->
IO.puts("'#{<<ch.character::utf8>>}' at (#{ch.bbox.x}, #{ch.bbox.y}) " <>
"font=#{ch.font_name} size=#{ch.font_size}")
end)
Jedes Zeichen enthält:
| 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 Extraktion auf Zeichenebene ist nützlich für die Rekonstruktion von Tabellen, die Erkennung von Überschriften anhand der Schriftgröße oder die Erstellung von Begrenzungsrahmen um Textbereiche. Beispielsweise können Zeichen anhand ihrer y-Koordinate in Zeilen gruppiert und Spaltengrenzen anhand von Lücken in den x-Positionen erkannt werden.
Stilisierte Text-Spans extrahieren
Aufeinanderfolgende Zeichen nach Schrift und Größe gruppieren:
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);
}
Nützlich zur Erkennung von Überschriften, Fettdruck oder für die Erstellung strukturierter Ausgaben.
Stapelverarbeitung
Hunderte oder Tausende von PDFs verarbeiten:
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)
# Process text...
except PdfError as e:
print(f"Skipped {pdf_path.name}: {e}")
Bei 0,8 ms pro Seite dauert die Verarbeitung von 3.830 PDFs etwa 3,1 Sekunden. Für Produktionspipelines finden Sie im Leitfaden zur Stapelverarbeitung Muster für parallele Verarbeitung mit Multiprocessing und Async I/O.
Gescannte PDFs verarbeiten (OCR)
Enthält ein PDF gescannte Bilder statt Text, gibt extract_text() leere oder minimale Ausgabe zurück. Verwenden Sie das integrierte OCR von PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("scanned.pdf")
text = doc.extract_text(0)
if not text.strip():
# Page is likely scanned — use OCR
text = doc.extract_text_ocr(0)
print(text)
PDF Oxide verwendet PaddleOCR über ONNX Runtime — keine Tesseract-Installation erforderlich. Zur Modellauswahl und Konfiguration siehe 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));
Java
try (PdfDocument doc = PdfDocument.open("protected.pdf", "secret")) {
System.out.println(doc.extractText(0));
}
Kotlin
PdfDocument.open("protected.pdf", "secret").use { doc ->
println(doc.extractText(0))
}
Scala
Using.resource(PdfDocument.open("protected.pdf", "secret")) { doc =>
println(doc.extractText(0))
}
Clojure
(with-open [doc (pdf/open "protected.pdf" "secret")]
(println (pdf/extract-text doc 0)))
Ruby
PdfOxide::PdfDocument.open('protected.pdf', password: 'secret') do |doc|
puts doc.extract_text(0)
end
C++
auto doc = pdf_oxide::Document::open_with_password("protected.pdf", "secret");
std::cout << doc.extract_text(0) << '\n';
Swift
let doc = try Document.openWithPassword("protected.pdf", password: "secret")
print(try doc.extractText(0))
Dart
final doc = PdfDocument.openWithPassword('protected.pdf', 'secret');
print(doc.extractText(0));
doc.close();
R
doc <- pdf_open_with_password("protected.pdf", "secret")
cat(pdf_extract_text(doc, 0))
Julia
doc = open_with_password("protected.pdf", "secret")
println(extract_text(doc, 0))
Zig
var doc = try pdf_oxide.Document.openWithPassword("protected.pdf", "secret");
const text = try doc.extractText(a, 0);
std.debug.print("{s}\n", .{text});
Objective-C
POXDocument *doc = [POXDocument openWithPassword:@"protected.pdf"
password:@"secret" error:&err];
NSLog(@"%@", [doc extractText:0 error:&err]);
Elixir
{:ok, doc} = PdfOxide.open_with_password("protected.pdf", "secret")
{:ok, text} = PdfOxide.extract_text(doc, 0)
IO.puts(text)
Unterstützt mit AES-256, AES-128 und RC4 verschlüsselte PDFs. Anders als pdfplumber (das verschlüsselte Dateien gar nicht öffnen kann) und pdfminer (das bei AES-256 versagt), verarbeitet PDF Oxide alle Standard-PDF-Verschlüsselungsmethoden transparent.
Als Markdown ausgeben
Für strukturierte Ausgabe mit Überschriften und Formatierung:
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));
Java
try (PdfDocument doc = PdfDocument.open(Path.of("paper.pdf"))) {
System.out.println(doc.toMarkdown(0));
}
Kotlin
PdfDocument.open(Path.of("paper.pdf")).use { doc ->
println(doc.toMarkdown(0))
}
Scala
Using.resource(PdfDocument.open("paper.pdf")) { doc =>
println(doc.toMarkdown(0))
}
Clojure
(with-open [doc (pdf/open "paper.pdf")]
(println (pdf/to-markdown doc 0)))
PHP
$doc = PdfDocument::open('paper.pdf');
echo $doc->toMarkdown(0);
$doc->close();
Ruby
PdfOxide::PdfDocument.open('paper.pdf') do |doc|
puts doc.to_markdown(0)
end
C++
auto doc = pdf_oxide::Document::open("paper.pdf");
std::cout << doc.to_markdown(0) << '\n';
Swift
let doc = try Document.open("paper.pdf")
print(try doc.toMarkdown(0))
Dart
final doc = PdfDocument.open('paper.pdf');
print(doc.toMarkdown(0));
doc.close();
R
doc <- pdf_open("paper.pdf")
cat(pdf_to_markdown(doc, 0))
Julia
doc = open_document("paper.pdf")
println(to_markdown(doc, 0))
Zig
var doc = try pdf_oxide.Document.open("paper.pdf");
const md = try doc.toMarkdown(a, 0);
std.debug.print("{s}\n", .{md});
Objective-C
POXDocument *doc = [POXDocument openPath:@"paper.pdf" error:&err];
NSLog(@"%@", [doc toMarkdown:0 error:&err]);
Elixir
{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, md} = PdfOxide.to_markdown(doc, 0)
IO.puts(md)
Integrationsmuster für RAG und LLM finden Sie im PDF-zu-Markdown-Leitfaden.
In PDFs suchen
Text über alle Seiten hinweg mit Positionsdaten finden:
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})");
Java
import fyi.oxide.pdf.search.SearchMatch;
try (PdfDocument doc = PdfDocument.open(Path.of("manual.pdf"))) {
for (SearchMatch m : doc.search("configuration")) {
System.out.printf("Page %d: '%s' at (%.0f, %.0f)%n",
m.pageIndex(), m.text(), m.bbox().x0(), m.bbox().y0());
}
}
Kotlin
PdfDocument.open(Path.of("manual.pdf")).use { doc ->
for (m in doc.search("configuration")) {
println("Page ${m.pageIndex()}: '${m.text()}' at (${m.bbox().x0()}, ${m.bbox().y0()})")
}
}
Scala
import fyi.oxide.pdf.searchSeq
Using.resource(PdfDocument.open("manual.pdf")) { doc =>
for (m <- doc.searchSeq("configuration"))
println(f"Page ${m.pageIndex}: '${m.text}' at (${m.bbox.x0}%.0f, ${m.bbox.y0}%.0f)")
}
Clojure
(with-open [doc (pdf/open "manual.pdf")]
(doseq [m (pdf/search doc "configuration")]
(let [b (.bbox m)]
(println (format "Page %d: '%s' at (%.0f, %.0f)"
(.pageIndex m) (.text m) (.x0 b) (.y0 b))))))
Ruby
PdfOxide::PdfDocument.open('manual.pdf') do |doc|
doc.search('configuration').each do |r|
puts "Page #{r[:page]}: '#{r[:text]}' at (#{r[:bbox][:x].round}, #{r[:bbox][:y].round})"
end
end
C++
auto doc = pdf_oxide::Document::open("manual.pdf");
for (const auto& r : doc.search_all("configuration", /*case_sensitive=*/false)) {
std::printf("Page %d: '%s' at (%.0f, %.0f)\n",
r.page, r.text.c_str(), r.bbox.x, r.bbox.y);
}
Swift
let doc = try Document.open("manual.pdf")
for r in try doc.searchAll("configuration", false) {
print("Page \(r.page): '\(r.text)' at (\(r.bbox.x), \(r.bbox.y))")
}
Dart
final doc = PdfDocument.open('manual.pdf');
for (final r in doc.searchAll('configuration', false)) {
print("Page ${r.page}: '${r.text}' at (${r.bbox.x}, ${r.bbox.y})");
}
doc.close();
R
doc <- pdf_open("manual.pdf")
for (r in pdf_search_all(doc, "configuration", case_sensitive = FALSE)) {
cat(sprintf("Page %d: '%s' at (%.0f, %.0f)\n",
r$page, r$text, r$bbox$x, r$bbox$y))
}
Julia
doc = open_document("manual.pdf")
for r in search_all(doc, "configuration", false)
println("Page $(r.page): '$(r.text)' at ($(r.bbox.x), $(r.bbox.y))")
end
Zig
var doc = try pdf_oxide.Document.open("manual.pdf");
const hits = try doc.searchAll(a, "configuration", false);
defer pdf_oxide.Document.freeSearchResults(a, hits);
for (hits) |r| {
std.debug.print("Page {d}: '{s}' at ({d:.0}, {d:.0})\n",
.{ r.page, r.text, r.bbox.x, r.bbox.y });
}
Objective-C
POXDocument *doc = [POXDocument openPath:@"manual.pdf" error:&err];
for (POXSearchResult *r in [doc searchAll:@"configuration" caseSensitive:NO error:&err]) {
NSLog(@"Page %ld: '%@' at (%.0f, %.0f)",
(long)r.page, r.text, r.bbox.x, r.bbox.y);
}
Elixir
{:ok, doc} = PdfOxide.open("manual.pdf")
{:ok, hits} = PdfOxide.search_all(doc, "configuration", false)
Enum.each(hits, fn r ->
IO.puts("Page #{r.page}: '#{r.text}' at (#{r.bbox.x}, #{r.bbox.y})")
end)
Vergleich mit anderen Python-PDF-Bibliotheken
Es gibt mehrere Python-Bibliotheken zur PDF-Textextraktion. So schneiden sie im Vergleich ab:
- pypdf — Reines Python, keine C-Abhängigkeiten. Einfach zu installieren, aber langsam (12 ms pro Seite) und schlägt bei 1,6 % der PDFs aufgrund begrenzter Schrift- und Kodierungsunterstützung fehl. Keine Zeichenpositionsdaten. Geeignet für einfache PDFs, bei denen Geschwindigkeit keine Rolle spielt.
- pdfplumber — Basiert auf pdfminer und bietet detaillierte Zeichen- und Tabellenextraktion. Sehr langsam (23 ms pro Seite) und kann keine verschlüsselten PDFs öffnen. Am besten geeignet für Tabellenextraktion, wenn Sie Daten auf Zellebene benötigen und keine Leistungsanforderungen haben.
- PyMuPDF (fitz) — Python-Bindings für die MuPDF-C-Bibliothek. Schnell (4,6 ms pro Seite) und zuverlässig (99,3 % Erfolgsquote). Erfordert eine C-Bibliotheksinstallation und hat eine AGPL-Lizenz. Eine solide Wahl, wenn die Lizenz für Ihr Projekt passt.
- pypdfium2 — Python-Bindings für Googles PDFium-Engine. Schnell (4,1 ms pro Seite), aber die p99-Latenz ist bei komplexen Dokumenten hoch (42 ms). Eingeschränktere API-Oberfläche als PyMuPDF.
- pdfminer.six — Reines Python mit detaillierter Layout-Analyse. Sehr langsam und nicht gewartet. Schlägt bei AES-256-verschlüsselten PDFs fehl. Wurde weitgehend durch pdfplumber ersetzt.
- PDF Oxide — Rust-basiert mit Python-Bindings über PyO3. Schnellste Option (0,8 ms pro Seite), 100 % Erfolgsquote, unterstützt alle Verschlüsselungsmethoden, enthält integriertes OCR. MIT-Lizenz ohne Systemabhängigkeiten.
PDF Oxide wurde speziell entwickelt, um die Lücken in bestehenden Bibliotheken zu schließen: die Geschwindigkeitsbeschränkungen reiner Python-Parser, die Lizenzbeschränkungen von MuPDF und die Zuverlässigkeitsprobleme, die dazu führen, dass Bibliotheken bei realen PDFs mit ungewöhnlichen Schriften, beschädigten Querverweistabellen oder nicht standardmäßigen Kodierungen versagen.
Leistung: Wie schnell ist PDF Oxide?
Getestet an 3.830 PDFs aus drei unabhängigen öffentlichen Test-Suites:
| Bibliothek | Mittelwert | 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, die 10.000 PDFs verarbeitet:
- PDF Oxide: 8 Sekunden
- PyMuPDF: 46 Sekunden
- pypdf: 2 Minuten
- pdfplumber: 3,9 Minuten
Methodik und Reproduktionsschritte finden Sie in den vollständigen Benchmarks.
Häufige Probleme und Fehlerbehebung
Leere Textausgabe
Wenn extract_text() einen leeren String zurückgibt, enthält die Seite wahrscheinlich gescannte Bilder anstatt Text. Verwenden Sie stattdessen extract_text_ocr(). Einrichtungsanweisungen finden Sie unter OCR für gescannte PDFs.
Verstümmelte oder falsche Zeichen
Dies deutet normalerweise auf eine Schrift mit nicht standardmäßigem Kodierungsvektor oder einen fehlenden ToUnicode CMap hin. PDF Oxide behandelt die meisten Kodierungs-Sonderfälle, aber absichtlich verschleierte PDFs (DRM-geschützte Inhalte) können zu falscher Ausgabe führen.
Fehlende Leerzeichen oder zusammengefügte Wörter
PDF-Textoperatoren platzieren Zeichen einzeln. Die Leerzeicheninferenz hängt vom Abstand zwischen Zeichenpositionen im Verhältnis zur Leerzeichenbreite der Schrift ab. Wenn Wörter zusammengeführt erscheinen, verwenden Sie extract_chars() und wenden Sie benutzerdefinierte Abstandslogik basierend auf Zeichenpositionen an.
Andere Ausgabe als bei anderen Bibliotheken
Verschiedene Bibliotheken verwenden unterschiedliche Heuristiken für Leerzeicheninferenz, Zeilenumbruch und Leserichtung. PDF Oxide erreicht 99,5 % Textparität mit PyMuPDF über 3.830 PDFs. Der Unterschied von 0,5 % liegt bei der Leerraumnnormalisierung und der Ligaturbehebung.
Praxisnahe Anwendungsfälle
Suchindizierung — Extrahieren Sie Text von jeder Seite jedes PDFs in einem Dokumentenrepository und speisen Sie den Text in Elasticsearch, Typesense oder eine Vektordatenbank für die Volltextsuche ein. Die Geschwindigkeit von PDF Oxide macht es praktikabel, Tausende von Dokumenten auf Abruf neu zu indizieren.
RAG-Pipelines (Retrieval-Augmented Generation) — Extrahieren und chunken Sie PDF-Text für Embeddings mit OpenAI, Cohere oder Open-Source-Modellen. Verwenden Sie extract_spans(), um die Überschriftenstruktur zu erhalten, damit Chunks mit Dokumentabschnitten übereinstimmen. Für LLM-optimierte Ausgabe siehe den PDF-zu-Markdown-Leitfaden.
Compliance und Audit — Durchsuchen Sie Verträge, Rechnungen und regulatorische Einreichungen nach bestimmten Klauseln oder Schlüsselwörtern. Verwenden Sie doc.search(), um Begriffe auf allen Seiten mit genauen Positionen zu finden, oder extrahieren Sie den Volltext für NLP-basierte Klauselerkennung.
Datenextraktion — Extrahieren Sie strukturierte Daten aus Rechnungen, Quittungen, Kontoauszügen und Formularen. Kombinieren Sie extract_chars() für Positionsdaten mit domänenspezifischen Regeln, um Felder wie „Gesamtbetrag" oder „Rechnungsdatum" zu finden und angrenzende Werte zu extrahieren.
Akademische Forschung — Verarbeiten Sie Tausende von Forschungsarbeiten für Literaturrecherche, Zitationsextraktion oder Meta-Analysen. PDF Oxide unterstützt die gesamte Bandbreite an PDF-Erzeugern (LaTeX, Word, InDesign, Quark) und Schriftkodierungen, die in akademischen Publikationen vorkommen.
Verwandte Seiten
- Textextraktions-API — vollständige API-Referenz
- PDF zu Markdown — strukturierte Konvertierung
- Stapelverarbeitung — Muster für parallele Verarbeitung
- OCR für gescannte PDFs — OCR-Einrichtung und -Nutzung
- Leistungs-Benchmarks — Benchmark-Methodik und Ergebnisse