Seite 1 von 1

Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 14:19
von forumseeker
Hallo!

Ich bein zwar kein Informatiker, aber programmier-affin. Ich habe 2 Excel-Dateien mit ca. 10.000 Zeilen, die verglichen werden müssen. XLWINGS dauert mir zu lange (obwohl es nachts läuft und es mir eingentlich egal sein könnte) OPENPYXL zeigt zwar Änderungen an, nicht aber Änderungen der Formelwerte, also Änderungen in der Zelle bei gleichbleibender Formel. Was kann ich sonst noch tun?

Gruss,
Andreas

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 14:36
von sparrow
Bei openpyxl kannst du beim Öffnen des Workbooks den Parameter "data_only" setzen. Dann werden nicht die Formeln sondern die Werte verwendet. Und zwar die Werte, die Excel bekannt waren, als die Datei das letzte Mal geschrieben wurde.
Und meiner Erfahrung nach solltest du read_only auf True setzen damit es schneller geht.

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 14:58
von __blackjack__
@forumseeker: Hm das ist doch jetzt irgendwie die gleiche Frage die Du schon mal gestellt hast: viewtopic.php?p=431144

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 15:39
von forumseeker
__blackjack__ hat geschrieben: Samstag 26. Oktober 2024, 14:58 @forumseeker: Hm das ist doch jetzt irgendwie die gleiche Frage die Du schon mal gestellt hast: viewtopic.php?p=431144
Jepp. Aber xlwings war mir doch zu langsam, und data_only bei openpyxl hat nicht funktioniert...

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 15:49
von sparrow
Was heißt denn "nicht funktioniert"?
Das wäre die Standardlösung.

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 16:58
von nezzcarth
Lassen die Tabellen es zu, dass man die als CSV exportieren und dann mit Standardtools (diff, comm, meld, etc.) vergleichen könnte oder geht das wegen Formeln etc. nicht?

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 17:28
von DeaD_EyE
Das würde ich auch so machen. Dann hat man das Problem mit den vielen Bibliotheken nicht. Ich bin zwar kein Excel-Spezialist, aber es würde mich wundern, wenn man bei Excel den Export einer Tabelle nicht auf einen Bereich festlegen kann. Daten, die aus Formeln hervorgehen, sollten sich auch exportieren lassen.

Es gibt Leute, die Spiele in Excel programmiert haben :-D

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 17:43
von forumseeker
ist nicht rausgegangen, aber der Tipp mit dem "Data_only" hat funktioniert. Wahrscheinlich habe ich beim letzten Mal was falsch gemacht. Wie gesagt, ich bin Anfänger. Gerade deshalb: Vielen Dank!!

Gruss,
Andreas

Re: Dateien vergleichen

Verfasst: Samstag 26. Oktober 2024, 19:31
von Dennis89
Hallo,

du kannst auch gerne mal deinen Code zeigen, eventuell lassen sich da auch ein paar Sachen verbessern.

Grüße
Dennis

Re: Dateien vergleichen

Verfasst: Sonntag 27. Oktober 2024, 09:39
von forumseeker
Danke! das ist er:

Code: Alles auswählen

import time
import openpyxl
import os
import helper
from openpyxl.styles import PatternFill
from openpyxl.formatting.formatting import ConditionalFormattingList

# Startzeit ermitteln
start = time.time()

change = False
number = []
anzahl = 0
FOLDER = os.path.join(os.path.dirname(__file__), "Sicherung\\")
MARKER_COLOR = PatternFill(fgColor='00008080', fill_type='solid')
NASFOLDER = "N:\\HA\\Results\\"

files = os.listdir(FOLDER)
files.sort(reverse=True)

print(f"alter file: {files[1]}")
print(f"neuer file: {files[0]}")

old_file = openpyxl.load_workbook(os.path.join(FOLDER, files[1]), data_only=True)
old_sheet = old_file["Geräteliste"]
new_file = openpyxl.load_workbook(os.path.join(FOLDER, files[0]), data_only=True)
new_sheet = new_file["Geräteliste"]
new_sheet.conditional_formatting = ConditionalFormattingList()

cell = [cell for cell in new_file["Geräteliste"]["B"] if cell.value is not None][-1]

MAX_ROW = cell.row
MAX_COLUMN = max(old_sheet.max_column, new_sheet.max_column)

