Seite 1 von 1

Zusammenfügen von Textdateien

Verfasst: Samstag 5. Februar 2022, 01:22
von Jemandneues
Hallo zusammen,

ich habe vor kurzem angefangen mich mit Python zu beschäftigen.
Ich habe ein Skript geschrieben welches aus den Seiten eines PDF-Dokuments jpgs macht und diese dann mit tesseract in Textdateien speichert.
Diese haben dann nach dem ursprünglichen Dateinamen den Zusatz "Seitex"
Nun möchte ich diese Textdateien wieder pro eingelesener Datei in eine Textdatei zusammenfügen und verwende dafür folgenden Code:

liste_Textdateien = []
txt_path = r"D:\Masterarbeit\Eingangsrechnungen\Rechnung_Text\\"
txt_name = os.listdir(r"D:\Masterarbeit\Eingangsrechnungen\Rechnung_Text")


for txt in txt_name:
liste_Textdateien.append(txt_path + txt)

for Datei1 in liste_Textdateien:
for Datei2 in liste_Textdateien:
if Datei1[:-11] == Datei2[:-11] and Datei1 != Datei2:
Ausgabe = open(("%s.txt") % (Datei1)[:-11] , "a")
with open((Datei1), "r") as text1, open((Datei2) , "r") as text2:
Ausgabe.write(text1.read())
Ausgabe.write(text2.read())
liste_Textdateien.remove(Datei1)
liste_Textdateien.remove(Datei2)
liste_Textdateien.append("%s.txt" % (Datei1[:-11]))
os.remove(Datei1)
os.remove(Datei2)


elif Datei1[:-11] == Datei2:
Ausgabe = open(Datei2 , "a")
with open((Datei1), "r") as text1, open(Datei2 , "r") as text2:
Ausgabe.write(text1.read())
Ausgabe.write(text2.read())
liste_Textdateien.remove(Datei1)
liste_Textdateien.append("%s.txt" % (Datei1[:-11]))
os.remove(Datei1)


Irgendwie funktioniert das ganze aber nur für eine gerade Anzahl an Dateien. Wenn die Anzahl ungerade ist, bleibt am Ende immer eine übrig.
Was übersehe ich hier?

Und vielen Dank schon einmal für die Hilfe!

Re: Zusammenfügen von Textdateien

Verfasst: Samstag 5. Februar 2022, 13:18
von __blackjack__
@Jemandneues: Man darf Listen über die man gerade iteriert nicht verändern. Insbesondere Löschen ist gar keine gute Idee, weil dadurch alle folgenden Elemente eins nach vorne rücken und dadurch Elemente in Positionen rutschen können über die der Iterator schon hinweg ist, und die deshalb dann nicht berücksichtigt werden. Mal ein ganz einfaches Beispiel:

Code: Alles auswählen

In [46]: xs = list(range(10))                                                   

In [47]: xs                                                                     
Out[47]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [48]: for x in xs: 
    ...:     xs.remove(x) 
    ...:                                                                        

In [49]: xs                                                                     
Out[49]: [1, 3, 5, 7, 9]
Ein weiteres Problem das Dein Code hat ist, dass das auflisten von Dateinamen keinerlei Garantien über die Reihenfolge gibt. Die kommen so wie das Dateisystem die liefert. Das kann in der Reihenfolge sein in der die Dateien angelegt wurden, oder grossteils in dieser Reihenfolge. Das kann lexikographisch sein. Das kann aber auch ”zufällig” sein, wenn das Dateisystem beispielsweise auf einer Hashtabelle für die Namen aufgebaut ist. Du musst also explizit selber für die passende Reihenfolge der Daten sorgen wenn die Seiten auch 100%ig sicher in der Reihenfolge sein sollen die in den Dateinamen über "Seite…" kodiert sind.

Und da dann auch gleich mit daran denken, dass lexikographisch gesehen "10" < "2" gilt. Das heisst einfach naiv Zeichenketten sortieren funktioniert nur solange die Seitenzahlen einstellig sind/bleiben. Kann sein, dass sie das bei Dir sind, das würde ich aber trotzdem im Code berücksichtigen. Wenn schon nicht über eine passende Sortierung, dann wenigstens das eine Ausnahme ausgelöst wird, wenn man versucht Daten mit zu vielen Seiten zu verarbeiten.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).

