Bestimmte Werte aus txt Dateien auslesen und in neue txt Datei schreiben

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
Mekkes
User
Beiträge: 2
Registriert: Freitag 18. November 2022, 13:03

Hallo,
bin neu im Forum und ziemlicher Python Anfänger. Lerne Python seit ein paar Wochen in der Uni und konnte es aber schon für die Arbeit gebrauchen. Ich sollte Laborversuche, die als pdf gespeichert waren in eine excel Datei übertragen. Dabei waren die Versuche des gleichen Typs immer in einer großen pdf, wobei jeder Versuch eine Seite groß war (ca. 200 Versuche, einzelne Seite immer gleich aufgebaut).
Ich habe die pdf dann einfach in eine txt-Datei umgewandelt und zum Glück wurden alle wichtigen Werte übertragen.
Habe dann ein Skript geschrieben, was die einzelnen Zeilen der Datei in eine Liste überträgt und dann nach bestimmten strings sucht. Anschließend wird die line auf den gesuchten Wert gekürzt und in die Ausgabedatei geschrieben.

Das Skript sieht so aus:

Code: Alles auswählen

with open('Atterberg_LA14.txt') as f:     #vorher pdf in txt mit pdf24
    lines = f.readlines()                 #am ende txt in excel importieren

with open("AusgabeAtterbergLA14.txt", "a") as g:
    g.write('Entnahmestelle;Tiefe;Bodenart;Wassergehalt;Fließgrenze;Ausrollgrenze;Plastizitaetszahl;Konsistenzzahl;korr. Wassergehalt\n')
    
    for line in lines:
        if line.startswith('Entnahmestelle'):
            line = line.lstrip('Entnahmestelle: ')
            line = line.rstrip()
            g.write(f"{line};")
        elif "Tiefe:" in line:
            line = line.split(':')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line};")
        elif "Bodenart:" in line:           #!!! Strich über den Nebenbodenarten fehlt, da nicht von pdf in txt übersetzt --> per hand nachtragen
            line = line.split(':')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line};")
        elif "Wassergehalt w =" in line and "[%]" not in line:
            line = line.split('=')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line};")
        elif "grenze wL =" in line:                #für fließgrenze (scharfes ß wird nicht erkannt)
            line = line.split('=')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line};")
        elif "Ausrollgrenze wP =" in line:
            line = line.split('=')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line};")
        elif "szahl IP =" in line:               #ä geht nicht
            line = line.split('=')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line};")
        elif "Konsistenzzahl IC =" in line:
            line = line.split('=')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line}\n")
        elif "Korr. Wassergehalt = " in line:       #Korr Wassergehalte eine Zeile unter Probe falls vorhanden
            line = line.split('=')[1]
            line = line.lstrip()
            line = line.rstrip()
            g.write(f"{line}\n")
Insgesamt hat das auch sehr gut funktioniert. Nur gibt es eine Zeile (korr. Wassergehalt), welche nicht bei allen Versuchen vorhanden ist. Habe das bisher so gelöst, dass der korr. Wassergehalt dann einfach in die Zeile darunter gepackt wird und wieder ein Zeilenumbruch dahinter gemacht. Und das dann anschließen per Hand angepasst.

Die Ausgabe sieht dann so aus (als txt-Datei):

Entnahmestelle;Tiefe;Bodenart;Wassergehalt;Fließgrenze;Ausrollgrenze;Plastizitaetszahl;Konsistenzzahl
korr. Wassergehalt
Entnahmestelle;Tiefe;Bodenart;Wassergehalt;Fließgrenze;Ausrollgrenze;Plastizitaetszahl;Konsistenzzahl
Entnahmestelle;Tiefe;Bodenart;Wassergehalt;Fließgrenze;Ausrollgrenze;Plastizitaetszahl;Konsistenzzahl
korr. Wassergehalt
Entnahmestelle;Tiefe;Bodenart;Wassergehalt;Fließgrenze;Ausrollgrenze;Plastizitaetszahl;Konsistenzzahl
korr. Wassergehalt