timestamp = str(time.strftime("%Y%m%d_%H%M%S"))
outfile = timestamp + "_results.xlsx"
outfolder = os.path.join(os.path.dirname(__file__), "Results")
outfile_all_tmp = outfolder + "\\results_all_tmp.txt"
outfile_all = outfolder + "\\results_all.txt"

for col_idx in range(3, MAX_COLUMN + 1):
       
    for row_idx in range(3, MAX_ROW + 1):
        old_cell_value = old_sheet.cell(column=col_idx, row=row_idx).value
        new_cell_value = new_sheet.cell(column=col_idx, row=row_idx).value
        
        if old_cell_value != new_cell_value:
            new_sheet.cell(column=col_idx, row=row_idx).fill = MARKER_COLOR 
            change = True

            if row_idx not in number:
                anzahl = anzahl + 1
            number.append(row_idx)

            with open(outfile_all_tmp, "a", encoding="utf-8") as file:
                file.write(f"Datei: {files[0]} Spalte: {col_idx}, Zeile: {row_idx}, Änderung von : {old_cell_value} in :{new_cell_value} \n")

# Endzeit bestimmen
end = time.time()

verstrichene_zeit = end - start

if change: 
    with open(outfile_all_tmp, "a", encoding="utf-8") as file:
        file.write(f"Änderung bei {anzahl} Geräten\n")
        file.write(f'Ausführungszeit: {int(verstrichene_zeit)} Sekunden\n')
        file.write(f"\n")
    new_file.save(os.path.join(outfolder, outfile))
    new_file.close()

# Dateihalt umkehren

    inhalt = open(outfile_all_tmp,'r').readlines()
    inhalt.reverse()
    ausgabe = open(outfile_all,'w')
    ausgabe.writelines(inhalt)
    
    os.system(f"copy {outfolder}\\{outfile} {NASFOLDER}")
    os.system(f"copy {outfile_all} {NASFOLDER}")
Gruss,
Andreas

Re: Dateien vergleichen

Verfasst: Sonntag 27. Oktober 2024, 12:12
von __blackjack__
@forumseeker: `helper` wird importiert, aber nirgends verwendet.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Es macht wenig Sinn Funktionen wie `os.path.join()` zu verwenden und dann doch selbst noch wieder hart in den Code geschriebene Pfadtrenner zu verwenden, die nur unter Windows funktionieren. Und dann auch noch Pfadteile mit `+` zusammenzusetzen. Ausserdem verwendet man in neuem Code besser `pathlib` statt `os.path` & Co.

`time.time()` ist für Zeitmessungen nicht wirklich geeignet. Dafür gibt es `time.monotonic()`.

Sowohl `change` als auch `number` sind überflüssig. Wenn sich nichts geändert hat ist `number` leer und die Anzahl entspricht der Anzahl der Elemente in `number`. `number` sollte dementsprechend auch `numbers` heissen, denn das steht ja nicht für *eine* Zahl sondern für (potentiell) mehrere. Ausserdem sollte das eine Menge und keine Liste sein. Und es sollte nicht so verdammt weit vor der Schleife definiert werden in der das letztendlich verwendet wird. Das macht das Programm unübersichtlicher und schwerer Funktionen heraus zu ziehen, wenn die über den gesamten Code verstreut sind.

`numbers` könnte einen etwas aussagekräftigeren Namen bekommen.

`outfile_all` und `outfile` sind auch zu früh definiert. An der Stelle ist noch gar nicht entschieden ob die Werte überhaupt benötigt werden. `file` ist auch ungünstig als Name, denn bei einem `file` erwartet der Leser ein Dateiobjekt und keine Zeichenkette mit einem Pfad (oder ein `Path`-Objekt).

Bei der Auflistung der Dateien könnte/sollte man Verzeichnisse ausfiltern und/oder mit `glob()` nach Dateinamenserweiterungen filtern.

`MAX_ROW` und `MAX_COLUMN` sind keine Konstanten, sollten also auch nicht so geschrieben werden.

`time.strftime()` liefert eine Zeichenkette, da macht es keinen Sinn noch mal `str()` drauf los zu lassen.

`row_idx` und `col_idx` würde ich nicht Index nennen. Indexwerte fangen in Python immer bei 0 an, die Zeilen- und Spaltennummern aber bei 1.

Die temporäre Ausgabedatei würde ich auslassen, oder sind die Tabellen tatsächlich so gross, dass diese Informationen nicht in den Arbeitsspeicher passen?