Grunddatentypen haben in Namen nichts verloren. Die ändert man während der Entwicklung nicht selten in dem man den Typ durch einen passenderen ersetzt oder gar einen eigenen schreibt, und dann hat man falsche, irreführende Namen im Quelltext oder man muss überall alle betroffenen Namen ändern.

Pfade sind keine beliebigen Zeichenketten, da müssen bestimmte Regeln eingehalten werden, damit die gültig sind, und diese Regeln unterscheiden sich auch noch bei verschiedenen Betriebssystemen. Darum geht man da nicht mit Zeichenkettenoperationen dran sondern verwendet die entsprechenden Werkzeuge. Früher waren das die Funktionen in `os.path`, bei neuem Code sollte man gleich auf das `pathlib`-Modul setzen.

Das „slicen“ mit -11 ist nicht robust. Es lässt vermuten, dass die Seitennummern tatsächlich einstellig sind, heisst aber auch, dass auch das eine weitere Stelle ist, wo der Code auf die Nase fallen wird, wenn da mal Dokumente mit mehr Seiten vorkommen.

Ebenfalls ein Programmierfehler ist das nicht schliessen der Dateiobjekte die an `Ausgabe` gebunden werden. Das kann zu Datensalat führen weil erst das schliessen des Dateiobjekts alle eventuell noch gepufferten Daten tatsächlich in die Datei schreibt. Da Python keine Garantien gibt wann das passiert wenn man es nicht explizit tut (im Grunde noch nicht einmal *ob* das passiert), können da Daten in den Dateien in einer anderen Reihenfolge landen als das gewünscht ist.

Der Wert von `txt_path` wird im Grunde in der nächsten Zeile noch mal wiederholt. Solche Wiederholungen sollte man sich sparen. Wenn man den Code mal anpassen will/muss, dann muss man das an mehreren Stellen gleich machen und dabei können Fehler passieren.

Textdateien sollte man immer explizit mit der Kodierung öffnen. In diesem Fall kann man allerdings davon ausgehen, dass die Dateien alle die selbe Kodierung haben und das die Kodierung nicht wirklich wichtig für das Programm ist und deshalb die Dateien einfach im Binärmodus öffnen. Das ist auch etwas effizienter, weil man sich dann beim kopieren einmal dekodieren und enkodieren spart.

So vom Grund her ist die Aufgabe ja eigentlich recht einfach: Die Dateipfade ermitteln, sortieren nach Dateiname und Seitennummer, was im Falle von einstelligen Seitennummern trivial ist, und dann nach dem ”Grundnamen” gruppieren.

Wir wissen nicht wie die Dateinamen in den letzten 11 Zeichen genau aussehen, darum rate ich mal was am praktischsten wäre, würde folgendes vorschlagen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import groupby
from pathlib import Path

BASE_PATH = Path(r"D:\Masterarbeit\Eingangsrechnungen\Rechnung_Text")


def get_name(path):
    name, _, _ = path.name.rpartition("_")
    return name


def main():
    paths = sorted(BASE_PATH.glob("*_Seite?.txt"))
    for name, path_group in groupby(paths, get_name):
        with (BASE_PATH / f"{name}.txt").open("wb") as file:
            for path in path_group:
                file.write(path.read_bytes())
                path.unlink()


if __name__ == "__main__":
    main()
Das Problem mit Dokumenten mit mehr als 9 Seiten ist hier dadurch ”umgangen”, dass die durch das Glob-Muster nicht erfasst werden, weil das nur eine Stelle für die Seitenzahl vorsieht. Im Fehlerfall würden dann also die überzähligen Seiten einfach nicht verarbeitet und damit auch nicht gelöscht und verbleiben im Verzeichnis.

Re: Zusammenfügen von Textdateien

Verfasst: Samstag 5. Februar 2022, 14:01
von Jemandneues
Vielen Dank für die ausführliche Erklärung und die Zeit die du dir genommen hast!
Vor allem bei Dateien mit mehr als 10 Seiten bin ich schon auf die Nase gefallen, die Annahme mit dem auf die Nase fallen hat also ganz gut gepasst :)

Ich habe erst vor 2 Tagen mit dem Thema programmieren angefangen und merke, dass ich mir hier noch vieles beibringen muss.

Ich werde mich wohl erstmal weiter auf die Suche nach guter Literatur begeben.