*.csv Dateien lesen und Inhalt zu *.xlsx Datei hinzufügen

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
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend zusammen,

ich muss euch nochmals um Hilfe bitten.

Ich habe mehrere '.csv' Dateien mit folgendem beispielhaften Inhalt:

Code: Alles auswählen

hour,minute
2,30
5,10
9,50
Diese befinden sich auf einem USB-Stick. Beim einstecken des Sticks in einen Raspberry möchte ich gerne, dass der Inhalt der CSV-Dateien in eine vorhandene Excel-Datei geschrieben wird. Vorausgesetzt der Inhalt steht nicht schon drin.
Das mit dem USB-Stick habe ich schon hinbekommen. Gerade versuche ich einen Code zu schreiben, der die *.csv-Dateien ausliest, die Zeit darin zusammenzählt und dann in die Excel schreibt.
Mein Problem dabei ist, das ich es nicht hinbekomme in der vorhandenen Datei die Werte in die nächsten leeren Zellen zu schreiben. Als letztes habe ich es noch mit 'pandas' und 'openpyxl' in Kombination versucht. Aber ich schaffe es leider nicht. Die Test-Excel-Datei lässt sich mittlerweile nicht mehr öffnen.
Ich habe zwar nicht das ganze Internet durchsucht, aber eine Menge Anleitungen gefunden, jedoch sind wird in sehr vielen einen neue xlsx-Datei erstellt und die anderen die für mich vertrauenswürdig aussahen brachten mich nicht zum Erfolg.

Es "muss" in Excel sein, da die Person die mit den Daten später arbeitet das so am liebsten hat und ich froh bin, dass das jemand für mich übernimmt.
Meine Alternative, die ich zu 99,5378% auch hinbekomme wäre, dass Excel mittels VBA die Werte aus den *.csv Dateien selbst ausliest, ABER ich habe allein in den folgenden Python-Code schon sehr viel Zeit reingesteckt, das wäre für mich sehr schade und deprimierend wenn ich den wegwerfen müsste. (Auch wenn es wahrscheinlich für euch nichts besonderes ist)

Also soweit bin ich, darin enthalten ist der Versuch aus dem letzten Forumsbeitrage den ich zur Problemlösung versucht hatte:

Code: Alles auswählen

#!/usr/bin/env python3

import csv
import pandas as pd
from openpyxl import load_workbook
from pathlib import Path

CSV_PATH = Path.home() / 'Dokumente' / 'Python' / 'Python_Excel'
EXCEL_PATH = Path('/media/Dennis/Arbeitszeiten/Arbeitszeiten.xlsx')

def read_time(csv_file):
    hours = []
    minutes = []
    with open(csv_file, newline='') as csvfile:
        csvreader = csv.DictReader(csvfile)
        for row in csvreader:
            hours.append(int(row['hour']))
            minutes.append(float(row['minute']))
        total_hours = sum(hours)
        total_minutes = sum(minutes)
        
        if total_minutes >= 60:
            new_hour, new_minutes = calculate_minutes(total_minutes)
            total_hours += new_hour
            total_minutes = new_minutes
            
        return total_hours, total_minutes
    
def calculate_minutes(minutes):
    hour = round(minutes / 60, 2)
    minute = round(int(str(hour)[-2:]) * 0.6)
    hour = int(str(hour)[:-3])
    return hour, minute

def search_files():
    files = []
    for file in CSV_PATH.iterdir():
        files.append(file)
    return files

def write_in_excel(csv_file, hours, minutes):
    order = str(csv_file)[:-4]
    working_time = f'{hours},{minutes}'
    writing_data = pd.DataFrame({'Auftrag':[order], 'Arbeitszeit':[working_time], 'Erledigt':['']})
    
    with pd.ExcelWriter(EXCEL_PATH, engine='openpyxl', mode='a') as writer:
        writing_data.to_excel(writer, sheet_name="2021", cols=['Auftrag', 'Arbeitszeit', 'Erledigt'])
        

def main():
    csv_files =search_files()
    for csv_file in csv_files:
        hours, minutes = read_time(csv_file)
        write_in_excel(csv_file.name, hours, minutes)


if __name__ == '__main__':
    main()
Die Fehlermeldung dazu:

Code: Alles auswählen

