Tabellen aus PDF extrahieren in Python
Das Extrahieren von Tabellen aus PDF-Dokumenten gehört zu den häufigsten Aufgaben in Pipelines für die Dokumentenverarbeitung. Ob Sie Kennzahlen aus Geschäftsberichten ziehen, Produktkataloge aufbereiten oder strukturierte Daten in ein LLM einspeisen — eine verlässliche Tabellenerkennung ist unverzichtbar. Dieser Leitfaden zeigt alles, was Sie dafür in Python brauchen: vom kurzen Einzeiler bis zu produktionsreifen Abläufen für mehrseitige Tabellen.
Erkennungs-Engine
PDF Oxide verwendet die universelle Pipeline Kanten → Snap/Merge → Schnittpunkte → Zellen → Gruppen — denselben Ansatz wie Tabula, pdfplumber oder PyMuPDF, jedoch in reinem Rust implementiert.
Funktionen der Erkennung:
- Schnittpunktbasiert — findet H×V-Linienkreuzungen, baut Zellen aus den vier Eckpunkten und gruppiert sie per Union-Find zu Tabellen.
- Erweitertes Gitter — liegen horizontale und vertikale Linien in unterschiedlichen Seitenbereichen, entsteht ein virtuelles Gitter aus dem kartesischen Produkt aller Koordinaten.
- Spaltenbewusste Texterkennung — zerlegt zweispaltige Layouts per X-Projektionshistogramm und führt dann pro Spalte eine reine Texttabellen-Erkennung durch.
- Texttabellen mit H-Rule-Rahmen — erkennt Tabellen, die nur durch horizontale Linien begrenzt sind (typisch für wissenschaftliche Arbeiten).
- Hybride Zeilenerkennung — leitet Zeilengrenzen aus den Y-Positionen des Textes ab, wenn nur vertikale Ränder vorhanden sind (Rechnungsposten).
- Wiederaufbau gepunkteter / gestrichelter Linien — fügt kurze Liniensegmente zu durchgehenden Kanten zusammen.
- Abschnittstrennung — teilt mehrteilige Formulare an seitenbreiten horizontalen Trennlinien auf.
- Filterung nach Kantenabdeckung — entfernt verwaiste Kanten, die an keinem möglichen Gitter beteiligt sind.
Konfiguration
TableDetectionConfig stellt einstellbare Parameter bereit:
| Feld | Standard | Beschreibung |
|---|---|---|
horizontal_strategy |
"lines_strict" |
"lines_strict", "lines", "text" oder "explicit" |
vertical_strategy |
"lines_strict" |
Dieselben Werte |
v_split_gap |
20.0 pt |
Abstand zwischen vertikalen Linien, ab dem in getrennte Tabellen aufgespalten wird (vor v0.3.20 fest auf 4 pt) |
snap_tolerance |
3.0 pt |
Toleranz beim Zusammenführen benachbarter Kanten |
text_tolerance |
3.0 pt |
Toleranz beim Zusammenführen von Textzeilen |
Verhaltensänderung
Ab v0.3.20 ist die Standardstrategie für extract_tables() in Python Both (erkannt wird über Linien und Text gleichzeitig). Seiten, die sich auf das frühere Text-Only-Verhalten verlassen haben, sollten horizontal_strategy="text" und vertical_strategy="text" explizit setzen.
Die Python-Anbindung liest vertical_strategy aus dem Dict table_settings nun korrekt aus — zuvor wurde der Wert stillschweigend ignoriert.
Darstellung
Extrahierte Tabellen werden mit durch Leerzeichen aufgefüllten Spalten ausgegeben (anstelle der ASCII-Rahmenzeichen früherer Versionen). Währungs- und Zahlenspalten werden automatisch rechtsbündig gesetzt. Formular-Nummernpräfixe ("1 Apr 11" → "Apr 11") und dekorative Bindestrich-/Unterstrich-Zellen ("------") werden beim Rendern entfernt.
Tabellendaten aus einem PDF über die Markdown-Konvertierung extrahieren:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)
# Die Ausgabe enthält Tabellen im GFM-Format:
# | Item | Qty | Price |
# |------|-----|-------|
# | Widget | 10 | $9.99 |
WASM
import { WasmPdfDocument } from "pdf-oxide-wasm";
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
// Die Ausgabe enthält Tabellen im GFM-Format:
// | Item | Qty | Price |
// |------|-----|-------|
// | Widget | 10 | $9.99 |
doc.free();
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("invoice.pdf")?;
let md = doc.to_markdown(0, true)?;
println!("{}", md);
Go
package main
import (
"fmt"
"log"
pdfoxide "github.com/yfedoseev/pdf_oxide/go"
)
func main() {
doc, err := pdfoxide.Open("invoice.pdf")
if err != nil { log.Fatal(err) }
defer doc.Close()
md, err := doc.ToMarkdown(0)
if err != nil { log.Fatal(err) }
fmt.Println(md)
}
C#
using PdfOxide;
using var doc = PdfDocument.Open("invoice.pdf");
Console.WriteLine(doc.ToMarkdown(0));
PDF Oxide erkennt tabellarische Layouts durch räumliche Analyse ausgerichteter Textblöcke und gibt Tabellen als GitHub Flavored Markdown aus.
Warum Tabellenextraktion aus PDFs schwierig ist
Wer einmal versucht hat, eine Tabelle aus einem PDF zu kopieren und in eine Tabellenkalkulation einzufügen, kennt das Ergebnis: meistens ein Chaos. Das liegt nicht am PDF-Viewer, sondern an einer grundlegenden Eigenschaft des PDF-Formats selbst.
PDFs kennen kein Konzept von „Tabelle". Anders als HTML, das mit <table>, <tr> und <td> eine tabellarische Struktur definiert, enthält eine PDF-Datei nur Zeichenanweisungen: Glyph an Koordinate (x, y) setzen, Linie von A nach B ziehen. Es gibt keine semantische Ebene, die sagt, dass „diese Zeichen zur Zelle in Zeile 3, Spalte 2 gehören". Jede Extraktionsbibliothek muss diese Struktur rekonstruieren, indem sie die räumliche Anordnung von Text und Linien analysiert.
Diese Rekonstruktion ist aus mehreren Gründen anspruchsvoll:
-
Tabellen mit und ohne Rahmen. Sichtbare Gitterlinien erlauben es, Zellgrenzen direkt aus ihnen abzuleiten. Rahmenlose Tabellen — häufig in Bilanzen, Behördenberichten und wissenschaftlichen Arbeiten — haben gar keine Linien. Die Bibliothek muss Spaltengrenzen rein aus Leerraumlücken zwischen Textblöcken erschließen, was bei variablen Spaltenbreiten oder rechtsbündigen Zahlen leicht fehlschlägt.
-
Verbundene Zellen und spaltenübergreifende Überschriften. Eine Kopfzelle, die sich über drei Spalten erstreckt, wirkt wie ein einzelner breiter Textblock. Ohne Gitterlinien hat ein Parser keinen verlässlichen Weg, um zu erkennen, welche Spalten von der Überschrift abgedeckt werden. Manche Bibliotheken meistern das sauber, viele produzieren stillschweigend unbrauchbare Ausgaben.
-
Mehrzeiliger Zelleninhalt. Enthält eine Zelle einen Absatz, der über mehrere Zeilen umbricht, behandeln naive zeilenbasierte Parser jede Umbruchzeile als eigene Tabellenzeile. Um sie korrekt wieder zu einer Zelle zusammenzuführen, muss die vertikale Ausdehnung jeder Zeile verstanden werden.
-
Mehrseitige Tabellen. Große Tabellen erstrecken sich oft über zwei oder mehr Seiten. Die Kopfzeile kann auf jeder Seite wiederholt werden oder auch nicht, und zwischen den Zeilen können Fußzeilen, Wasserzeichen oder Seitenzahlen auftauchen. Diese Fragmente wieder zu einer kohärenten Tabelle zusammenzufügen, verlangt seitenbewusste Logik.
-
Gedrehter Text und ungewöhnliche Layouts. Manche PDFs drehen Spaltenüberschriften oder platzieren Tabellen in mehrspaltigen Seitenlayouts. Solche Sonderfälle durchbrechen die Annahmen, die die meisten Parser über die Leserichtung links-nach-rechts, oben-nach-unten treffen.
Wer diese Hürden kennt, wählt leichter das passende Werkzeug für die eigenen Dokumente. Für klare, ausgerichtete Tabellen — die Mehrzahl von Rechnungen, Auftragsbestätigungen und einfachen Berichten — funktioniert ein schneller räumlicher Ansatz wie der von PDF Oxide bestens. Bei Dokumenten mit komplex verbundenen Zellen, rahmenlosen Layouts oder ausgefallener Formatierung kann eine Bibliothek mit aufwendigeren Heuristiken die bessere Wahl sein.
Tabellenextraktion: PDF Oxide im Vergleich mit anderen Bibliotheken
Welche Bibliothek für die PDF-Tabellenextraktion in Python passt, hängt von den Dokumenten, den Laufzeitanforderungen und der gewünschten Ausgabeform ab. So schneiden die großen Optionen ab:
| Bibliothek | Tabellenerkennung | Tabellen mit Rahmen | Rahmenlose Tabellen | Ausgabeformat | Geschwindigkeit |
|---|---|---|---|---|---|
| PDF Oxide | Eingebaut | Ja | Einfach | Markdown/HTML | 0,8 ms |
| pdfplumber | Eingebaut | Ja | Fortgeschritten | Python-Listen | 23,2 ms |
| Camelot | Eingebaut | Ja | Ja (Lattice/Stream) | DataFrames | ~50 ms+ |
| PyMuPDF | Einfach (ab v1.23) | Ja | Begrenzt | DataFrames | 4,6 ms |
| pypdf | Nein | Nein | Nein | — | — |
| tabula-py | Eingebaut | Ja | Ja | DataFrames | ~100 ms+ (Java) |
PDF Oxide ist mit Abstand die schnellste Option. Tabellen werden über die räumliche Analyse ausgerichteter Textblöcke erkannt und als saubere GitHub-Flavored-Markdown-Tabellen ausgegeben. Mit 0,8 ms mittlerer Extraktionszeit ist es 29-mal schneller als pdfplumber und über 100-mal schneller als tabula-py. Tabellen mit Rahmen und einfache ausgerichtete rahmenlose Tabellen werden zuverlässig verarbeitet. In LLM-Pipelines, die ohnehin Markdown benötigen, ist es die naheliegende Wahl.
pdfplumber hat die reifste Erkennung rahmenloser Tabellen. find_tables() bietet konfigurierbare Strategien für Zeilen und Spalten anhand der Textausrichtung und kommt mit verbundenen Zellen und mehrzeiligen Inhalten besser zurecht als die meisten Alternativen. Der Preis ist die Geschwindigkeit: 23,2 ms pro Seite bremsen Batch-Läufe spürbar aus.
Camelot bietet zwei Modi — Lattice für Tabellen mit Rahmen und Stream für rahmenlose. Die Ausgabe erfolgt direkt als pandas-DataFrame, was für Analyse-Workflows praktisch ist. Allerdings sind Ghostscript und OpenCV Voraussetzung (schwergewichtige Installation), und die Performance ist die langsamste unter den reinen Python-Optionen.
PyMuPDF (fitz) hat in Version 1.23 eine einfache Tabellenextraktion eingeführt. Es ist schnell (4,6 ms) und funktioniert gut bei einfachen Tabellen mit Rahmen, die Unterstützung für rahmenlose Tabellen reicht jedoch nicht an pdfplumber oder Camelot heran.
pypdf hat keinerlei Tabellenerkennung. Es liefert nur Rohtext, sodass man die Struktur selbst per Parser rekonstruieren müsste.
tabula-py ist ein Python-Wrapper um die Java-basierte Tabula-Bibliothek. Die Erkennung für Tabellen mit und ohne Rahmen ist gut, setzt aber eine Java-Laufzeitumgebung voraus und ist wegen des JVM-Starts die langsamste Option. Sie eignet sich besser für einmalige Extraktionen als für Durchsatz-Pipelines.
Für die meisten Produktionseinsätze empfiehlt sich folgender Aufbau: PDF Oxide als primärer Extraktor wegen Geschwindigkeit und Einfachheit und pdfplumber als Fallback für die Teilmenge an Dokumenten mit komplexen Tabellenlayouts, die aufwendigere Heuristiken verlangen.
Installation
pip install pdf_oxide
Grundlegende Tabellenextraktion
Als Markdown-Tabellen
Der einfachste Weg — die Seite nach Markdown konvertieren, das Tabellen in GFM-Syntax enthält:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
if "|" in md: # Seite enthält eine Tabelle
print(f"--- Seite {i + 1} ---")
print(md)
WASM
const doc = new WasmPdfDocument(bytes);
for (let i = 0; i < doc.pageCount(); i++) {
const md = doc.toMarkdown(i);
if (md.includes("|")) { // Seite enthält eine Tabelle
console.log(`--- Seite ${i + 1} ---`);
console.log(md);
}
}
doc.free();
Rust
let mut doc = PdfDocument::open("report.pdf")?;
for i in 0..doc.page_count()? {
let md = doc.to_markdown(i, true)?;
if md.contains("|") {
println!("--- Seite {} ---", i + 1);
println!("{}", md);
}
}
Go
doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
n, _ := doc.PageCount()
for i := 0; i < n; i++ {
md, _ := doc.ToMarkdown(i)
if strings.Contains(md, "|") {
fmt.Printf("--- Seite %d ---\n%s\n", i+1, md)
}
}
C#
using var doc = PdfDocument.Open("report.pdf");
for (int i = 0; i < doc.PageCount; i++)
{
var md = doc.ToMarkdown(i);
if (md.Contains("|"))
Console.WriteLine($"--- Seite {i + 1} ---\n{md}");
}
Strukturierte Tabellenextraktion (v0.3.34)
Wer typisierten Zugriff auf Zeilen und Bounding-Boxen ohne Markdown-Parsing benötigt, ruft ExtractTables(pageIndex) (Go, C#) bzw. extract_tables(page) (Python, Rust) auf. Jede Tabelle liefert strukturierte Zellen, sodass Ergebnisse direkt in eine Datenbank oder einen DataFrame fließen — ganz ohne reguläre Ausdrücke.
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
for table in doc.extract_tables(0):
for row in table.rows:
print(row)
Rust
let mut doc = PdfDocument::open("invoice.pdf")?;
for table in doc.extract_tables(0)? {
for row in &table.rows {
println!("{:?}", row);
}
}
Go
doc, _ := pdfoxide.Open("invoice.pdf")
defer doc.Close()
tables, _ := doc.ExtractTables(0)
for _, t := range tables {
for _, row := range t.Rows {
fmt.Println(row)
}
}
C#
using var doc = PdfDocument.Open("invoice.pdf");
foreach (var table in doc.ExtractTables(0))
foreach (var row in table.Rows)
Console.WriteLine(string.Join(" | ", row));
Markdown-Tabellen in Zeilen parsen
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0)
# Tabellenzeilen aus dem Markdown herausziehen
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
header = rows[0] if rows else []
data = rows[1:] if len(rows) > 1 else []
print(f"Spalten: {header}")
for row in data:
print(row)
WASM
const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
const rows = [];
for (const line of md.split("\n")) {
const trimmed = line.trim();
if (trimmed.startsWith("|") && !trimmed.startsWith("|--")) {
const cells = trimmed.split("|").slice(1, -1).map(c => c.trim());
rows.push(cells);
}
}
const header = rows[0] || [];
const data = rows.slice(1);
console.log("Spalten:", header);
data.forEach(row => console.log(row));
doc.free();
Rust
let mut doc = PdfDocument::open("invoice.pdf")?;
let md = doc.to_markdown(0, false)?;
let rows: Vec<Vec<String>> = md.lines()
.map(|l| l.trim())
.filter(|l| l.starts_with('|') && !l.starts_with("|--"))
.map(|l| l.split('|').skip(1).map(|c| c.trim().to_string())
.take_while(|c| !c.is_empty()).collect())
.collect();
if let Some(header) = rows.first() {
println!("Spalten: {:?}", header);
for row in &rows[1..] {
println!("{:?}", row);
}
}
Export nach CSV
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("invoice.pdf")
md = doc.to_markdown(0)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
with open("table.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
Export in ein Pandas-DataFrame
import pandas as pd
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
md = doc.to_markdown(0)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
if rows:
df = pd.DataFrame(rows[1:], columns=rows[0])
print(df)
Zeichenpositionen für eigenes Tabellen-Parsing nutzen
Für feine Kontrolle nutzen Sie die zeichengenaue Extraktion und eigene räumliche Analyse:
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("financial.pdf")
chars = doc.extract_chars(0)
# Zeichen nach Y-Position gruppieren (Zeilen)
rows = {}
for ch in chars:
row_key = round(ch.y / 2) * 2 # Auf 2-pt-Raster einrasten
rows.setdefault(row_key, []).append(ch)
# Zeilen von oben nach unten, Zeichen von links nach rechts sortieren
for y in sorted(rows.keys(), reverse=True):
line_chars = sorted(rows[y], key=lambda c: c.x)
text = "".join(c.char for c in line_chars)
print(text)
WASM
const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);
// Zeichen nach Y-Position gruppieren (Zeilen)
const rows = new Map();
for (const ch of chars) {
const rowKey = Math.round(ch.y / 2) * 2; // Auf 2-pt-Raster einrasten
if (!rows.has(rowKey)) rows.set(rowKey, []);
rows.get(rowKey).push(ch);
}
// Zeilen von oben nach unten, Zeichen von links nach rechts sortieren
const sortedKeys = [...rows.keys()].sort((a, b) => b - a);
for (const y of sortedKeys) {
const lineChars = rows.get(y).sort((a, b) => a.x - b.x);
const text = lineChars.map(c => c.char).join("");
console.log(text);
}
doc.free();
Rust
use std::collections::BTreeMap;
let mut doc = PdfDocument::open("financial.pdf")?;
let chars = doc.extract_chars(0)?;
let mut rows: BTreeMap<i32, Vec<_>> = BTreeMap::new();
for ch in &chars {
let row_key = ((ch.y / 2.0).round() * 2.0) as i32;
rows.entry(row_key).or_default().push(ch);
}
for (_, line_chars) in rows.iter().rev() {
let mut sorted = line_chars.clone();
sorted.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
let text: String = sorted.iter().map(|c| c.char).collect();
println!("{}", text);
}
Go
doc, _ := pdfoxide.Open("financial.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)
rows := map[int][]pdfoxide.Char{}
for _, ch := range chars {
key := int(math.Round(float64(ch.Y)/2) * 2)
rows[key] = append(rows[key], ch)
}
keys := make([]int, 0, len(rows))
for k := range rows { keys = append(keys, k) }
sort.Sort(sort.Reverse(sort.IntSlice(keys)))
for _, y := range keys {
line := rows[y]
sort.Slice(line, func(i, j int) bool { return line[i].X < line[j].X })
var b strings.Builder
for _, c := range line { b.WriteString(c.Char) }
fmt.Println(b.String())
}
C#
using var doc = PdfDocument.Open("financial.pdf");
var chars = doc.ExtractChars(0);
var rows = chars
.GroupBy(c => (int)(Math.Round(c.Y / 2) * 2))
.OrderByDescending(g => g.Key);
foreach (var row in rows)
{
var line = string.Concat(row.OrderBy(c => c.X).Select(c => c.Char));
Console.WriteLine(line);
}
Tabellen nach Markdown extrahieren
Markdown ist das ideale Ausgabeformat, wenn Sie PDF-Inhalte in ein Sprachmodell füttern, eine RAG-Pipeline aufbauen oder extrahierte Daten in einer Form speichern, die für Mensch und Maschine lesbar ist. PDF Oxide gibt Tabellen nativ als GitHub Flavored Markdown (GFM) aus — ein zusätzlicher Konvertierungsschritt entfällt.
from pdf_oxide import PdfDocument
doc = PdfDocument("quarterly-report.pdf")
# Alle Tabellen aller Seiten als Markdown extrahieren
all_tables = []
for i in range(doc.page_count()):
md = doc.to_markdown(i, detect_headings=True)
# Das Markdown in Abschnitte zerlegen und Tabellenblöcke finden
in_table = False
current_table = []
for line in md.split("\n"):
if line.strip().startswith("|"):
in_table = True
current_table.append(line)
else:
if in_table and current_table:
all_tables.append("\n".join(current_table))
current_table = []
in_table = False
if current_table:
all_tables.append("\n".join(current_table))
print(f"{len(all_tables)} Tabellen gefunden")
for idx, table in enumerate(all_tables):
print(f"\n--- Tabelle {idx + 1} ---")
print(table)
Die GFM-Tabellenausgabe ist direkt für LLM-Prompts geeignet. Sie können sie unverändert an eine OpenAI- oder Anthropic-API-Anfrage übergeben; das Modell versteht die tabellarische Struktur ohne weitere Aufbereitung:
# Extrahierte Tabelle zur Analyse an ein LLM übergeben
prompt = f"""Analysiere die folgende Finanztabelle und fasse die wichtigsten Trends zusammen:
{all_tables[0]}
"""
Dieser Ansatz ist deutlich schneller, als Tabellen mit pdfplumber zu extrahieren und anschließend selbst nach Markdown zu konvertieren.
Mehrseitige Tabellen handhaben
Tabellen über mehrere Seiten sind eine klassische Herausforderung in der PDF-Extraktion. Jahresabschlüsse, Inventarlisten und behördliche Meldungen enthalten regelmäßig Tabellen, die sich über zwei, fünf oder Dutzende Seiten ziehen. Der Kern: Tabelle pro Seite einzeln extrahieren und die Zeilen anschließend zusammenfügen — dabei wiederholte Kopfzeilen und Seitenartefakte sauber behandeln.
from pdf_oxide import PdfDocument
doc = PdfDocument("long-report.pdf")
def extract_table_rows(md_text):
"""Tabellenzeilen aus Markdown ziehen, Header und Daten getrennt."""
header = None
data_rows = []
for line in md_text.split("\n"):
line = line.strip()
if not line.startswith("|") or line.startswith("|--"):
continue
cells = [cell.strip() for cell in line.split("|")[1:-1]]
if header is None:
header = cells
else:
data_rows.append(cells)
return header, data_rows
# Zeilen über alle Seiten sammeln
combined_header = None
combined_rows = []
for i in range(doc.page_count()):
md = doc.to_markdown(i)
header, rows = extract_table_rows(md)
if header is None:
continue # Keine Tabelle auf dieser Seite
if combined_header is None:
combined_header = header
elif header == combined_header:
pass # Wiederholte Kopfzeile auf Folgeseiten überspringen
else:
# Andere Tabelle — aktuelle speichern und neu beginnen
print(f"Tabelle mit {len(combined_rows)} Zeilen gefunden")
combined_header = header
combined_rows = []
combined_rows.extend(rows)
if combined_header and combined_rows:
print(f"Spalten: {combined_header}")
print(f"Zeilen insgesamt: {len(combined_rows)}")
for row in combined_rows[:5]:
print(row)
if len(combined_rows) > 5:
print(f"... und {len(combined_rows) - 5} weitere Zeilen")
Dieses Muster funktioniert zuverlässig für Tabellen, deren Kopfzeile auf jeder Seite wiederholt wird (der Normalfall). Erscheint die Kopfzeile nur auf der ersten Seite, können Sie die Logik vereinfachen, indem Sie den Header einmalig von der ersten tabellenführenden Seite übernehmen und alle weiteren Zeilen als Daten behandeln.
Tabellen nach CSV oder DataFrame exportieren
Sobald Tabellendaten extrahiert sind, braucht man sie meist in strukturierter Form für die weitere Auswertung. Die folgenden Beispiele zeigen, wie Sie in wenigen Zeilen von einem PDF zu einem pandas-DataFrame oder einer CSV-Datei kommen.
Stapelexport: alle Tabellen in einzelne CSV-Dateien
import csv
from pdf_oxide import PdfDocument
doc = PdfDocument("catalog.pdf")
table_count = 0
for i in range(doc.page_count()):
md = doc.to_markdown(i)
rows = []
for line in md.split("\n"):
line = line.strip()
if line.startswith("|") and not line.startswith("|--"):
cells = [cell.strip() for cell in line.split("|")[1:-1]]
rows.append(cells)
if len(rows) > 1: # Mindestens Header plus eine Datenzeile
table_count += 1
filename = f"table_page{i + 1}_{table_count}.csv"
with open(filename, "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)
print(f"{filename} gespeichert ({len(rows) - 1} Datenzeilen)")
print(f"Insgesamt {table_count} Tabellen exportiert")
Mehrseitige Tabelle in ein DataFrame
Für Tabellen über mehrere Seiten kombinieren Sie das Stitching-Muster mit pandas:
import pandas as pd
from pdf_oxide import PdfDocument
doc = PdfDocument("financial-statement.pdf")
header = None
all_rows = []
for i in range(doc.page_count()):
md = doc.to_markdown(i)
for line in md.split("\n"):
line = line.strip()
if not line.startswith("|") or line.startswith("|--"):
continue
cells = [cell.strip() for cell in line.split("|")[1:-1]]
if header is None:
header = cells
elif cells == header:
continue # Wiederholte Kopfzeile überspringen
else:
all_rows.append(cells)
if header and all_rows:
df = pd.DataFrame(all_rows, columns=header)
# Numerische Spalten bereinigen
for col in df.columns:
# Spalten, die numerisch wirken, konvertieren
cleaned = df[col].str.replace(r"[$,%]", "", regex=True).str.strip()
try:
df[col] = pd.to_numeric(cleaned)
except (ValueError, TypeError):
pass # Als String belassen
print(df.dtypes)
print(df.head(10))
df.to_csv("financial_data.csv", index=False)
Dieser Ablauf liefert ein sauberes DataFrame mit passenden numerischen Typen — bereit für die Auswertung mit pandas, die Visualisierung mit matplotlib oder das Laden in eine Datenbank.
Komplexe Tabellen: wann pdfplumber sinnvoll ist
Die Tabellenerkennung von PDF Oxide bewältigt standardmäßig ausgerichtete Tabellen gut. In anspruchsvollen Fällen — verbundene Zellen, spaltenübergreifende Überschriften, rahmenlose Tabellen oder mehrzeilige Zellen — sind die spezialisierten Algorithmen von pdfplumber robuster:
import pdfplumber
with pdfplumber.open("complex-report.pdf") as pdf:
page = pdf.pages[0]
tables = page.extract_tables()
for table in tables:
for row in table:
print(row)
Wann welches Werkzeug
| Szenario | Empfehlung |
|---|---|
| Einfache, ausgerichtete Tabellen | PDF Oxide (29× schneller) |
| Tabellen als Teil seitenweiter Markdown-Ausgabe | PDF Oxide |
| Komplex verbundene Zellen / spaltenübergreifende Überschriften | pdfplumber |
| Rahmenlose Tabellen | pdfplumber |
| Geschwindigkeitskritische Stapelverarbeitung | PDF Oxide |
Beides kombinieren
Schnelle Textextraktion mit PDF Oxide, komplexe Tabellenextraktion mit pdfplumber:
from pdf_oxide import PdfDocument
import pdfplumber
# Schnelle Volltext-Extraktion
doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
# Gezielte Tabellenextraktion für komplexe Seiten
with pdfplumber.open("report.pdf") as pdf:
tables = pdf.pages[0].extract_tables()
Verwandte Seiten
- Markdown-Konvertierung — vollständige Markdown-API-Referenz
- Textextraktion — reiner Text und zeichengenaue Extraktion
- PDF Oxide vs. pdfplumber — ausführlicher Vergleich
- PDF zu Markdown — Leitfaden zur Markdown-Konvertierung