PDF-Dateien extrahieren und katalogisieren

Aus LinuxUser 12/2019

PDF-Dateien extrahieren und katalogisieren

© Puntasit Choksawatdikorn, 123RF

Gemolken

Möchten Sie Texte, Tabellen oder Bilder aus PDF-Dateien weiterverarbeiten gilt es, diese daraus zu extrahieren. Die richtigen Tools und etwas Python-Code helfen dabei.

Das 1993 von Adobe ins Leben gerufene Portable Document Format (PDF) stellt ein elektronisches Äquivalent zu einem gedruckten Text dar. Seine Entwicklung geht auf die 1970er-Jahre zurück und leitet sich von der Seitenbeschreibungssprache Postscript ab.

Einer gedruckten Seite sieht man die Reihenfolge nicht an, in der ihre Elemente ausgegeben wurden. Genauso verhält es sich auch bei einem PDF, was das Extrahieren der Inhalte erschwert. Diese Erfahrung hat vermutlich jeder schon einmal gemacht, der versucht, eine Tabelle aus einem PDF zu kopieren. Die Ortskoordinaten gehen verloren, und damit die Information über die Reihenfolge der Tabellenzellen.

Hinzu kommt, dass sich PDF nicht um Buchstaben kümmert. Neben eingebetteten Zeichensätzen bringt es eigene Übersetzungstabellen für Glyphen mit, die grafische Darstellung eines Buchstabens. Dazu gehören auch Ligaturen, die sprachlich zusammengehörende Buchstaben zusammenschieben, wie bei den Zeichenfolgen “fi” oder “fl”.

Zusätzlich liegen die Daten mitunter in komprimierter oder verschlüsselter Form vor. Wer es genau wissen will, liest sich in die mehr als 1000 Seiten umfassende PDF-Spezifikation ein [1]. Ähnlich vielgestaltig wie das PDF-Format fallen die Werkzeuge für seine Analyse aus, von denen Sie viele über die Linux-Kommandozeile starten.

Der Artikel konzentriert sich darauf, Inhalte nicht nur auszulesen, sondern weiterzuverarbeiten und zu katalogisieren. Deshalb bettet der Autor die Tools in eine Python-Umgebung ein. Wer möchte, baut die Python-Programmbeispiele zu eigenständigen Wrappern aus. Der Artikel stellt die wichtigsten Bibliotheken vor, die Sie über Python 3 ansprechen.

Inhalte ausgeben

Das Drucken einer PDF-Datei stellt im Prinzip das Beschreiben eines virtuellen Blatts dar. In welcher Reihenfolge die Zeichen ankommen, spielt keine Rolle, sondern nur die jeweilige Position auf der Seite. Oft bestehen die Zeichen auch nicht aus Buchstaben, sondern aus einer Menge von Druckpunkten.

Die Entwickler des PDF-Formats haben es nicht dafür entworfen, es nachträglich elektronisch zu bearbeiten. Wurden die Inhalte aber sauber erzeugt, gelingt in vielen Fällen eine Rücktransformation. Die hier vorgestellten Verfahren beschränken sich auf das Extrahieren zusammenhängender Texte und lassen das Layout außen vor. Einige PDF-Dateien zum Experimentieren, die dieser Artikel im Folgenden heranzieht, fasst die Tabelle “Beispiele” zusammen. Sie finden sie auch im Download-Bereich zum Artikel.

Datei

Bemerkung

Quelle

Saale.pdf

Wikipedia-Artikel Saale (PDF-Ausdruck)

https://de.wikipedia.org/wiki/Saale

b1.pdf, b2.pdf, b3.pdf

ähnlich strukturierte PDF-Dateien aus dem Bundesanzeiger

https://www.bundesanzeiger.de/ebanzwww/wexsservlet

kh-tab.pdf

tabellenartig angeordneter Text von Diabetes.de

https://preview.tinyurl.com/lu-kh-tabelle-pdf

