Tabelle aus PDF in Datenbank

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
FabianD
User
Beiträge: 6
Registriert: Freitag 20. März 2015, 23:11

Hi,

mein Name ist Fabian und ich bin derzeit Schüler.
Seit einiger Zeit suche ich nach einer Möglichkeit, den Vertretungsplan unserer Schule im PDF-Format für eine Android App aufzubereiten und die Daten der PDF in einer Datenbank für die App abrufbar zu machen. Allerdings scheitere ich nach dem Download der PDF auf dem Server mit der Umwandlung in ein geeignetes Format.

Bisher waren meine Versuche erfolglos, die PDF mit "PDFMiner" oder "PDF2Text" in eine HTML umzuwandeln, da man durch die Tabellen und die Formatierung keine Daten in einer angemessenen Form lesen kann.
Hat jemand von euch eine Idee, wie ich die PDF (bestehend aus mehreren Seiten mit Tabellen) in ein geeignetes Format umwandle, mit dem ich dann etwas anfangen kann?

Vielen Dank im Voraus!
Fabian
BlackJack

@FabianD: PDFs sind für so etwas nicht unbedingt geeignet. Es kann bei einigen PDFs einfach oder halbwegs einfach funktionieren, aber eben nicht bei allen. Es gibt im PDF keine programmatisch zuverlässig erkennbaren Tabellen. Die bestehen nur aus ”Zeichenbefehlen” wie „zeichne eine Line von A nach B” oder „schreibe an Position X folgenden Text”. Ob die Linie zu einer Umrandung für ein Bild, zur Abgrenzung von Fussnoten, oder Bestandteil von einer Trennlinie von einer Tabelle ist, kann man diesen Daten nicht mehr wirklich ansehen.

Vielleicht macht es Sinn die Schule zu fragen ob sie die Daten auch in einem maschinenlesbaren Format anbieten kann. Wenn möglich in einem offenen Format. Kannst Dich ja vorher mal mit „Open Government” und „Open Data” beschäftigen, und was da in Deutschland von Bund, Ländern, und teilweise auch Gemeinden, schon so angeboten wird, und daraus eventuell auch Argumente für Dein Anliegen ableiten.
FabianD
User
Beiträge: 6
Registriert: Freitag 20. März 2015, 23:11

@BlackJack: Vielen Dank für deine schnelle Antwort und Erklärung. Dass es in PDF´s keine Tabellen als ganzes gibt, wusste ich nicht :oops:. Das erschwert das ganze Projekt natürlich stark.
Ich bin mir nicht sicher, ob die für den Webauftritt der Schule verantwortlichen Personen diesen Mehraufwand akzeptieren würden, aber einen Versuch ist es Wert und meiner Meinung nach auch sinnvoll in der heutigen Zeit.
Sieht jemand noch eine andere Möglichkeit, außer die Schule zu einem anderen Format zu überreden, und dass ich jeden Tag die Datenbank manuell zu aktualisieren muss?
BlackJack

@FabianD: Als erstes mal würde ich ja argumentieren dass das PDF ein Mehraufwand ist, denn die schreiben die Daten ja sehr wahrscheinlich nicht direkt in ein PDF sondern in einem anderen Dokumentformat das dann als PDF ”gedruckt” wird. *Das* Format könnte ja vielleicht schon etwas sein was einfacher per Programm verarbeitbar ist. Also zum Beispiel wenn das vor dem Umwandeln in ein PDF als Exceltabelle vorliegt, könnte man auch die (zusätzlich) zum herunterladen anbieten ohne dass das wirklich viel mehr Arbeit macht.

Falls für den Vertretungsplan eine spezielle Software verwendet wird, kann die vielleicht auch in andere Formate als PDF exportieren, zum Beispiel HTML, oder die Ausgangsdaten sind, wenn schon nicht in einem offenen Format, dann doch vielleicht wenigstens in einem Format das man mit einem Programm verarbeiten kann.

