Möchten Sie PDFs parsen?
(eliot-jones.com)- PDF-Parsing sollte auf einer klaren Reihenfolge und Struktur basieren, doch reale Dateien halten sich oft nicht an diese Vorgaben
- Bei Cross-Reference-(xref-)Zeigern und beim Finden von Offsets treten verschiedene Fehler und Inkonsistenzen auf
- In der Praxis entstehen viele Probleme durch unnötige Daten vor dem PDF-Header oder durch falsch platzierte Zeiger und Offsets
- Es gibt auch viele Fälle, in denen die xref-Tabelle selbst unklar oder fehlerhaft formatiert ist
- Deshalb implementieren große Viewer zusätzliche Logik, um auch nicht standardkonforme PDF-Dateien zu unterstützen
Ein idealer Ansatz für das PDF-Parsing
- PDF-Parsing läuft theoretisch in festen Schritten ab
- Am Dateianfang wird der Kommentar mit dem Versions-Header gesucht
- Der Cross-Reference-(xref-)Zeiger wird gefunden
- Alle Objekt-Offsets werden gesammelt
- Das trailer-Dictionary wird gefunden, um auf die gesamte Katalogstruktur zuzugreifen
Einführung in PDF-Objekte
- PDF-Objekte sind Einheiten, die Zahlen, Strings, Dictionaries und andere PDF-Elemente kapseln und speichern
- Jedes Objekt befindet sich zwischen den Markern "
obj/endobj" - Objekte sind über indirekte Referenzen (indirect reference, z. B. "16 0 R") miteinander verbunden
- Wie Objekte in der Datei verteilt sind, ist flexibel, aber einige Objekttypen müssen zwingend indirekt referenziert werden
Cross-Reference-Offsets finden
- Ein PDF enthält strukturell eine Cross-Reference-(xref-)Tabelle, die als Index für Objektpositionen dient
- Am Dateiende wird mit der Syntax "startxref" eine bestimmte Byte-Position als Zeiger angegeben
- Dieser Zeiger bestimmt die xref-Position, aber zwischen Spezifikation und realen Dateien gibt es Unterschiede. Zum Beispiel sollte der Marker "%EOF" eigentlich in der letzten Zeile stehen, in realen PDFs kann er jedoch irgendwo innerhalb der letzten 1.024 Byte auftauchen
- In realen Dateien finden sich verschiedene Abweichungen wie Formatfehler beim Zeiger (
startrefusw.) oder fehlende Zeilenumbrüche
Objekt-Offsets finden
- In der xref-Tabelle folgen auf "xref" die Startnummer des Objekts und die Anzahl der Objekte, und Offset/Generationsnummer/Status (n oder f) jedes Objekts werden zeilenweise aufgeführt
- Es kann mehrere xref-Tabellen geben, die über den /Prev-Eintrag miteinander verknüpft sind
Position des trailer-Dictionarys suchen
- Oberhalb des startxref-Markers befindet sich das trailer-Dictionary, das die wesentlichen Metadaten enthält, um das Root-Objekt zu finden
- Ausgehend vom Root-Objekt kann mit der Interpretation der gesamten Struktur begonnen werden
Die Praxis: unerwartete Probleme
-
Viele Dateien halten sich nicht an die PDF-Spezifikation, sodass sie mit einem allgemeinen Parser nur schwer verarbeitet werden können
-
Häufige Fehlschläge bei der Suche nach Cross-Reference-Zeigern
- Der Zeiger befindet sich nicht am Dateiende oder nicht innerhalb der letzten 1.024 Byte
- Tippfehler (
startrefusw.) - Ausnahmeformate
-
In einer Untersuchung von 3.977 realen PDF-Beispielen hatten etwa 0,5 % Fehler in der xref-Deklaration
PDF-Inhalt beginnt bei einem Offset ungleich 0
- Wenn sich vor dem Header unnütze Daten (junk) befinden, verschieben sich alle Byte-Offsets, wodurch die startxref-Position nicht mehr stimmt
- Offsets müssen relativ zur Header-Position neu berechnet werden, und beide Positionen müssen geprüft werden
- Das macht etwa 50 % aller Fehler aus
Der xref-Zeiger zeigt auf die Mitte der xref-Tabelle
- Der angegebene Offset kann mitten in den Inhalt der xref-Tabelle zeigen
- In 3.977 Beispielen wurde das etwa 5 Mal beobachtet
Der Zeiger liegt in der Nähe von xref
- Oft ist der Zeiger nicht exakt korrekt, sondern nur um Leerzeichen oder Zeilenumbruchzeichen direkt vor oder nach xref verschoben
Der Zeiger stimmt, aber die xref-Offsets sind falsch
- Auch die in der xref-Tabelle eingetragenen Offsets selbst können falsch sein
- Bei manchen Objekten stimmt der Wert, während andere fehlerhafte Offsets haben
Der erste Zeiger ist korrekt, aber der vorherige Offset (/Prev) ist fehlerhaft
- In vielen Fällen werden beim Bearbeiten eines PDFs im /Prev-Zeiger falsche Werte gespeichert (z. B. 0)
Das Format der xref-Tabelle ist fehlerhaft
- Es gibt viele Varianten:
"xref"und Zahlen kleben ohne Zeilenumbruch zusammen, es gibt mehr Einträge als deklarierte Objekte, oder mitten in der Tabelle befinden sich Mülldaten - Solche Fälle wurden etwa bei PdfPig vielfach als Issues gemeldet
Fazit
- Laut Spezifikation sollte PDF-Parsing in einer standardisierten Reihenfolge ablaufen, aber viele reale Dateien tun das nicht, wodurch verschiedenste Parsing-Probleme entstehen
- PDF-Viewer für den praktischen Einsatz enthalten standardmäßig Funktionen zur Unterstützung nicht standardkonformer PDFs
- Diese Zusammenfassung behandelt nur einen kleinen Teil des Parsings aus der PDF-Spezifikation (22 Seiten von insgesamt 1300 Seiten)
5 Kommentare
Diese Zusammenfassung behandelt nur einen Teil des Parsings, der einem Ausschnitt der PDF-Spezifikation entspricht (22 Seiten von insgesamt 1300 Seiten) <- ... 1300 Seiten sind wirklich enorm ...
Wow..
PDF ist, offen gesagt, ein für Menschen gut lesbares Format, das von Menschen erstellte Formatierungen möglichst weitgehend bewahrt – für Maschinen ist es jedoch denkbar ungeeignet.
Kann ich nachvollziehen. Ehrlich gesagt bin ich mir auch nicht sicher, ob es sich gut lesen lässt … Es ist einfach zu schwerfällig und unhandlich.
Hacker-News-Kommentare
Die Antwort ist klar
In der Realität heiße ich Geoff, und die Hälfte der Lebenslauf-Parser erkennt meinen Namen getrennt als „Geo“ und „ff“
Das liegt daran, wie Text in PDFs landet, und ist ein Problem, das bei vielen Quell-Apps immer wieder auftritt
PDF-Parsing und das Parsen von PDF-Inhalten sind völlig unterschiedliche Dinge
Eine PDF-Datei zu parsen ist schon lästig genug, aber PDFs basieren selbst auf „an einer bestimmten Position etwas platzieren“, also nicht auf klar definiertem Text innerhalb von Bounding Boxes, sodass man zur Wortextraktion raten muss, welche Zeichen zusammengehören
Wenn man Lebenslauf-Parsern helfen will, lohnt sich ein Blick auf den Accessibility tree
Nicht jeder PDF-Renderer exportiert barrierefreie PDFs, aber barrierefreie PDFs können immerhin helfen, Dinge wie Namen korrekt auszulesen
Das „ff“-Problem entsteht vermutlich dadurch, dass der Lebenslauf-Analysator Nicht-ASCII-Zeichen nicht verarbeiten kann, etwa die Ligatur ff
Man kann den PDF-Renderer so einstellen, dass er keine Ligaturen erzeugt, aber dann kann der Text unschön aussehen
Es wirkt so, als würde man sehr viel in das Wort „sollte“ hineinlegen
Wenn die Nutzung von PDFs in der Praxis ziemlich feindselig ist, denken die Leute wohl nicht so weit
Schon der Grund, einen Lebenslauf als PDF einzureichen, ist oft, zu verhindern, dass Zwischenhändler ihn verändern, und „Bearbeitung“ hat viele Formen, etwa Kästen über ein Bild zu legen, um etwas zu verdecken, oder Tabellen als PDF statt als CSV zu erstellen, damit die Auswertung schwieriger wird
In der Praxis funktioniert dieser Ansatz in manchen Fällen durchaus gut, einige Apps verwenden ihn bereits
Allerdings bleibt das Problem, dass die beiden Darstellungen (Inhalt/Metadaten) in der Realität nicht übereinstimmen müssen
Es bleibt die Frage, was man mit eingescannten handschriftlichen Dokumenten oder anderen Scans macht, wenn Scanner und normale Heimcomputer kein perfektes OCR bieten
Wahrscheinlich liegt das Problem daran, dass ff als Ligatur gerendert wird
Ich bin Gründer von Tensorlake
Wir haben eine Dokument-Parsing-API für Entwickler gebaut
Das ist der Grund, warum Computer-Vision-Ansätze beim PDF-Parsing in der Praxis tatsächlich gut funktionieren
Sich nur auf Metadaten innerhalb der Datei zu verlassen, skaliert bei der Vielfalt an PDF-Quellen nicht
Deshalb wandeln wir PDFs in Bilder um, wenden zuerst ein Layout-Erkennungsmodell an, lassen dann spezialisierte Modelle für Text- und Tabellenerkennung laufen und setzen die Teile wieder zusammen, um Ergebnisse zu erzielen, die selbst in Bereichen mit hohen Genauigkeitsanforderungen brauchbar sind
Auf den ersten Blick wirkt dieser Ansatz etwas albern, aber eigentlich scheint er die realistischste Lösung zu sein
PDF ist im Kern ein Format, das dafür entwickelt wurde, ein für Menschen lesbares Layout darzustellen, nicht etwas, das für Maschinenlesbarkeit entworfen wurde, sondern ein Format mit Fokus auf ansprechende Darstellung
Deshalb wirkt ein Ansatz, der die menschliche Art zu lesen nachahmt, ziemlich plausibel
Schade ist nur, dass PDF in über 30 Jahren keine bessere Maschinenlesbarkeit bekommen hat
Ich frage mich, welcher Anreiz gefehlt hat, damit das nicht möglich wurde
Falls jemand dazu Einsichten hat, würde ich sie gern hören
Es ist ein wenig ironisch
Wenn man ein PDF ausdruckt, einscannt und dann per E-Mail verschickt, ist das normalerweise Anlass zum Spott, aber beim PDF-Parsing macht man im Grunde genau dasselbe
Dass so ein Ansatz nötig ist, ist eine frustrierende Realität
Die Welt parst HTML schließlich auch nicht auf diese Weise
Ich bin Mitgründer von Nutrient.io und arbeite seit über 10 Jahren mit PDFs
Wie Webbrowser müssen PDF-Viewer eine enorme Vielfalt an PDFs akzeptieren
Weil PDFs so alt sind, nehmen Dateierzeuger willkürliche Änderungen vor, solange es in dem Viewer, den sie benutzen, gut aussieht
Deshalb hat unser Unternehmen ein SDK für AI document processing entwickelt (REST API, PDF als Eingabe, strukturierte Daten als JSON zurück)
Durch Erfahrung mit visuellen Verfahren ebenso wie mit struktureller Vor- und Nachverarbeitung liefern wir bessere Ergebnisse bei Leistung und Kosten als rein visionsbasierte Ansätze
Wenn man sich nicht selbst mit PDF-Verarbeitung herumschlagen und sich lieber auf das eigentliche Geschäft konzentrieren will, kann das hilfreich sein
https://www.nutrient.io/sdk/ai-document-processing
Wenn schon jemand mit Expertenwissen zur internen PDF-Struktur da ist, habe ich eine Frage
Warum ist mupdf-gl (auf einem typischen Desktop-Linux-System) so viel schneller als praktisch alle anderen Programme?
Beim Durchsuchen großer PDFs ist es deutlich überlegen, und ich habe mich immer gefragt, warum andere Viewer nicht genauso schnell sein können
Falls jemand dazu Einsichten hat, würde ich sie gern hören
Im Ergebnis hat man die Parsing-Arbeit einfach an die Software ausgelagert, die das PDF beim Rendern in ein Bild umwandelt
Ich denke schon lange, dass wir uns von layoutzentrierter Dokumentkommunikation lösen sollten
Anders gesagt: Professionell gestaltetes Layout ist eigentlich eher eine alte Gewohnheit und hat fast nichts mit dem tatsächlichen Verständnis des Inhalts zu tun
Zum Beispiel sind Unterlagen für Einreichungen bei Regulierungsbehörden oft extrem umfangreich, und um alle Layout-Regeln einzuhalten, verbringt man in Microsoft Word sehr viel Zeit
Um dieses Layout zu garantieren, reicht man dann im DOCX- oder PDF-Format ein, aber diese Formate sind für Programme sehr ungeeignet, um Inhalte automatisch zu extrahieren oder weiterzuverarbeiten
LLMs können solche Dateien zwar lesen, aber im Vergleich zu einfachen maschinenfreundlichen Dateien wie Text, markdown, XML oder JSON kostet das deutlich mehr Rechenaufwand
Als Alternative denke ich über die Möglichkeit nach, einfache Formate nach dem Prinzip „machine first“ und „content first“ zu standardisieren, etwa auf Basis von JSON, XML oder HTML
Sie würden nur minimale Struktur- und Bild-Einbettungsinformationen enthalten, und für Menschen könnte eine Viewer-App sie ansprechend rekonstruieren
Die maschinelle Verarbeitung wäre deutlich einfacher
Obwohl es mit HTML/Browsern, EPUB und Ähnlichem bereits verwandte Formate gibt, scheint es Zeit, den klassischen Ansatz zu ersetzen
Ich hoffe, dass die LLM-Revolution in diese Richtung führt und dass teures PDF-Parsing künftig nur noch in Legacy-Pipelines übrig bleibt
Ich habe noch nie selbst einen DOCX-Parser gebaut, aber DOCX basiert auf XML, und solange man nicht explizit Layout vorgibt, ist nicht alles in absolute Koordinaten gegossen; wenn JPEG 0 Punkte, PDF 15 Punkte und markdown 100 Punkte sind, dann wäre DOCX vielleicht ein Schwierigkeitsgrad von ungefähr 80
Ich fand das eine großartige Zusammenfassung, und es gab noch einen zusätzlichen Punkt, den ich interessant fand
Incremental-save-Ketten: Der erste startxref-Offset ist meist okay, aber die /Prev-Links, die Acrobat bei wiederholten Änderungen immer weiter anhängt, zeigen oft ein paar Bytes vor das nächste xref
Die meisten Viewer (PDF.js, MuPDF, sogar Adobe Reader) suchen dann stumpf im ganzen Dateiinhalt nach dem obj-Token und rekonstruieren daraus neue Tabellen, während spezifikationsnahe Parser explodieren
Wenn man in der Praxis Dokumente verarbeiten will, die von mehreren Anwendungen wiederholt verändert wurden, ist so ein Recovery- bzw. Salvage-Pfad unverzichtbar
Oft zeigt eine frühere Referenz oder ein Element in der Kette auf einen Offset außerhalb der Datei, auf Offset 0 oder auf einen sonstigen falschen Wert
Dieser Artikel wurde durch meine Überarbeitung der frühen Parsing-Logik in meinem Projekt PdfPig ausgelöst
Anfangs hatte ich den Code aus Java PDFBox portiert, aber ich wollte ihn schneller und einfacher machen
Die neue Logik scannt die gesamte Datei, sobald auch nur eine xref-Tabelle oder ein xref-Stream fehlt, und vertraut im Recovery-Pfad nur diesem Offset
Allerdings ist sie definitiv langsamer als vorher, und ich bin mir nicht sicher, ob die Änderung in der Praxis wirklich gut ist
Ich untersuche gerade verschiedene Sonderfälle in einem Testsatz mit 10.000 Dateien
https://github.com/UglyToad/PdfPig/pull/1102
Mit funktionierenden Annahmen und einem geeigneten PDF-Objektparser wirkt es vielleicht einfach, aber ich glaube, in Wirklichkeit ist es das ganz und gar nicht
Diese Situation ist wie die Hölle der PDFs
PDF ist keine Spezifikation, sondern ein sozialer Konsens, eher eine „Vibe“-Sache
Je mehr man sich dagegen wehrt, desto tiefer sinkt man hinein, und inzwischen fühlt es sich an, als würden wir alle in einem Sumpf leben, der sich dem Blick Gottes entzieht
Darüber musste ich lachen
Auf die Frage „Willst du PDFs parsen?“ kann ich mit Nachdruck sagen: auf keinen Fall
Warum, wird im Originalbeitrag gut erklärt
Ich wünschte, meine Bank würde die Unterlagen in einem besser lesbaren Format bereitstellen, aber bis dahin habe ich wohl keine Wahl
Ich habe diesen Fehler schon einmal gemacht und werde ihn nie wieder machen
Als jemand, der schon einen PDF-Parser geschrieben hat, finde ich PDF ein wirklich seltsames Format
Ich glaube, diese Merkwürdigkeit kommt aus dem ursprünglichen Design als Mischung aus Binär- und Textformat
Selbst die etwas schlampig ungenauen xref-Offsets stammen vermutlich von Bugs bei der Verarbeitung von LF/CR-Zeilenumbruchkonvertierungen
Einer der Punkte, die im Artikel nicht erwähnt wurden, ist, dass moderne PDFs (v1.5+) oft keinen normalen Text-xref-Table mehr haben, sondern einen „xref stream“
Ab v1.6 können sogar die Objekte selbst in einem object stream liegen
Erst scheint alles unproblematisch, aber dann steckt das gewünschte Objekt in einem Stream, dieser Stream verwendet eine abgewandelte PNG-Kompression, oder der Offset liegt in einem flate-komprimierten xref stream, und schon wird es unerquicklich
Dazu kommen mehrere vermischte Dokumentversionen, sodass es komplex wird zu bestimmen, was überhaupt der neueste Stand ist
Das PDF-1.7-Dokument ist leicht zu bekommen, aber bis vor gerade einmal zwei Jahren war die PDF-2.0-Spezifikation noch hinter einer Paywall versteckt
PDF ist kein Format, das für Streaming entworfen wurde
Durch das trailer dictionary am Ende wird es schwierig, die Datei zu parsen, bevor sie komplett geladen ist
Allerdings gibt es auch „streamingfähige PDFs“, bei denen die erste Seite sofort gerendert werden kann, wenn die nötigen Informationen am Anfang liegen, auch wenn das nicht für den Rest gelten muss
Es ist allerdings schon etwas her, seit ich mich zuletzt intensiv mit PDFs beschäftigt habe
Auch mit einem Footer kann PDF gestreamt werden, solange die Website Range Requests unterstützt und den Content-Length-Header korrekt setzt
Ein Streaming-Reader kann einen HEAD-Request senden, dann die letzten paar hundert Bytes der Datei anfordern, um Pointer und Tabellen zu finden, und anschließend den Rest nachladen
Für in Echtzeit erzeugte PDFs ist das ungeeignet, aber bei einem normalen älteren Webserver reichen ein bis zwei zusätzliche Roundtrips völlig aus
Leider kümmern sich nur wenige um Range-basierte Parser pro Datei, aber technisch unmöglich ist es nicht
Genau, es gibt das Format Linearized PDF, das genau dafür entwickelt wurde, die erste Seite schnell anzuzeigen, ohne die ganze Datei herunterzuladen
In der Zusammenfassung habe ich es weggelassen, weil dazu noch viel Zusatzbeschreibung nötig gewesen wäre
Eines meiner ersten Projekte, nachdem ich Python gelernt hatte, war ein PDF-Parser
Ich wollte damit automatisch Karten für eine DnD-Kampagne extrahieren, aber das Ergebnis war ein Fehlschlag, haha
Ich habe schon einmal einen TIFF-Reader geschrieben
TIFF ist ebenfalls berüchtigt dafür, leicht zu schreiben, aber schwer zu lesen zu sein
PDF scheint in dieselbe Kategorie zu fallen