Gibt ein Befehl der sowas sagt wie "schreibe {line} in die voherige Zeile" oder "lösche den letzten Absatz und schreibe {line}"?
Anschließend lese ich die txt-Datei dann in Excel ein und erhalte eine fertige Übersicht.
Das Skript an sich hat mir jetzt schon einen Haufen Arbeit gespart.

Es gibt bestimmt sehr viel bessere Methoden um das ganze zu machen, aber bin wie gesagt noch Anfänger.
Leider gibt es die Dateien nur im pdf Format.

Vielen Dank schon mal und Grüße
Mekkes
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Mekkes: Dateien auf einem Datenträger/in einem Dateisystem bestehen heutzutage aus einer linearen Folge von Bytes. Man kann da nur Bytes anhängen oder bestehene Bytes überschreiben, aber nicht mittendrin Bytes einfügen oder löschen so dass sich die nachfolgenden Bytes automatisch entsprechend verschieben.

Das überträgt sich auf Textdateien, denn das sind letztlich auch nur eine Folge von Bytewerten, die nach bestimmten Regeln auf Zeichen abgebildet werden. Also füge in eine Textdatei eine Zeile oder in eine Zeile einen Wert ein, geht so nicht. Der übliche Weg ist die Daten zu lesen, zu verändern, und neu zu schreiben.

Laut Kopfzeile sollte der Wert ja am Ende jeder Zeile der Ausgabedatei stehen, darum verstehe ich nicht so ganz warum Du den in eine neue Zeile schreibst.

Aber erst mal zu den Kommentaren das bestimmte Zeichen nicht verwendet werden können: Beim öffnen von Textdateien sollte man immer explizit die Kodierung angeben in der der Text gespeichert ist, oder gespeichert werden soll. Also bei der Eingabedatei musst Du herausfinden wie die kodiert sind und diese Kodierung angeben. Und bei der Ausgabedatei dann entweder die gleiche Kodierung angeben, oder falls Du das selbst festlegen kannst, UTF-8, weil damit alle Zeichen gespeichert werden können, die als Unicode-Zeichenkette repräsentiert werden können.

Einbuchstabige Namen sind selten gute Namen. Was soll `g` bedeuten? `f` ist ja nicht einfach nur ein Buchstabe wo man dann als nächsten Buchstaben `g` nimmt. Das `f` soll für `file` stehen. Und eigentlich sollte das dann auch tatsächlich `file` heissen und nicht nur `f`. Was ist dann `g`?

Man braucht da aber auch nicht zwingend einen neuen Namen so wie es im Moment geschrieben ist, denn `f` wird nach dem Einlesen der Datei nicht mehr verwendet, also könnte man zum Schreiben auch wieder den gleichen Namen verwenden.

Andererseits muss man die Daten auch nicht komplett in den Speicher lesen, sondern könnte einfach die Eingabedatei zeilenweise direkt verarbeiten. Dann braucht man unterschiedliche Namen für die Dateiobjekte, weil beide gleichzeitig geöffnet sind.

CSV-Dateien schreibt man nicht manuell, dafür gibt es das `csv`-Modul in der Standardbibliothek.

Der Code in der Schleife muss dann einen kompletten Datensatz sammeln und den schreiben wenn er komplett ist. Am besten mit einem `csv.DictWriter()` denn damit kann man den Code deutlich robuster schreiben wenn Werte fehlern/nicht erkannt werden.

"a" ist der falsche Dateimodus für die CSV-Datei, denn die Kopfzeile sollte ja nicht irgendwo *in* den Daten stehen, sondern in der ersten Zeile.

``line.lstrip('Entnahmestelle: ')`` macht relativ sicher nicht was Du denkst was es macht. Das macht das gleiche wie ``line.lstrip(' :Eaehlmnst')``. Falls Dich das jetzt überrascht, solltest Du die `strip()`-Methoden mit Argument nicht benutzen bis Dir klar ist was die machen.