Traceback (most recent call last):
  File "/home/dennis/Dokumente/excel_test.py", line 58, in <module>
    main()
  File "/home/dennis/Dokumente/excel_test.py", line 54, in main
    write_in_excel(csv_file.name, hours, minutes)
  File "/home/dennis/Dokumente/excel_test.py", line 46, in write_in_excel
    with pd.ExcelWriter(EXCEL_PATH, engine='openpyxl', mode='a') as writer:
  File "/home/dennis/.local/lib/python3.9/site-packages/pandas/io/excel/_openpyxl.py", line 41, in __init__
    self.book = load_workbook(self.handles.handle)
  File "/home/dennis/.local/lib/python3.9/site-packages/openpyxl/reader/excel.py", line 315, in load_workbook
    reader = ExcelReader(filename, read_only, keep_vba,
  File "/home/dennis/.local/lib/python3.9/site-packages/openpyxl/reader/excel.py", line 124, in __init__
    self.archive = _validate_archive(fn)
  File "/home/dennis/.local/lib/python3.9/site-packages/openpyxl/reader/excel.py", line 96, in _validate_archive
    archive = ZipFile(filename, 'r')
  File "/usr/lib64/python3.9/zipfile.py", line 1257, in __init__
    self._RealGetContents()
  File "/usr/lib64/python3.9/zipfile.py", line 1324, in _RealGetContents
    raise BadZipFile("File is not a zip file")
zipfile.BadZipFile: File is not a zip file
Vielen Dank und Grüße
Dennis

PS: Der Teil mit dem USB-Stick ist in einer anderen .py-Datei. Ich versuche erst immer die Funktionen getrennt zum laufen zu bekommen, bis ich sie dann zusammen füge.
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Ich konnte meinen Eingangspost nicht bearbeiten, deswegen der Doppelpost hier.
Gerade habe ich das Thema 'Pandas' hier im Forum entdeckt, bei dem Name hätte ich nicht damit gerechnet das es mir weiter hilft.
Ich kann jetzt in die Excel-Datei schreiben, allerdings wird das vorhandene überschrieben. Wenn ich 'w' durch 'a' ersetze ändert das leider auch nichts. Der Code an sich ist gleich nur diese Funktion hat sich geändert:

Code: Alles auswählen

def write_in_excel(csv_file, hours, minutes):
    order = str(csv_file)[:-4]
    working_time = f'{hours},{minutes}'
    writing_data = pd.DataFrame({'Auftrag':[order], 'Arbeitszeit':[working_time], 'Erledigt':['']})
    
    book = load_workbook(EXCEL_PATH)
    writer = pd.ExcelWriter(EXCEL_PATH, engine = 'openpyxl', mode='a',)
    writer.book = book
    
    writer.sheets = dict((ws.title, ws) for ws in book.worksheets)
    writing_data.to_excel(writer, "2021",)

    writer.save()
    writer.close()
Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Alles einlesen, aendern, nochmal schreiben.

In meiner gesamten Karriere habe ich vielleicht 10 mal append benutzt.
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend __deets__,

danke für die schnelle Antwort. Du meinst, ich soll die Excel-Datei erst auslesen, die ausgelesenen Daten zu meinen neuen Daten hinzufügen und dann alle wieder schreiben?

Hat 'append' irgendwelche Nachteile? Ich benutze das eigentlich immer, das konnte ich mir gut merken. Wenn das aber nicht üblich ist, will ich es mir wieder abgewöhnen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die allerwenigsten Formate erlauben es. CSV ist im Grunde das einzige. Und dann musst du auch noch sicher sein, dass das letzte Zeichen ein \n ist, oder nicht. Das ist alles viel zu fummelig. Einlesen, anhaengen, alles wegschreiben.
Benutzeravatar
__blackjack__
User
Beiträge: 14054
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Nur damit keine Missverständnisse entstehen: Bei Exceldateien *muss* man neu schreiben, das Dateiformat unterstützt kein Anhängen an die Datei. Das meint aber wohl der `mode` "a" vs. "w" hier auch gar nicht, denn da geht es nicht um das low-level Dateiobjekt, sondern um das Pandas-`ExcelWriter`-Objekt.

Bei `csv`-Dateien halte ich den Dateimodus "a" für wenig problematisch sofern man nicht davon ausgeht dass da jemand kaputtes CSV ablegt. Ansonsten ist "a" nützlich bei Logdateien und beim JSON Lines-Format und bei YAML. Und auch ein paar andere, nicht so verbreitete Serialisierungsformate erlauben es mehrere Werte/Objekte einfach nacheinander weg in eine Datei zu schreiben.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für eure Erklärungen.

Ich habe jetzt die Excel ausgelesen und die *.csv-Dateien auch und meiner Meinung nach füge ich die Daten auch zusammen und schreibe sie dann in die Excel. Python sieht das wohl anders.

Ich habe in der Excel-Tabelle zwei Spalten. In der ersten Reihe befindet sich die Überschrift. Dann habe ich in die zweite und dritte Reihe von Hand Test-Daten geschrieben.
Weiters habe ich zwei *.csv-Dateien angelegt, die sich vom Dateienname als auch von Inhalt mit den Daten aus der Excel-Datei unterscheiden. Ich erwarte also dass nach dem Programmdurchlauf 5 Reihen in Excel beschrieben sind.
Ich hatte aber nur 2 beschriebene Reihen. In der ersten wieder die Überschrift in der zweiten die Werte aus der "letzten" *.csv-Datei. Und ich hatte ganz links eine zusätzliche Spalte, mit der überschrift "Auftrag". Also waren drei Spalten anstatt zwei vorhanden.

Ich verstehe auch nicht wirklich den Aufbau bzw. die richtige Verwendung von dem pandas-DataFrame. Ich vermute dass da mein Fehler liegt?

Code: Alles auswählen

#!/usr/bin/env python3

import csv
import xlrd
import pandas as pd
import numpy as np
from openpyxl import load_workbook
from pathlib import Path

CSV_PATH = Path.home() / 'Dokumente' / 'Python' / 'Python_Excel'
EXCEL_PATH = Path('/media/HugosWolke/Nici und Dennis/CENTER Engineering/Arbeitszeiten/Arbeitszeit.xlsx')

def read_time(csv_file):
    hours = []
    minutes = []
    with open(csv_file, newline='') as csvfile:
        csvreader = csv.DictReader(csvfile)
        for row in csvreader:
            hours.append(int(row['hour']))
            minutes.append(float(row['minute']))
        total_hours = sum(hours)
        total_minutes = sum(minutes)
        
        if total_minutes >= 60:
            new_hour, new_minutes = calculate_minutes(total_minutes)
            total_hours += new_hour
            total_minutes = new_minutes
            
        return total_hours, total_minutes
    
def calculate_minutes(minutes):
    hour = round(minutes / 60, 2)
    minute = round(int(str(hour)[-2:]) * 0.6)
    hour = int(str(hour)[:-3])
    return hour, minute

def search_files():
    files = []
    for file in CSV_PATH.iterdir():
        files.append(file)
    return files

def read_excel():
    book = xlrd.open_workbook(EXCEL_PATH, encoding_override = "utf-8")
    sheet = book.sheet_by_index(0)
    order = np.asarray([sheet.cell(i, 0).value for i in range(1, sheet.nrows)])
    working_time = np.asarray([sheet.cell(i, 1).value for i in range(1, sheet.nrows)])
    return order, working_time

def write_in_excel(csv_file, order, working_time, hours, minutes):
    new_order = str(csv_file)[:-4]
    new_working_time = f'{hours},{minutes}'
    np.append(order, new_order)
    np.append(working_time, new_working_time)
    writing_data = pd.DataFrame({'Auftrag':[new_order], 'Arbeitszeit':[new_working_time]})
    
    book = load_workbook(EXCEL_PATH)
    writer = pd.ExcelWriter(EXCEL_PATH, engine = 'openpyxl', mode='w',)
    writer.book = book
    
    writer.sheets = dict((ws.title, ws) for ws in book.worksheets)
    writing_data.to_excel(writer, "2021",)

    writer.save()
    writer.close()


def main():
    csv_files = search_files()
    for csv_file in csv_files:
        hours, minutes = read_time(csv_file)
        order, working_time = read_excel()
        write_in_excel(csv_file.name, order, working_time, hours, minutes)


if __name__ == '__main__':
    main()
Danke und einen schönen Abend noch
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Funktion

Code: Alles auswählen

def calculate_minutes(minutes):
    hour = round(minutes / 60, 2)
    minute = round(int(str(hour)[-2:]) * 0.6)
    hour = int(str(hour)[:-3])
    return hour, minute
funktioniert nicht. `round` kann man selten sinnvoll einsetzen. Meist will man ja nur eine bestimmte Ausgabe, dann ist Stringformatierung das Richtige.
`str` liefert nicht immer zwei Nachkommastellen, trotz des round, kann die Stringstückelung auch den Dezimalpunkt enthalten und int funktioniert nicht mehr.
Minuten in Dezimalstunden umzurechnen um das dann wieder zurückzurechnen ist sehr umständlich und Du hast Glück, dass es keine Rundungsfehler gibt.
Wenn man was aus der String-Repräsentation einer Zahl etwas herauspulen will, macht man etwas falsch.
Du suchst divmod:

Code: Alles auswählen

def calculate_minutes(minutes):
    return divmod(minutes, 60)
Insgesamt solltest Du gleich komplett in Minuten rechnen (hast Du überhaupt Decimalminuten)?

Code: Alles auswählen

def read_time(csv_file):
    with open(csv_file, newline='') as csvfile:
        csvreader = csv.DictReader(csvfile)
        total_minutes = sum(
            int(row['hour']) * 60 + int(row['minute'])
            for row in csvreader)
        return total_minutes
Soll als Arbeitszeit wirklich ein String mit Komma getrennt "Stunde,Minute" in Excel geschrieben werden? Das kann in Excel ja ganz schnell zu einer Zahl werden und damit zu falschen Dezimalstunden.

Warum benutzt Du pandas zum Schreiben, aber nicht zum Lesen?
Bzw. umgekehrt, pandas ist nicht das richtige hier.
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo Sirius3,

danke für deine Antwort.

Zu 'calculate_minutes' kann ich nur sagen, das ich mit meinem Testdaten dann wohl Glück gehabt habe. So weit wie du habe ich das noch nicht betrachtet. Das werde ich ändern.

Es werden nur ganze Minuten ausgelesen, die Runderei kam nur von der Umrechnerei.

Wenn ich die Excel-Datei öffne und mit den geschriebenen String ansehe, dann steht da ein ' vor der ersten Zahl, also zum Beispiel '17,10. Das ist nicht schön, war von mir auch nicht so gedacht, aber zumindest wird es so zu keiner Zahl. Hast du eine sinnvollere Idee? Ich könnte die Zellen als "Zeit" vorformatieren, aber ob das nach dem schreiben dann noch so ist?

Zu deiner letzten Frage: Da ich sowas noch nie gemacht habe, habe ich mit funktionierenden Codestücke, die ich gefunden habe, experimentiert. Mir fehlt hier etwas die klare Struktur wie man so etwas macht. Bis jetzt muss ich für diese, wie ich dachte einfache Aufgabe, drei Module importieren. Da blick ich nicht mehr durch.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Frage ist ja, in welcher Form die Zeit vorliegen muß.
Möglich wären ein String, dann werden die Minuten aber per : abgetrennt.
Als Datumsfeld in Excel formatiert (das wären die Zeit umgerechnet in Dezimaltage 7:30 -> 0,3125).
Als Zahl in Dezimalstunden, also 7:30 --> 7,5
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für die Antwort. Dann werde ich beim String bleiben und den Doppelpunkt einfügen.

Kann ich für die Schreiberei in Excel dann numpy und xlrd verwenden oder hat jemand noch ein Modul, mit dem ich alles abdecken kann?

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Da Du nur unendlich komplizierten Code zeigst, frag ich mal direkt, ob Du schon die einfachste Möglichkeit ausprobiert hast?

Code: Alles auswählen

from openpyxl import load_workbook
book = load_workbook(EXCEL_PATH)
sheet = book.get_sheet_by_name('2021')
sheet.append([order, working_time])
book.save(EXCEL_PATH)
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo Sirius3,

mir wäre es lieber ich hätte keinen komplizierten Code geschrieben, aber ich arbeite daran.

Mit 'openpyxl' hatte ich auch mal einen Code versucht, da ich aber zwischen so vielen Anleitungen hin und her gesprungen bin, weis ich nicht mehr auswendig, wie der Code genau aussah.
Sobald ich Feierabend habe und daheim bin teste ich deinen Vorschlag aus. Vielen Dank dafür.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich melde mich zurück und muss sagen, die einfachste Möglichkeit funktioniert.
Zur Info, es gibt eine Erneuerung in 'load_workoob' aus

Code: Alles auswählen

sheet = book.get_sheet_by_name('2021')
wird jetzt

Code: Alles auswählen

sheet = book['2021']
Auch die übrigen Verbesserung von @Sirius3 hab ich eingebaut und es gefällt mir sehr gut.

Jetzt habe ich die Funktion noch mit zwei if-Abfragen erweitert, da ich keine doppelten Einträge haben will. Wenn sich bei einem Auftrag nur die Zeit geändert hat, dann soll nur die neue Zeit aktualisiert werden. Das sieht bis jetzt so aus:

Code: Alles auswählen

def write_in_excel(csv_file, working_time):
    write_data = True
    count_row = 0
    order = str(csv_file)[:-4]
    working_time = f'{working_time[0]}:{working_time[1]}'
    book = load_workbook(EXCEL_PATH)
    sheet = book['2021']
    for row in sheet.values:
        count_row += 1
        if order and working_time in row:
            book.save(EXCEL_PATH)
            write_data = False
            break
        elif order in row:
            sheet[f'B{count_row}'] = working_time
            book.save(EXCEL_PATH)
            write_data = False
            break
    if write_data:    
        sheet.append([order, working_time])
        book.save(EXCEL_PATH)
Ich habe wieder das dumme Gefühlt, dass das kompakter geht?

Wenn das kompakter geht, dann möchte ich die Excel um eine Spalte erweitern. Ist in dieser ein 'X' eingetragen, dann wird die entsprechende *.csv-Datei gelöscht, erledigte Aufträge müssen ja nicht jedes mal eingelesen werden.

Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Hatte noch einen dummen Fehler drin, 'if'-Abfrage mit 'in' bringt natürlich "falsche" Informationen.

Code: Alles auswählen

def write_in_excel(csv_file, working_time):
    write_data = True
    check = 'X'
    count_row = 0
    order = str(csv_file)[:-4]
    working_time = f'{working_time[0]}:{working_time[1]}'
    book = load_workbook(EXCEL_PATH)
    sheet = book['2021']
    for row in sheet.values:
        count_row += 1
        if row[0] == order and row[1] == working_time and row[2] == check:
            book.save(EXCEL_PATH)
            delete_csv(csv_file)
            write_data = False
            break
        elif row[0] == order:
            sheet[f'B{count_row}'] = working_time
            book.save(EXCEL_PATH)
            write_data = False
            break
    if write_data:    
        sheet.append([order, working_time])
        book.save(EXCEL_PATH)
        
def delete_csv(csv_file):
    run([f'rm {CSV_PATH}/{csv_file}'], shell=True, check=True)
Schade das man(ich) den Beitrag nicht editieren kann und immer Doppelposts machen muss. Aber wenn ich euch das nicht zeige, denkt ihr noch: Der checkt nicht mal 'if'-Abfragen :lol:

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Man benutzt kein shell=True bei run, und für triviale Operationen benutzt man erst gar kein run.

Code: Alles auswählen

def delete_csv(csv_file):
    (CSV_PATH / csv_file).unlink()
Wenn man einen Index braucht, benutzt man enumerate.
Wenn in jedem Zweig book.save aufgerufen wird, dann reicht das einmal im Code stehen zu haben.

Code: Alles auswählen

def write_in_excel(csv_file, working_time):
    order = Path(csv_file).stem
    working_time = f'{working_time[0]}:{working_time[1]}'
    book = load_workbook(EXCEL_PATH)
    sheet = book['2021']
    for index, row in enumerate(sheet.values, 1):
        if row[0] == order:
            if row[1] == working_time and row[2] == 'X':
                (CSV_PATH / csv_file).unlink()
            else:
                sheet[f'B{index}'] = working_time
            break
    else:
        sheet.append([order, working_time])
    book.save(EXCEL_PATH)
Benutzeravatar
Dennis89
User
Beiträge: 1555
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend Sirius3,

vielen Dank für deine Hilfe und für die Verbesserungen. Das funktioniert soweit jetzt alles.
Habe gerade noch die einzelnen Dateien zu einem Projekt "zusammen gesetzt" und das läuft eigentlich soweit auch.

Ich weis nicht wie es bei euch so üblich ist, aber wenn jemand Lust hat würde ich meine fertigen Codes gern in einem neuen Thema posten um zu sehen was ich noch verbessern muss :)

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten