Skip to content

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 0x41 kann in einer Schrift „A" und in einer anderen „α" bedeuten.
  • Textstrom-Parsing — Textoperatoren wie Tj, TJ, ', " platzieren Zeichen auf der Seite. Kerning-Anpassungen in TJ-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