Wenn man nacheinander `lstrip()` und `rstrip()` auf den gleichen Ausgangswert anwendet, hätte man auch einfach einmal `strip()` verwenden können.

Beim `split()` steht auch die Frage im Raum ob klar ist was das macht, weil man zum aufteilen an *einem* Trenner eher `partition()` verwenden würde, und falls der Unterschied den `split()` macht tatsächlich gewollt ist, würde man das besser durch eine explizite Beschränkung auf *zwei* Trennpunkte klar machen.

Die ganzen Zweige enthalten fast den gleichen Code. Das würde man versuchen in eine Datenstruktur herauszuziehen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import csv


def main():
    # vorher pdf in txt mit pdf24
    # am ende txt in excel importieren

    column_descriptions = [
        ("Entnahmestelle", ": ", lambda line: True),
        ("Tiefe", ":", lambda line: True),
        ("Bodenart", ":", lambda line: True),
        ("Wassergehalt", " w =", lambda line: "[%]" not in line),
        ("Fließgrenze", " wL =", lambda line: True),
        ("Ausrollgrenze", " wP =", lambda line: True),
        ("Plastizitätszahl", " IP =", lambda line: True),
        ("Konsistenzzahl", " IC =", lambda line: True),
        ("Korr. Wassergehalt", " =", lambda line: True),
    ]
    with open("Atterberg_LA14.txt", encoding="utf-8") as lines:
        with open(
            "AusgabeAtterbergLA14.txt", "w", encoding="utf-8", newline=""
        ) as csv_file:
            writer = csv.DictWriter(
                csv_file, [header for header, _, _ in column_descriptions]
            )
            writer.writeheader()
            record = {}
            for line in lines:
                for column_index, header, postfix, predicate in enumerate(
                    column_descriptions
                ):
                    _, found, value = line.partition(header + postfix)
                    if found and predicate(line):
                        if column_index == 0 and record:
                            writer.writerow(record)
                            record = {}
                        record[header] = value.strip()
                        break

            if record:
                writer.writerow(record)


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Mekkes
User
Beiträge: 2
Registriert: Freitag 18. November 2022, 13:03

Hey @__blackjack__,
danke erstmal für die ausführliche Antwort und die Erklärungen. Hab leider länger nicht rein geschaut, deswegen erst jetzt die Antwort.
Ich habe den letzten Wert immer in eine neue Zeile geschrieben, weil der nicht bei jedem Versuch vorhanden ist. Somit würde die Struktur dann kaputt gehen, wenn man ihn in die gleiche schreibt (ist immer der letzte in der txt-datei und dann fehlt der Zeilenumbruch). Deswegen ist das auch nicht gut in ner txt Datei zu machen denke ich, aber wusste noch nichts anderes.

Danke für den Tipp mit den Namen, wir hatten das in der Uni so gemacht und habe das dann übernommen. Wenn jemand anderes das Skript verstehen will ist das natürlich sehr schlecht. Werde ich in Zukunft anders machen.

Die eigentlich funktionsweise von lstrip() / rstrip habe ich erst bei dem Code für einen anderen Versuch verstanden. Man muss jeden Buchstaben nur einmal angeben, der "gestript" werden soll. Nur habe ich nicht ganz verstanden, wie man festlegt wie weit gestript wird? Habe auch die Erklärung in der Dokumentation nicht ganz verstanden. Das mit strip habe ich mir im nachhinein schon fast gedacht, wollte dann aber nicht nochmal alles ändern haha. Das mit split() war mir schon klar, dass der dann an jedem Punkt, wo das Zeichen vorkommt trennt und man dann mit [*] die jeweiligen Abschnitte ansprechen kann. Wusste nicht, dass es auch partition() gibt.
Hatten in der Uni wieder nur split() für ein Datum benutzt.

Danke für den Code mit csv, den werde ich mir Zuhause dann mal genauer anschauen und versuchen zu verstehen.

Grüße Mekkes
Antworten