fifo.pdf

PDF-Formular von Bundesregierung.de

https://preview.tinyurl.com/lu-fifo-pdf

fifofi.pdf

ausgefülltes PDF-Formular fifo.pdf

Das Paket poppler-utils beziehungsweise poppler-tools mit den Programmen Pdftotext und Pdfimages finden Sie in den Repositories der meisten Distributionen. In Listing 1 ruft das Skript alle Dateien auf und speichert das Ergebnis in entsprechenden Dateien mit der Endung .txt. Nutzt das Skript den Befehl subprocess.Popen statt subprocess.run, gibt es Fehlermeldungen aus. Verwenden Sie Python 2, heißt der Aufruf subprocess.call.

Listing 1

import subprocess
fnl = ['Saale', 'kh-tab', 'b1', 'b2', 'b3', 'fifo', 'fifofi']
dd = 'data/'
for fi in fnl:
  fn = dd+fi+'.pdf'
  fo = dd+fi+'.txt'
  subprocess.run(["pdftotext", fn, fo])
# proc = subprocess.Popen(["pdftotext", fn, fo], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# output, error = proc.communicate()
# print(error.decode("utf-8") )
  print(fo)

Bei Pdfminer handelt es sich um eine reine Python-Bibliothek. Achten Sie bei der Installation über Pip darauf, die mittels der Endung .six an Python 3 angepasste Version auszuwählen (Listing 2). Unter Pdfminer heißt das Skript zum Extrahieren von Texten pdf2txt. Sie rufen es mit dem Parameter -o für die Dateiausgabe auf (Listing 3).

Listing 2

$ pip3 install pdfminer.six
# Falls ausschließlich Python 3
# auf dem System installiert ist:
$ pip install pdfminer.six

Listing 3

fn = 'inputfile.pdf'
fo = 'outfile.txt'
subprocess.run(["pdf2txt", fn, '-o', fo])

Schlüsselbegriffe und Formulare auslesen

Ein einfaches Extraktionsprogramm wie Pdftotxt genügt, um aus ähnlich strukturierten PDF-Dateien Schlüsselwörter zu extrahieren. Abbildung 1 zeigt einen Auszug aus dem Bundesanzeiger als PDF-Datei. Möglicherweise möchte man die PDF-Dateien ihrem Aktenzeichen entsprechend umbenennen. Im Ausdruck steht es rechts in der dritten Zeile, beginnend mit “BAnz”.

Abbildung 1: Auszug aus dem Bundesanzeiger. Als Schlüsselwort zum weiteren Verarbeiten dient hier die Abkürzung "BAnz" oben rechts.

Abbildung 1: Auszug aus dem Bundesanzeiger. Als Schlüsselwort zum weiteren Verarbeiten dient hier die Abkürzung “BAnz” oben rechts.

Listing 4 zeigt, wie Python die Aktennummer dreier Beispieldateien findet. Als Quelldateien dienen die umgewandelten PDF-Dateien aus Listing 1, die eine For-Schleife nacheinander liest. Die Variable sp legt fest, mit welcher Zeichenfolge die gesuchte Textzeile beginnt. Läuft der Befehl startswith(sp) ohne Fehler durch, gibt das Skript den Dateinamen und die vollständige Aktennummer aus. Seit der Version 6 kennt Python den komfortablen Ausdruck f-String, welcher die geschweiften Klammern durch die Werte der eingeschlossenen Variablen ersetzt.

Listing 4

sp = 'BAnz'
ext='txt'
for fi in fnl:
  fn = dd + fi + '.' + ext
  with open(fn) as fp:
    for line in fp:
      if line.startswith(sp):
        print(f'{fi}.{ext}: {line.strip()}')
        break
# Ausgabe:
# b1.txt: BAnz AT 30.08.2019 V1
# b2.txt: BAnz AT 30.08.2019 B1
# b3.txt: BAnz AT 30.08.2019 B2