Bei den beiden `open()` zum Schluss wird weder die Kodierung angegeben, noch werden die Dateien wieder geschlossen. Da aber mindestens mal bei „Geräte“ ein „ä“ vorkommt ist die Kodierungsangabe wichtig, und für das anschliessende Kopieren der Datei ist das schliessen wichtig, denn sonst sind da eventuell nicht alle Zeilen in der Datei.

`os.system()` sollte man nicht verwenden. Die Dokumentation verweist auf das `subprocess`-Modul. Aber auch das braucht man nicht um Dateien zu kopieren. Dafür gibt es in der Standardbibliothek etwas im `shutil`-Modul.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import time
from pathlib import Path
from shutil import copy

import openpyxl
from openpyxl.formatting.formatting import ConditionalFormattingList
from openpyxl.styles import PatternFill

SELF_PATH = Path(__file__).absolute().parent
FOLDER_PATH = SELF_PATH / "Sicherung"
OUTPUT_FOLDER_PATH = SELF_PATH / "Results"
NAS_FOLDER_PATH = Path(R"N:\HA\Results")
MARKER_COLOR = PatternFill(fgColor="00008080", fill_type="solid")
DEVICES_SHEET_NAME = "Geräteliste"


def main():
    start_time = time.monotonic()
    #
    # TODO Fällt unschön auf die Nase wenn nicht mindestens zwei Dateien
    #   vorhanden sind, ausserdem wäre ein glob() auf die Dateinamensendung
    #   vielleicht auch eine gute Idee.
    #
    old_file_path, new_file_path = sorted(
        path for path in FOLDER_PATH.iterdir() if path.is_file()
    )[-2:]

    print(f"alte Datei: {old_file_path.name}")
    print(f"neue Datei: {new_file_path.name}")

    old_sheet = openpyxl.load_workbook(old_file_path, data_only=True)[
        DEVICES_SHEET_NAME
    ]
    new_workbook = openpyxl.load_workbook(new_file_path, data_only=True)
    new_sheet = new_workbook[DEVICES_SHEET_NAME]
    new_sheet.conditional_formatting = ConditionalFormattingList()
    #
    # TODO Fehlerbehandlung falls Spalte "B" komplett leer ist.
    #
    max_row = next(
        cell for cell in reversed(new_sheet["B"]) if cell.value is not None
    ).row
    changed_rows_numbers = set()
    change_info_lines = []
    for column_number in range(
        3, max(old_sheet.max_column, new_sheet.max_column) + 1
    ):
        for row_number in range(3, max_row + 1):
            old_cell = old_sheet.cell(column=column_number, row=row_number)
            new_cell = new_sheet.cell(column=column_number, row=row_number)
            if old_cell.value != new_cell.value:
                new_cell.fill = MARKER_COLOR
                changed_rows_numbers.add(row_number)
                change_info_lines.append(
                    f"Datei: {new_file_path.name}"
                    f" Spalte: {column_number},"
                    f" Zeile: {row_number},"
                    f" Änderung von: {old_cell.value}"
                    f" in: {new_cell.value}\n"
                )

    verstrichene_zeit = time.monotonic() - start_time

    if changed_rows_numbers:
        result_workbook_path = OUTPUT_FOLDER_PATH / (
            time.strftime("%Y%m%d_%H%M%S") + "_results.xlsx"
        )
        new_workbook.save(result_workbook_path)
        new_workbook.close()

        change_info_file_path = OUTPUT_FOLDER_PATH / "results_all.txt"
        change_info_lines.extend(
            [
                f"Änderung bei {len(changed_rows_numbers)} Geräten\n",
                f"Ausführungszeit: {int(verstrichene_zeit)} Sekunden\n",
                "\n",
            ]
        )
        with change_info_file_path.open("w", encoding="utf-8") as file:
            file.writelines(reversed(change_info_lines))

        for source_path in [result_workbook_path, change_info_file_path]:
            copy(source_path, NAS_FOLDER_PATH)


if __name__ == "__main__":
    main()
Nächster Schritt wäre es da sinnvoll Funktionen heraus zu ziehen. Die `main()` ist für meinen Geschmack zu umfangreich.

Re: Dateien vergleichen

Verfasst: Sonntag 27. Oktober 2024, 14:36
von forumseeker
Danke!!