Skip to content

PDF zu Markdown in Python

Die Konvertierung von PDF zu Markdown gehört zu den wichtigsten Schritten in modernen Dokument-Pipelines. Egal ob Sie eine LLM-Anwendung oder eine RAG-Pipeline aufbauen oder einfach Dokumente in einem lesbaren Format archivieren wollen: PDF-Dateien per Python nach Markdown zu konvertieren liefert Ihnen eine strukturierte, portable Ausgabe, die sich überall weiterverarbeiten lässt.

Warum überhaupt PDF nach Markdown?

Markdown hat sich als Standardformat für den Datenaustausch in KI- und Dokumenten-Workflows etabliert. Hier die Gründe, warum sich die Konvertierung lohnt:

LLM-Kontextfenster arbeiten am besten mit strukturiertem Text. Große Sprachmodelle wie GPT-4, Claude oder Llama liefern spürbar bessere Ergebnisse, wenn sie sauberes Markdown statt Rohtext als Eingabe bekommen. Überschriften geben dem Modell eine Karte des Dokuments, und Formatierungen wie fett und kursiv tragen eine semantische Bedeutung, die beim reinen Text verlorengeht.

RAG-Pipelines brauchen saubere, gechunkte Texte mit erhaltenen Überschriften. Retrieval-Augmented-Generation-Systeme zerlegen Dokumente in Chunks, erzeugen Embeddings und holen zur Abfragezeit die relevantesten Stücke zurück. Markdown-Überschriften sind dafür natürliche Chunk-Grenzen — ein Split an ## liefert Ihnen semantisch zusammenhängende Abschnitte, jeweils mit einem Titel für den Chunk. Reine Textextraktion verliert diese Grenzen komplett, und Sie müssen auf Heuristiken wie Absatzlänge oder Satzanzahl zurückgreifen.

Markdown bewahrt die Struktur und bleibt trotzdem Klartext. Überschriften, Aufzählungen, nummerierte Listen, Tabellen, Fettdruck und Kursivschrift überstehen die Konvertierung in einem Format, das sowohl lesbar als auch maschinell parsbar ist. Eine Markdown-Datei ist einfach eine Textdatei und funktioniert mit Versionskontrolle, Volltextsuche und jeder beliebigen Programmiersprache.

Die Alternativen sind schlechter. Reiner Text verliert die gesamte Struktur: Überschriften sind nicht mehr von Fliesstext unterscheidbar, Tabellen zerfallen zu wirren Zeilen und Listen verlieren ihre Hierarchie. HTML-Konvertierung erhält die Struktur, bläht die Datei aber enorm auf — aus 2 KB Markdown werden schnell 15 KB HTML mit verschachtelten <div>-Tags, CSS-Klassen und Escape-Sequenzen. Markdown ist der goldene Mittelweg: strukturiert, schlank und universell unterstützt.

Schnellstart

Eine PDF-Seite in drei Zeilen nach sauberem Markdown konvertieren:

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)

WASM

import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0);
console.log(md);
doc.free();

Rust

use pdf_oxide::PdfDocument;

let mut doc = PdfDocument::open("paper.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("paper.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("paper.pdf");
Console.WriteLine(doc.ToMarkdown(0));

PDF Oxide erkennt Überschriften anhand von Schriftgrößen-Clustern, behält Fett- und Kursivformatierung, überführt Tabellen in GFM-Syntax und bettet bei Bedarf Bilder ein. Keine andere Python-Bibliothek für PDFs liefert diese Markdown-Konvertierung direkt mit.

Installation

pip install pdf_oxide

Komplettes Dokument konvertieren

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("book.pdf")
md = doc.to_markdown_all(detect_headings=True)

with open("book.md", "w", encoding="utf-8") as f:
    f.write(md)

WASM

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdownAll();
console.log(md);
doc.free();

Rust

let mut doc = PdfDocument::open("book.pdf")?;
let md = doc.to_markdown_all(true)?;
std::fs::write("book.md", &md)?;

Go

doc, _ := pdfoxide.Open("book.pdf")
defer doc.Close()

md, _ := doc.ToMarkdownAll()
_ = os.WriteFile("book.md", []byte(md), 0644)

C#

using var doc = PdfDocument.Open("book.pdf");
File.WriteAllText("book.md", doc.ToMarkdownAll());

to_markdown_all() konvertiert sämtliche Seiten und trennt sie mit ---.

Optionen für die Konvertierung

Parameter Voreinstellung Beschreibung
detect_headings True Schriftgrößen auf #-, ##-, ###-Überschriften abbilden
preserve_layout False Visuelle Positionierung erhalten
include_images True Bilder in die Ausgabe aufnehmen
embed_images True Als base64-kodierte Data-URIs einbetten
image_output_dir None Bilder stattdessen in dieses Verzeichnis speichern

Nur Überschriften, keine Bilder

doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True, include_images=False)