Im Prinzip ließen sich so auch Formulardaten auslesen, allerdings geht dabei die Zuordnung zu den Eingabefeldern in der Textrepräsentation verloren. Das Problem löst die Bibliothek Pdfminer. Während zum Umwandeln einer PDF-Datei in Text eine Bibliothek genügt, fehlt ein vergleichbares Programm für Formulare.

Das Tool Slate3k dient als Wrapper für Pdfminer, um die umständliche Programmierung zu vereinfachen. Mit aktuellen Python-Implementationen scheint es in Version 0.5.3 von 2017 aber noch nicht zu funktionieren. Die Lösung aus Listing 5 fällt eher sperrig aus, da sie drei Unterbibliotheken einbezieht; im Gebrauch spielt das aber keine Rolle.

Listing 5

from pdfminer.pdfparser import PDFParser
from pdfminer.pdfdocument import PDFDocument
from pdfminer.pdftypes import resolve1
fn='data/fifofi.pdf'
fp = open(fn, 'rb')
parser = PDFParser(fp)
doc = PDFDocument(parser)
fields = resolve1(doc.catalog['AcroForm'])['Fields']
for i in fields:
  field = resolve1(i)
  name, value = field.get('T'), field.get('V')
  if value:
    try:
      print(f'{i}: {name.decode("latin_1")}: {value.decode("utf-16-be")}')
    except AttributeError:
      print(f'{i}: {name.decode("latin_1")}: {value}')
  else:
    print(f'{i}: {name.decode("latin_1")} *** Keine Eingabe *** ')

Die For-Schleife in Listing 5 ab Zeile 12 durchläuft alle Formularfelder. Deren Namen und ihren Inhalt geben die Variablen name und value aus. Ohne Dekodierung sähe eine ausgedruckte Zeile so aus wie das Beispiel in Listing 6. Spitze Klammern umschließen dort die Bezeichnung der internen Referenz. In der Byte-Ausgabe des Feldnamens markiert ein Hex-Zeichen einen Umlaut. Feldnamen liegen in einem ANSI-ähnlichen Format vor. Den Zelleninhalt leitet die magische Zahl feff ein, ein Hinweis auf eine UTF-16-Kodierung mit 2 Bytes pro Zeichen.

Listing 6

# Ohne Dekodierung:
<PDFObjRef:179>: b'F\xf6rderung(en)': b'\xfe\xff\x00H\x00e\x00i\x00n\x00z'
# Mit Dekodierung (siehe Text)
(...)
<PDFObjRef:167>: Name: <feff>Willi Wunder
<PDFObjRef:168>: Straße: <feff>Werkstattstraße
<PDFObjRef:169>: PLZ, Ort: <feff>Wetzlar
<PDFObjRef:170>: Telefonnummer: <feff>123
<PDFObjRef:171>: Faxnummer: <feff>45
<PDFObjRef:172>: E-Mail-Adresse: <feff>willi.wunder@wetzlar.de
<PDFObjRef:173>: Rechtsform: <feff>GbR
<PDFObjRef:174>: Anzahl der Mitarbeiter/-innen:: <feff>1
<PDFObjRef:175>: Beteiligungsverhältnisse:: <feff>Kommentarfeld, das die Eingabe ...
<PDFObjRef:46>: Check Box Liste aller Filme: /'Ja'
<PDFObjRef:48>: Check Box Erläuterungen *** Keine Eingabe ***
(...)

Die If-Abfrage versucht, die Zeichen möglichst korrekt zu dekodieren: Feldnamen als “Latin”, Feldinhalte, sofern angegeben, als “UTF-16 big Endian” oder, wenn es sich um Auswahlfelder handelt, ohne Kodierung. Der untere Teil von Listing 6 gibt die Inhalte als ordentlich formatierte Texte aus und stellt sie für eine mögliche weitere Verarbeitung bereit.

Tabellen erfassen