Dem Mehraufwand für die Schule kannst Du versuchen den Mehrwert gegenüberzustellen der durch maschinellen Zugang möglich ist. Zum Beispiel das man dann Apps oder Webanwendungen damit entwickeln kann. Eine interessante Anwendung wäre vielleicht auch das man als Schüler seine Klasse und/oder seine Kurse angibt, und Änderungen am Stundenplan dann in den Kalender vom Smartphone oder Tablet (oder Desktop-Rechner) eingetragen werden.

Notfalls kann man noch versuchen die Daten doch automatisiert aus dem PDF zu lesen. Man kann bei einem festen Format ja mehr Annahmen treffen als wenn man ein Programm für beliebige PDFs entwickeln würde. Das macht aber sehr wahrscheinlich mehr Arbeit und ist nicht so robust wie das verarbeiten von einem Format in dem die Tabellendaten strukturierter abgelegt sind.
FabianD
User
Beiträge: 6
Registriert: Freitag 20. März 2015, 23:11

@BlackJack: Es wird wohl darauf hinauslaufen, dass ich die Schule um ein anderes Dateiformat bitte. Welches bietet sich dabei an, HTML und Exceltabelle hatte ich auch schon im Kopf, aber auch XML wäre doch eine Idee? Bezüglich der Kalendereinträge hatte ich eine Benachrichtigung durch die App bei einer Aktualisierung sowie an ein Homescreen Widget gedacht, aber die Integration in einen Google-Kalender wäre mittels der von Google bereitgestellten API´s auch kein Problem.
Darf ich in diesem Forum auf den Stundenplan verlinken oder verstößt das gegen die Regeln?
BlackJack

@FabianD: Von uns aus (Forum) spricht da nichts gegen. Und wenn die Daten von der Schule öffentlich gemacht werden, dann haben die ja auch nichts dagegen. Anders könnte es aussehen wenn sie den Zugriff technisch auf ihre Schüler einschränkt.
FabianD
User
Beiträge: 6
Registriert: Freitag 20. März 2015, 23:11

@BlackJack: Ok, ich habe gerade mithilfe der Testversion von PDFGenie eine lesbare HTML-Datei erzeugen können:
Vertretungsplan:
http://www.rhs-chemnitz.de/wp-content/u ... FOS122.pdf

Kleiner Teil der ausgegebenen Tabelle:

Code: Alles auswählen

<table style="font-size:9.96pt;color:#000000" class="f0" bbox="41.16,526.948,547.875,656.534" border="1">
      <tr>
        <th rowspan="1" bbox="41.16,639.92,90.72,656.534"><span style="color:#000000">Klasse</span></th>
        <th rowspan="1" bbox="90.72,639.92,110.52,656.534"><span style="color:#000000">St.</span></th>
        <th rowspan="1" bbox="110.52,639.92,168.96,656.534"><span style="color:#000000">Fach</span></th>
        <th rowspan="1" bbox="168.96,639.92,274.44,656.534"><span style="color:#000000">Lehrer</span></th>
        <th rowspan="1" bbox="274.44,639.92,307.44,656.534"><span>DEMO</span></th>
        <th rowspan="1" bbox="307.44,639.92,547.875,656.534"><span style="color:#000000">Info</span></th>
      </tr>
      <tr>
        <td rowspan="1" bbox="41.16,614.36,90.72,639.92"><span style="color:#000000">F12</span></td>
        <td rowspan="1" bbox="90.72,614.36,110.52,639.92"><span style="color:#000000">1</span></td>
        <td rowspan="1" bbox="110.52,614.36,168.96,639.92"><span style="color:#000000">Inf</span></td>
        <td rowspan="1" bbox="168.96,614.36,274.44,639.92"><span>DEMO</span></td>
        <td rowspan="1" bbox="274.44,614.36,307.44,639.92"><span style="color:#000000">152</span></td>
        <td rowspan="1" bbox="307.44,614.36,547.875,639.92"><span style="color:#000000">statt Mi (18.03.) St.1; Te Flem verlegt
        nach Mi (18.03.)</span> <span style="color:#000000">St.1</span></td>
      </tr>
Das sieht doch nach einem akzeptablen Ergebnis aus, allerdings stört mich der Preis von 29$ pro Monat für die Vollversion. Aber es zeigt, dass es möglich ist, diese Tabelle in eine HTML-Tabelle umzuwandeln...
BlackJack

``pdftohtml`` aus dem `poppler-utils`-Paket spuckt mit folgendem Aufruf:

Code: Alles auswählen

$ pdftohtml -xml -stdout FOS122.pdf > test.xml
diese XML-Datei (Ich zeige hier nur den Anfang) aus:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE pdf2xml SYSTEM "pdf2xml.dtd">

<pdf2xml>
<page number="1" position="absolute" top="0" left="0" height="1263" width="892">
	<fontspec id="0" size="10" family="Helvetica" color="#000000"/>
	<fontspec id="1" size="15" family="Helvetica" color="#000000"/>
	<fontspec id="2" size="15" family="Helvetica" color="#000000"/>
	<fontspec id="3" size="12" family="Helvetica" color="#000000"/>
	<fontspec id="4" size="12" family="Helvetica" color="#000000"/>
<text top="86" left="62" width="139" height="11" font="0">Richard-Hartmann-Schule</text>
<text top="108" left="62" width="94" height="11" font="0">13.03.2015 12:37</text>
<text top="55" left="62" width="154" height="17" font="1">Vertretungsplan für</text>
<text top="56" left="237" width="177" height="17" font="2"><b>16.03. bis 19.03.2015</b></text>
<text top="55" left="784" width="48" height="11" font="0">Seite 1/4</text>
<text top="102" left="733" width="98" height="17" font="1">Klassenplan</text>
<text top="145" left="62" width="191" height="17" font="2"><b>Montag, 16. März 2015</b></text>
<text top="183" left="62" width="167" height="17" font="2"><b>Abwesende Lehrer:</b></text>
<text top="183" left="278" width="251" height="17" font="1">GolE, Herb, Herm, Radü, SchU</text>
<text top="210" left="62" width="197" height="17" font="2"><b>Klassen mit Änderung:</b></text>
<text top="210" left="278" width="31" height="17" font="1">F12</text>
<text top="246" left="62" width="266" height="17" font="2"><b>Geänderte Unterrichtsstunden:</b></text>
<text top="281" left="62" width="48" height="14" font="3"><b>Klasse</b></text>
<text top="281" left="168" width="35" height="14" font="3"><b>Fach</b></text>
<text top="281" left="138" width="19" height="14" font="3"><b>St.</b></text>
<text top="281" left="256" width="46" height="14" font="3"><b>Lehrer</b></text>
<text top="281" left="414" width="77" height="14" font="3"><b>Raum Info</b></text>
<text top="308" left="62" width="26" height="14" font="4">F12</text>
<text top="308" left="168" width="17" height="14" font="4">Inf</text>
<text top="308" left="147" width="8" height="14" font="4">1</text>
<text top="308" left="256" width="26" height="14" font="4">Stei</text>
<text top="308" left="414" width="25" height="14" font="4">152</text>
<text top="308" left="463" width="358" height="14" font="4">statt Mi (18.03.) St.1; Te Flem verlegt nach Mi (18.03.)</text>
<text top="325" left="463" width="26" height="14" font="4">St.1</text>
<text top="346" left="62" width="26" height="14" font="4">F12</text>
<text top="346" left="168" width="17" height="14" font="4">Inf</text>
<text top="346" left="147" width="8" height="14" font="4">2</text>
<text top="346" left="256" width="26" height="14" font="4">Stei</text>
<text top="346" left="414" width="25" height="14" font="4">152</text>
<text top="346" left="463" width="358" height="14" font="4">statt Mi (18.03.) St.2; Te Flem verlegt nach Mi (18.03.)</text>
<text top="363" left="463" width="26" height="14" font="4">St.2</text>
<text top="384" left="62" width="26" height="14" font="4">F12</text>
<text top="384" left="168" width="18" height="14" font="4">Sp</text>
<text top="384" left="147" width="8" height="14" font="4">5</text>
<text top="384" left="256" width="39" height="14" font="4">MüAn</text>
<text top="384" left="414" width="20" height="14" font="4">TH</text>
<text top="384" left="463" width="279" height="14" font="4">verlegt von St.7; Ph Pios verlegt nach St.7</text>
<text top="409" left="62" width="26" height="14" font="4">F12</text>
<text top="409" left="168" width="18" height="14" font="4">Sp</text>
<text top="409" left="147" width="8" height="14" font="4">6</text>
<text top="409" left="256" width="39" height="14" font="4">MüAn</text>
<text top="409" left="414" width="20" height="14" font="4">TH</text>
<text top="409" left="463" width="279" height="14" font="4">verlegt von St.8; Ph Pios verlegt nach St.8</text>
<text top="434" left="62" width="26" height="14" font="4">F12</text>
<text top="434" left="168" width="18" height="14" font="4">Ph</text>
<text top="434" left="147" width="8" height="14" font="4">7</text>
<text top="434" left="256" width="29" height="14" font="4">Pios</text>
<text top="434" left="414" width="25" height="14" font="4">251</text>
<text top="434" left="463" width="289" height="14" font="4">verlegt von St.5; Sp MüAn verlegt nach St.5</text>
<text top="459" left="62" width="26" height="14" font="4">F12</text>
<text top="459" left="168" width="18" height="14" font="4">Ph</text>
<text top="459" left="147" width="8" height="14" font="4">8</text>
<text top="459" left="256" width="29" height="14" font="4">Pios</text>
<text top="459" left="414" width="25" height="14" font="4">251</text>
<text top="459" left="463" width="289" height="14" font="4">verlegt von St.6; Sp MüAn verlegt nach St.6</text>
</page>...
Das ist zwar nicht so komfortabel wie eine fertige Tabellenstruktur, aber da man die Struktur des Textes im PDF kennt und zum Beispiel den Inhalt der Kopfzeile, sollte man aus den `top`- und `left`-Angaben in der Lage sein die Daten richtig zuordnen zu können.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