Bilder in einem Verzeichnis ablegen

doc = PdfDocument("report.pdf")
md = doc.to_markdown(0,
    detect_headings=True,
    embed_images=False,
    image_output_dir="output/images"
)
with open("output/report.md", "w") as f:
    f.write(md)

Integration in RAG- und LLM-Pipelines

Markdown ist das ideale Format für RAG-Pipelines. Überschriften liefern natürliche Chunk-Grenzen, und die erhaltene Struktur bewahrt Bedeutung, die bei reinem Text verlorengeht.

Nach Überschriften chunken

Python

from pdf_oxide import PdfDocument
import re

doc = PdfDocument("paper.pdf")
md = doc.to_markdown_all(detect_headings=True)

# An Ueberschriften fuer semantisches Chunking splitten
chunks = re.split(r'\n(?=#{1,3} )', md)
chunks = [chunk.strip() for chunk in chunks if chunk.strip()]

for i, chunk in enumerate(chunks):
    print(f"Chunk {i}: {chunk[:80]}...")

WASM

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdownAll();

// An Ueberschriften fuer semantisches Chunking splitten
const chunks = md.split(/\n(?=#{1,3} )/).filter(c => c.trim());
chunks.forEach((chunk, i) => {
    console.log(`Chunk ${i}: ${chunk.slice(0, 80)}...`);
});
doc.free();

Rust

let mut doc = PdfDocument::open("paper.pdf")?;
let md = doc.to_markdown_all(true)?;

let chunks: Vec<&str> = md.split("\n#")
    .map(|c| c.trim())
    .filter(|c| !c.is_empty())
    .collect();

for (i, chunk) in chunks.iter().enumerate() {
    println!("Chunk {}: {}...", i, &chunk[..chunk.len().min(80)]);
}

Go

doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()

md, _ := doc.ToMarkdownAll()

re := regexp.MustCompile(`\n(?=#{1,3} )`)
for i, chunk := range re.Split(md, -1) {
    chunk = strings.TrimSpace(chunk)
    if chunk == "" { continue }
    if len(chunk) > 80 { chunk = chunk[:80] }
    fmt.Printf("Chunk %d: %s...\n", i, chunk)
}

C#

using var doc = PdfDocument.Open("paper.pdf");
var md = doc.ToMarkdownAll();

var chunks = Regex.Split(md, @"\n(?=#{1,3} )")
    .Select(c => c.Trim())
    .Where(c => c.Length > 0)
    .ToList();

for (int i = 0; i < chunks.Count; i++)
{
    var preview = chunks[i].Length > 80 ? chunks[i][..80] : chunks[i];
    Console.WriteLine($"Chunk {i}: {preview}...");
}

Chunking auf Seitenebene

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
chunks = []
for i in range(doc.page_count()):
    md = doc.to_markdown(i, detect_headings=True, include_images=False)
    chunks.append({
        "page": i,
        "content": md,
        "source": "report.pdf"
    })

WASM

const doc = new WasmPdfDocument(bytes);
const chunks = [];
for (let i = 0; i < doc.pageCount(); i++) {
    const md = doc.toMarkdown(i);
    chunks.push({ page: i, content: md, source: "report.pdf" });
}
doc.free();

Rust

let mut doc = PdfDocument::open("report.pdf")?;
let mut chunks = Vec::new();
for i in 0..doc.page_count()? {
    let md = doc.to_markdown(i, true)?;
    chunks.push((i, md));
}

Go

doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()

type Chunk struct {
    Page    int
    Content string
    Source  string
}
n, _ := doc.PageCount()
chunks := make([]Chunk, 0, n)
for i := 0; i < n; i++ {
    md, _ := doc.ToMarkdown(i)
    chunks = append(chunks, Chunk{Page: i, Content: md, Source: "report.pdf"})
}

C#

using var doc = PdfDocument.Open("report.pdf");
var chunks = Enumerable.Range(0, doc.PageCount)
    .Select(i => new { Page = i, Content = doc.ToMarkdown(i), Source = "report.pdf" })
    .ToList();

Stapel-Konvertierung für eine Vektordatenbank

from pdf_oxide import PdfDocument, PdfError
from pathlib import Path

pdf_dir = Path("documents/")
documents = []

for pdf_path in pdf_dir.glob("*.pdf"):
    try:
        doc = PdfDocument(str(pdf_path))
        md = doc.to_markdown_all(detect_headings=True, include_images=False)
        documents.append({
            "source": pdf_path.name,
            "content": md,
            "pages": doc.page_count()
        })
    except PdfError as e:
        print(f"Uebersprungen {pdf_path.name}: {e}")

print(f"Konvertiert: {len(documents)} PDFs")

Bei 0,8 ms pro Seite ist die Konvertierung Tausender PDFs für Ihre Vektordatenbank eine Angelegenheit von Sekunden, nicht Minuten.

Wie die Überschriftenerkennung funktioniert

PDF Oxide clustert die auf der Seite gefundenen Schriftgrößen, um Überschriftenebenen abzuleiten:

  1. Alle Text-Spans mit Schriftgröße und Gewicht erfassen
  2. Spans nach Größe clustern — die häufigste Größe gilt als Fliesstext
  3. Grössere oder fettere Größen auf # (am größten), ## und ### abbilden
  4. Inline-Formatierungen wie fett (**text**) und kursiv (*text*) erhalten

Das funktioniert zuverlässig bei wissenschaftlichen Arbeiten, Berichten und Dokumentationen. Bei PDFs mit ungewöhnlichen Schriftschemata deaktivieren Sie die Erkennung einfach:

md = doc.to_markdown(0, detect_headings=False)

PDF zu Markdown für LLM- und RAG-Pipelines

Die eingebaute Markdown-Konvertierung von PDF Oxide ist gezielt für KI-Workflows konzipiert. Die erkannte Überschriftenhierarchie bildet die semantische Struktur direkt ab, sodass die nachgelagerte Verarbeitung unkompliziert bleibt.

Markdown an ein LLM übergeben

Konvertieren Sie ein PDF und schicken Sie das Markdown direkt an ein Sprachmodell zur Zusammenfassung, für Q&A oder zur Analyse:

from pdf_oxide import PdfDocument

doc = PdfDocument("quarterly-report.pdf")
md = doc.to_markdown_all(detect_headings=True, include_images=False)

# An beliebige LLM-API senden — die Markdown-Struktur hilft dem Modell,
# die Gliederung des Dokuments zu verstehen
prompt = f"""Fasse das folgende Dokument zusammen. Achte auf die
Ueberschriftenstruktur, um die Hauptabschnitte zu erkennen.

{md}
"""
# response = llm_client.generate(prompt)

Da PDF Oxide die Überschriftenhierarchie (#, ##, ###) erhält, kann das LLM Abschnittstitel vom Fliesstext unterscheiden und abschnittsbewusste Zusammenfassungen liefern. Bei reiner Textextraktion muss das Modell den Anfang und das Ende von Abschnitten raten.

Nach Überschriften chunken für RAG

An Markdown-Überschriften zu splitten liefert semantisch sinnvolle Chunks, die gut eingebettet und präzise retrievd werden:

from pdf_oxide import PdfDocument
import re

doc = PdfDocument("technical-manual.pdf")
md = doc.to_markdown_all(detect_headings=True, include_images=False)

# An Ueberschriften in Chunks zerlegen
chunks = re.split(r'\n(?=#{1,3} )', md)
chunks = [c.strip() for c in chunks if c.strip()]

# Jeder Chunk beginnt mit einer Ueberschrift — ideal als Metadatum
for chunk in chunks:
    lines = chunk.split('\n', 1)
    title = lines[0].lstrip('#').strip()
    body = lines[1].strip() if len(lines) > 1 else ""
    # embed_and_store(title=title, content=body, source="technical-manual.pdf")

So erhalten Sie Chunks, die zusammenhängend sind (jeder Chunk entspricht einem kompletten Abschnitt), einen Titel besitzen (die Überschrift dient als Metadatum für das Retrieval) und in der Länge konsistent bleiben (Autoren erzeugen meist Abschnitte vergleichbarer Länge). Die Überschriftenerkennung von PDF Oxide macht das ganz ohne manuelle Konfiguration möglich — der Clustering-Algorithmus auf Basis der Schriftgrößen erkennt die Ebenen automatisch.

Warum PDF Oxide ideal für KI-Pipelines ist

Mit 0,8 ms pro Seite ist PDF Oxide schnell genug, um Dokumente spontan zur Abfragezeit zu konvertieren, nicht nur beim Indizieren. Das eröffnet Workflows, die mit langsameren Tools unpraktikabel wären:

  • On-Demand-Konvertierung: Markdown-Ausgabe direkt, wenn ein Anwender ein PDF hochlädt — ohne spürbare Verzögerung
  • Neu-Verarbeitung: Aktualisieren Sie Ihren RAG-Index, indem Sie alle PDFs nach einer Änderung der Chunking-Strategie neu konvertieren — Tausende Seiten in Sekunden
  • Streaming-Pipelines: Konvertieren Sie PDFs direkt beim Eintreffen in einer Queue, ohne einen Rückstau aufzubauen

Stapelverarbeitung

Konvertieren Sie ein ganzes Verzeichnis voller PDFs in Markdown-Dateien:

from pdf_oxide import PdfDocument
from pathlib import Path

for pdf_path in Path("documents/").glob("*.pdf"):
    doc = PdfDocument(str(pdf_path))
    md_parts = []
    for i in range(doc.page_count()):
        md_parts.append(doc.to_markdown(i, detect_headings=True))

    md_path = pdf_path.with_suffix(".md")
    md_path.write_text("\n\n".join(md_parts))
    print(f"Konvertiert {pdf_path.name} -> {md_path.name}")

Bei Sub-Millisekunden pro Seite werden Hunderte PDFs in Sekunden konvertiert. Für produktive Workloads mit Tausenden Dateien zeigt der Leitfaden zur Stapelverarbeitung Muster für parallele Verarbeitung.

PDF zu Markdown: PDF Oxide im Vergleich

Werkzeug Geschwindigkeit Eingebaut Überschriftenerkennung Tabellen-Erhalt
PDF Oxide 0,8 ms Ja Ja Ja
pymupdf4llm 55,5 ms (69× langsamer) Nein (separates Paket) Ja Ja
marker rund 500 ms und mehr Nein (separates Tool) Ja Ja
pdfplumber + Eigencode rund 23 ms und mehr Nein (manuell) Nein Manuell
pypdf + Eigencode rund 12 ms und mehr Nein (manuell) Nein Nein

PDF Oxide ist die einzige Python-Bibliothek für PDFs mit eingebauter, schneller Markdown-Konvertierung. Überschriften werden per Schriftgrößen-Clustering erkannt, Tabellen in GitHub Flavored Markdown überführt und Inline-Formatierungen bewahrt — alles in einem einzigen to_markdown()-Aufruf.

pymupdf4llm benötigt PyMuPDF (AGPL-lizenziert) plus das Zusatzpaket pymupdf4llm. Es ist 69-mal langsamer als PDF Oxide und bringt Copyleft-Auflagen mit, die mit proprietären Anwendungen oft nicht vereinbar sind.

marker ist ein eigenständiges Tool, keine Bibliothek. Es nutzt Deep-Learning-Modelle zur Layouterkennung — das macht es bei komplexen Layouts präzise, aber um Grössenordnungen langsamer. Außerdem benötigt es für gute Performance erheblich GPU-Speicher.

pdfplumber und pypdf bieten überhaupt keine Markdown-Konvertierung. Sie müssten selbst Code schreiben, um Überschriften zu erkennen, Tabellen zu rekonstruieren und die Ausgabe als Markdown zu formatieren — ein spürbarer Aufwand, um das nachzubauen, was PDF Oxide out of the box liefert.

Verwandte Seiten