Als wäre es nicht bereits schwer genug, Text aus PDF-Dateien zurückzugewinnen, versagen viele Programme bei Tabellen. Auch ein Kopieren über die Zwischenablage scheitert – nicht, weil sich die Zellen nicht markieren lassen, sondern weil der Kopiervorgang die Reihenfolge der Tabellenzellen nicht einhält.

Eine interaktive Annäherung an dieses Problem verspricht die Installation der grafischen Oberfläche Tabula [2]. Nach dem Start des Java-Programms mit dem Aufruf java -jar tabula.jar öffnen Sie dessen Nutzeroberfläche im Browser über die URL http://localhost:8080 (Abbildung 2). Automatisiert greift der Python-Wrapper tabula-py auf PDF-Tabellen zu. Eine weitere Bibliothek ist camelot-py. Sie installieren beide Tools wie in Listing 7 gezeigt.

Abbildung 2: Das Webfrontend von Tabula gestattet es, problemlos in PDF eingebettete Tabellen oder das ganze Dokument zu extrahieren.

Abbildung 2: Das Webfrontend von Tabula gestattet es, problemlos in PDF eingebettete Tabellen oder das ganze Dokument zu extrahieren.

Listing 7

$ pip3 install camelot-py
$ pip3 install tabula-py

Listing 8 startet einen Testlauf mit dem Programm Camelot, das nach Linien sucht, die Tabellen unterteilen. Findet es sie nicht, gibt es einen Index-Fehler aus. Das Programm fängt ihn ab und versucht es erneut, diesmal mit dem Schalter stream – manchmal mit Erfolg.

Stößt Camelot auf komprimierte PDF-Dateien, quittiert es den Durchlauf mit der Fehlermeldung NotImplementedError: only algorithm code 1 and 2 are supported. Auf Dekomprimierungsprogramme wie Qpdf geht dieser Artikel nicht ein, da die Alternative Tabula-py in vielen Fällen bessere Ergebnisse liefert.

Listing 8