Laut Handbuch hat das eingesetzte Programm keinen sinnvollen Export der Daten, sondern nur eine Druckfunktion. Der Weg scheidet also schonmal aus. Das Problem mit pdf ist, dass sich in der nächsten Version des Vertretungsplanprogramms die Ausgabe leicht ändern könnte und damit Deine Extrahierung nicht mehr funktioniert. Im Sinne von OpenData sollte man auch Schulen zur Bereitstellung von öffentlichen Daten in maschinenlesbarer Form verpflichten :wink: .
FabianD
User
Beiträge: 6
Registriert: Freitag 20. März 2015, 23:11

Vielen Dank für eure Hilfe, aber ehrlich gesagt ist mir das Arbeiten mit Daten solcher Art zu umständlich ^^, aber ich finde auch kein kostenloses Programm, dass mir etwas wie in meinem letzten Post ausspuckt...
Angenommen ich benutze die oben beschrieben Testversion, könnte ich nicht einfach das Script öfters durchlaufen lassen, wobei es jedes Mal wenn in einem Feld nur DEMO steht, ich es freilasse? Nach einer gewissen Anzahl (3-4 Mal) müssten dann alle Felder gefüllt sein... Das wäre allerdings der letzte Ausweg, ich mag so einen Pfusch nicht.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@FabianD: das PDF wird z.B. mit PDFCreator erzeugt, das wiederum Ghostscript verwendet. Dabei passieren seltsame Dinge, wie das z.B. in der Datei erst die Spalte "Fach" dann die Spalte "St." steht oder "Richard-Hartmann-Schule" vor "Vertretungsplan für". Um dieses Durcheinander in Ordnung zu bringen, ist viel Heuristik, also Glück, nötig.

Ich habe mal mit dem reinen Python-PDF-Leser PyPDF2 einen kleinen Parser geschrieben, der für das Beispiel-PDF funktioniert, was leider keine Garantie ist, dass das immer tut. Da bleibt Dir nur, testen, und immer wieder prüfen, ob das ganze für neue PDFs auch noch so funktioniert.

Code: Alles auswählen

from collections import namedtuple
import PyPDF2

Course = namedtuple('Course', 'Klasse,Stunde,Fach,Lehrer,Raum,Info')

def iter_text(page):
    content = page["/Contents"].getObject()
    if not isinstance(content, PyPDF2.pdf.ContentStream):
        content = PyPDF2.pdf.ContentStream(content, page.pdf)
    left, top = 0, 0
    for operands, operator in content.operations:
        if operator == "Tm":
            left = float(operands[-2])
            top = float(operands[-1])
        elif operator == "Td":
            left += float(operands[-2])
            top += float(operands[-1])
        elif operator == "TJ":
            yield left, top, ''.join(operands[0][::2])
        elif operator == "Tj":
            yield left, top, operands[0]

class CourseChanges(object):
    def __init__(self, page):
        fragments = iter_text(page)
        for _, _, text in fragments:
            if text == u"Klassenplan":
                break
        self.date = next(fragments)[2]
        self.absent_teachers = None
        self.absent_classes = None
        self.classes_with_changes = None
        while True:
            _, _, text = next(fragments)
            if text == u'Ge\xe4nderte Unterrichtsstunden:':
                break
            _, _, value = next(fragments)
            if text == u'Abwesende Lehrer:':
                self.absent_teachers = value
            elif text == u'Abwesende Klassen:':
                self.absent_classes = value
            elif text == u'Klassen mit \xc4nderung:':
                self.classes_with_changes = value
            else:
                print "unknown section:", text, value
        next(fragments) # KlasseFach
        next(fragments) # St.LehrerRaumInfo
        self.courses = []
        klasse = None
        for left, top, text in fragments:
            if left < 90: # Klasse
                if klasse:
                    self.courses.append(Course(klasse, stunde, fach, lehrer, raum, info))
                klasse, stunde, fach, lehrer, raum, info = [None]*6
                klasse = text
            elif left < 105:
                stunde = text
            elif left < 160:
                fach = text
            elif left < 260:
                lehrer = text
            elif left < 300:
                raum = text
            else: # Info
                if info is None:
                    info = text
                elif left < 320:
                    info += ' ' + text
                else:
                    info += text
        if klasse:
            self.courses.append(Course(klasse, stunde, fach, lehrer, raum, info))
            

TEMPLATE = u"{0:7s} {1:4s} {2:10s} {3:10s} {4:5s} {5:s}"
def main():
    pdf = PyPDF2.PdfFileReader('FOS122.pdf')

    for page in pdf.pages:
        course_changes = CourseChanges(page)
        print course_changes.date
        print TEMPLATE.format("Klasse","St.","Fach","Lehrer", "Raum", "Info")
        for course in course_changes.courses:
            print TEMPLATE.format(*course)
            
if __name__ == '__main__':
    main()
FabianD
User
Beiträge: 6
Registriert: Freitag 20. März 2015, 23:11

@Sirius3: Wow, vielen Dank. Das funktioniert genau so, wie ich mir das vorgestellt habe. Ich werde mich mal genauer mit dem Script auseinandersetzen, denn ehrlich gesagt überschreitet es auf den ersten Blick meine Kenntnisse.
Antworten