Daten aus PDF in CSV bestimmte Spalten extrahieren

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
HoLyYy
User
Beiträge: 4
Registriert: Freitag 12. März 2021, 13:07

Hallo zusammen, ich sitze nun seit fast einer Woche an folgendem Problem. Ich habe stundenlange Tutorials und zig Foren durchsucht, aber ich finde leider meinen Fehler nicht. Oder es funktioniert gar nicht, so wie ich es will? Ich muss dazu sagen, dass ich mich seit kurzem mit der Programmierung beschäftige und ich würde mich freuen, falls ihr mich unterstützen könntet. Ich habe einige Screenshots eingefügt, damit es leichter zu verstehen ist, was ich will und wie weit ich bin.

Ausganslage: Das ist die PDF-Datei, aus der ich genau die Daten ab Datum Zeit etc. haben möchte Bild
Code zum extrahieren der Daten: Bild
Anzeigen der extrahierten Daten: Bild
Daten in CSV schreiben: ausgabe.csv Bild

Ich dachte nun, dass es vielleicht normal ist und habe mit dem Modul csv versucht die erstellte Datei ausgabe.csv nochmal zu bearbeiten:
Code zum einlesen und bearbeiten der ausgabe.csv: Bild

Wenn ich die erstellte Datei ausgabe1.csv öffne sieht es immer noch aus wie es unter ausgabe.csv zu sehen ist, alle Daten werden in einer Spalte angelegt. Im Terminal, werden die Daten ja richtig getrennt, aber ich möchte, dass die Daten in der CSV einzeln in Spalten geschrieben werden (Datum unter Datum, Zeit unter Zeit etc). Ist das denn überhaupt möglich? Vielen dank schonmal für eure Mühen :-)
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Mit Bild können wir wenig anfangen. Poste den Code direkt hier im Forum in Codetags </>.
HoLyYy
User
Beiträge: 4
Registriert: Freitag 12. März 2021, 13:07

Code: Alles auswählen

import pdfplumber
import re
import pandas as pd
import requests
from collections import namedtuple

Spalten = namedtuple("Spalten", "Datum Zeit Beleg Preis Online")

with pdfplumber.open("test.pdf") as pdf:
    page = pdf.pages[0]
    text = page.extract_text()
daten = []


zeilen_beginn = re.compile(r"^\d{2}.\d{2}.\d{4}.*")
for line in text.split("\n"):
   if zeilen_beginn.match(line):
       datum, zeit, beleg, preis, *bezahlung = line.split()
       daten.append(Spalten(datum, zeit, beleg, preis, bezahlung))


df = pd.DataFrame(daten)
df.to_csv("ausgabe.csv")
Wenn dieser code ausgeführt wird erhalte ich folgendes im Terminal:
[Spalten(Datum='07-03-2021', Zeit='11:59:25', Beleg='5YXMP9', Preis='10.00', Online=['*']), Spalten(Datum='07-03-2021', Zeit='12:59:28', etc....

Dann versuch ich die Datei hiermit zu bearbeiten:

Code: Alles auswählen

import csv

daten = []

with open("ausgabe.csv", "r") as ausgabe_csv:
    csv_dict_reader = csv.DictReader(ausgabe_csv)
    for row in csv_dict_reader:
        daten.append({
                "Datum": row["Datum"],
                "Zeit": row["Zeit"],
                "Beleg": row["Beleg"],
                "Preis": row["Preis"],
                "Online": row["Online"]
            })

with open("ausgabe1.csv", "w", newline="") as ausgabe1_csv:
    fieldnames = ["Datum", "Zeit", "Beleg", "Preis", "Online"]
    csv_dict_writer = csv.DictWriter(ausgabe1_csv, fieldnames=fieldnames)
    csv_dict_writer.writeheader()

    for schreiben in daten:
        csv_dict_writer.writerow(schreiben)
In der ausgegeben Datei ändert sich nichts, alles steht in einer Spalte...
Benutzeravatar
__blackjack__
User
Beiträge: 14055
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@HoLyYy: Zum ersten Programm: `requests` wird importiert aber nicht verwendet.

`Spalten` ist ein sehr irreführender Name für ein Objekt das *eine* *Zeile* repräsentiert. Das sollte wohl eher `Zeile` oder `Datensatz` heissen, oder noch besser ein Name der beschreibt was so ein Objekt im Kontext des Programms bedeutet.

Eingerückt wird per Konvention mit vier Leerzeichen pro Ebene, nicht mit drei.

Bei dem regulären Ausdruck kann man sich das "^" am Anfang sparen, das ist bei der `match()`-Methode implizit, und das ".*" am Ende, denn *jede* Zeichenkette endet mit 0 oder mehr beliebigen Zeichen.

Sind die "." zwischen den Datumsteilen so gewollt? So ein "." steht für ein *beliebiges* Zeichen und nicht für einen Punkt! Kann auch eine weitere Ziffer sein. Wenn eine Zeile mit zehn Ziffern beginnt, dann ist das ein erfolgreicher Match mit dem Ausdruck, den Du da angegeben hast!

Das in `bezahlung` alle restlichen Teile (selbst wenn es nur einer oder keiner ist) in einer Liste gesammelt wird, die dann als *ein Wert* im CSV landen soll ist falsch. Selbst wenn man mehrere Werte in einer Zelle unterbringen möchte, würde man das nicht als Zeichenkettendarstellung einer Python-Liste machen, sondern etwas das man auch einfach wieder in eine Datenstruktur bringen kann. Also beispielsweise als JSON.

Wenn man die Textzeile aufteilt und davon per Slicing die ersten fünf Elemente nimmt, lässt sich die ``for``-Schleife durch eine „list comprehension“ ersetzen.

Was beim Speicher als CSV hier keinen Sinn macht ist das der namenlose Index als erste Spalte mitgespeichert wird. Der bringt hier absolut keinen Mehrwert.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import re
from collections import namedtuple

import pandas as pd
import pdfplumber

Datensatz = namedtuple("Datensatz", "Datum Zeit Beleg Preis Online")


def main():
    with pdfplumber.open("test.pdf") as pdf:
        text = pdf.pages[0].extract_text()
    #
    # FIXME Really *any* character between the number groups?
    #
    faengt_mit_datum_an = re.compile(r"\d{2}.\d{2}.\d{4}").match

    pd.DataFrame(
        [
            Datensatz(*line.split()[:5])
            for line in text.splitlines()
            if faengt_mit_datum_an(line)
        ]
    ).to_csv("ausgabe.csv", index=False)


if __name__ == "__main__":
    main()
Allerdings würde ich hier schon korrigieren das Datum und Zeit in zwei Spalten stehen. Beide Spalten gehören eigentlich zusammen in eine Spalte "Zeitpunkt" oder ähnlich, und dann auch gleich in einem vernünftigen Format. Das kann man mit Pandas erledigen.

Was Du mit dem zweiten Programm bezwecken willst ist mir nicht so ganz klar. Im Grunde lässt das ja nur die Indexspalte weg, die man im ersten Programm gar nicht erst schreiben sollte. Andererseits stört die auch nicht zwangsläufig bei der Weiterverarbeitung. Die kann man dann ja immer noch ignorieren.

Wie kommst Du darauf das alles in einer Spalte stehen würde? Excel? Nimm was anderes wo man beim öffnen von CSV-Dateien angeben kann welches Trennzeichen verwendet wurde. Das hängt bei Excel nämlich von der eingestellten Sprache ab. Und AFAIK kann man das auch nicht manuell ändern wenn man eine CSV-Datei öffnet. Und die komische Unart zu versuchen alles mögliche als Datum oder Zeitangabe zu verstehen kann man Excel soweit ich weiss auch nicht abgewöhnen. Kann man mich da an ”lustige” Sachen mit Adresslisten mit eigener Spalte für Hausnummern erinnern. Aus der Hausnummer 7a wurde die Zeit 7 Uhr, weil das als "7 a.m." interpretiert wurde. Auf einem deutschen Windows mit deutschem Office wohlgemerkt. Und das war dieser Kacksoftware nicht abzugewöhnen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
HoLyYy
User
Beiträge: 4
Registriert: Freitag 12. März 2021, 13:07

Danke __blackjack__ für deine ausführliche Antwort. Mit dem "." wollte ich die Bindestriche zwischen dem Datum darstellen, sodass keine andere Zeile aufgenommen wird. Die Spalte "bezahlung" benötige ich leider, weil das "Sternchen" eine Online-Zahlung darstellt und wenn keins angegeben ist, handelt es sich um eine Bar-Zahlung. Datum und Uhrzeit stört mich natürlich nicht wenn es in einer Spalte stehen würde.
Du sprichst genau mein Problem an. Mir wird zwar auch eine CSV-Datei zur Verfügung gestellt, aber in der Spalte Preis tauchen Datumsangaben auf. Beispielsweise steht in der PDF-Datei als Preis 14.20 EUR und in der CSV Datei taucht 14. Feb. auf.... Deswegen mein Versuch hier :-)
Auf den 2. Code kann ich natürlich auch verzichten, ich dachte vielleicht bringt es was... aber Fehlanzeige :-)
Ich werde mich heute aufjedenfall noch mit deiner Verbesserung beschäftigen. Vielen Dank nochmal !!!!
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Statt to_csv benutze to_excel, dann hast Du auch keine Probleme beim Lesen mit Excel.
HoLyYy
User
Beiträge: 4
Registriert: Freitag 12. März 2021, 13:07

Ich hatte to_xlsx probiert mit dem selben Ergebnis...
Antworten