from camelot.io import read_pdf
fnl = ['Saale', 'kh-tab', 'b1', 'b2', 'b3', fifo', 'fifofi']
dd = 'data2/'
for fi in fnl:
  fn = dd+fi+'.pdf'
  fo = dd+'c_'+fi+'.csv'
  print(fn, fo)
  try:
    tables = read_pdf(fn)
    print(fn, fo, tables[0].parsing_report)
    tables[0].to_csv(fo)
  except IndexError:
    tables = read_pdf(fn, flavor='stream')
    print(fn, fo, tables[0].parsing_report)
    tables[0].to_csv(fo)
  except:
    print('Undef. error')

In Listing 9 unterstützt die Bibliothek pandas Tabula und übernimmt das Formatieren in eine ordentliche CSV-Datei. Auf Wunsch fügt Pandas Überschriften für Spalten hinzu, oder platziert wie im Beispiel mehrere Tabellen in eine Datei.

Listing 9

import pandas as pd
import tabula
fnl = ['Saale', 'kh-tab', 'b1', 'b2', 'b3', 'fifo', 'fifofi']
dd = 'data2/'
for fi in fnl:
  fn = dd+fi+'.pdf'
  fo = dd+fi+'.csv'
  print(fn, fo)
  df = tabula.read_pdf(fn, pages='all', multiple_tables=True)
  dtf = pd.DataFrame(df)
  dtf.to_csv(fo)

Nehmen die Spalten zu viel Raum ein, ordnet das Skript sie untereinander an. Die Nummerierung hilft, sie anschließend richtig zusammenzufügen. Die Daten aus dem Ausschnitt in Abbildung 3 erscheinen in einem einfachen Zeicheneditor blockweise in der richtigen Zusammenstellung (Abbildung 4).

Abbildung 3: Ausschnitt einer tabellarischen Auflistung in einem PDF-Dokument.

Abbildung 3: Ausschnitt einer tabellarischen Auflistung in einem PDF-Dokument.


Abbildung 4: Die Positionen der Daten, die Tabula-py aus der Tabelle aus <a href="#artRef-f3">Abbildung&nbsp;3</a> zusammentr&auml;gt, bleibt vollst&auml;ndig erhalten.

Abbildung 4: Die Positionen der Daten, die Tabula-py aus der Tabelle aus Abbildung 3 zusammenträgt, bleibt vollständig erhalten.

Bilder

Geeignete Werkzeuge greifen unmittelbar auf Bilder in einer PDF-Datei zu. Dass auch Grafiken in PDF-Dateien nicht unveränderlich sind, sei hier nur erwähnt. Um eine Bildsammlung aus einer PDF-Datei zu extrahieren, eignet sich Pdfimages aus dem Paket Poppler-utils sehr gut. Der folgende Aufruf extrahiert alle im PDF enthaltenen Bilder ins JPG-Format:

$ pdfimages -all demo.pdf images/prefix-jpg

Wie die vorhergehenden Beispiele zeigen, lässt sich der Aufruf über die Bibliothek subprocess aus Python heraus erledigen.

Es ist schwer, Bilder oder Texte in eine bestehende PDF-Datei einzubauen. Schließlich darf sie die Papiergrenzen nicht überschreiten. Dagegen ist es einfach, Bilder gleicher Größe auszutauschen oder mit dem Programm Pypdf Streams zu überlagern. Die Bibliothek Python3-pypdf2 findet sich in den Paketquellen der meisten Distributionen.

Bei Pypdf2 handelt es sich um ein reines Python-Skript. Wahlweise lässt es sich auch mit dem Aufruf pip3 install pypdf2 über pip installieren. Anders als das alte Pypdf läuft Pypdf2 unter Python 3. Das Ergebnis der Überlagerung des Schriftzugs Demo zeigt Abbildung 5. Um das zu erreichen, wandelt das Tool das zu überlagernde Bild in eine PDF-Datei um. Dabei gilt es, darauf zu achten, dass der Untergrund transparent bleibt, da er andernfalls den gesamten Text verdeckt.

Abbildung 5: Mit Pypdf l&auml;sst sich relativ einfach ein Wasserzeichen in eine PDF-Datei integrieren.

Abbildung 5: Mit Pypdf lässt sich relativ einfach ein Wasserzeichen in eine PDF-Datei integrieren.

Wie Listing 10 zeigt, werden die PDF-Seite der Quelldatei und die des Bilds überlagert. Manche Schwärzungsprogramme arbeiten nach einem ähnlichen Verfahren. Liegt die Originaldatei vor, lässt sich die Maske leicht wieder entfernen. Darauf kann man aber meist verzichten, da die Texte nach den oben beschriebenen Methoden weiter zugänglich bleiben.

Listing 10

fn='data/Saale.pdf'
fo='data/Saalemark.pdf'
wm='data/watermark.pdf'
from PyPDF2 import PdfFileWriter, PdfFileReader
def watermark(inpdf, outpdf, watermarkpdf):
  watermark = PdfFileReader(watermarkpdf)
  watermark_page = watermark.getPage(0)
  pdf = PdfFileReader(inpdf)
  pdf_writer = PdfFileWriter()
  pdf_page = pdf.getPage(0)
  pdf_page.mergePage(watermark_page)
  pdf_writer.addPage(pdf_page)
  with open(outpdf, 'wb') as fp:
    pdf_writer.write(fp)
watermark(fn, fo, wm)

Fazit

Mit den hier beschriebenen Methoden gelingt es in aller Regel, Informationen aus PDF-Dateien zu extrahieren und sie systematisch zu verarbeiten. Auch ausgefüllte Formulare lassen sich mit Python erfassen und analysieren. Sogar Tabellen verlieren dabei ihren Schrecken, auch wenn viele Fälle ein Nachbearbeiten der extrahierten Daten erfordern. 

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDF
LinuxUser 12/2019 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben