From 115d83c3635ca710ed3149a94d2d308c18697c41 Mon Sep 17 00:00:00 2001 From: Floke Date: Thu, 17 Apr 2025 09:39:57 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 411 +++++++++++++++++++----------------------- 1 file changed, 184 insertions(+), 227 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 5caa46ec..77d1dc5e 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -65,6 +65,9 @@ class Config: HTML_PARSER = "html.parser" BATCH_SIZE = 10 TOKEN_MODEL = "gpt-3.5-turbo" # Oder "gpt-4" etc. + MAX_SCRAPING_WORKERS = 10 # Threads für paralleles Website-Scraping + OPENAI_BATCH_SIZE_LIMIT = 8 # Max Texte pro OpenAI Call in summarize_batch_openai + UPDATE_BATCH_ROW_LIMIT = 50 # Zeilen sammeln für gebündelte Sheet Updates # Zentrales API-Key-Management API_KEYS = {} @@ -954,26 +957,23 @@ def token_count(text): class GoogleSheetHandler: def __init__(self): + # ... (init und _connect unverändert) ... self.sheet = None self.sheet_values = [] self.headers = [] try: self._connect() if self.sheet: - self.load_data() # Erste Datenladung bei Initialisierung + self.load_data() except Exception as e: - # Fehler bei Initialisierung bereits loggen und None zurückgeben? debug_print(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {e}") - # Hier sollte das Hauptprogramm den Fehler erkennen und abbrechen. - # Man könnte auch eine Exception werfen: raise ConnectionError(...) + raise ConnectionError(f"Google Sheet Handler Init failed: {e}") - # retry_on_failure Decorator sollte hier angewendet werden @retry_on_failure def _connect(self): - """Stellt Verbindung zum Google Sheet her.""" + # ... (unverändert) ... self.sheet = None debug_print("Verbinde mit Google Sheets...") - # Fehlerbehandlung innerhalb ist gut, aber raise am Ende, damit retry greift try: scope = ["https://www.googleapis.com/auth/spreadsheets"] creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope) @@ -988,181 +988,120 @@ class GoogleSheetHandler: debug_print(f"FEHLER bei der Google Sheets Verbindung: {type(e).__name__} - {e}") raise e - # retry_on_failure Decorator sollte hier angewendet werden @retry_on_failure def load_data(self): - """Lädt alle Daten aus dem Sheet und aktualisiert self.sheet_values.""" - if not self.sheet: - debug_print("Fehler: Keine Sheet-Verbindung zum Laden der Daten.") - self.sheet_values = [] - self.headers = [] - return False # Signalisiert Fehler + # ... (unverändert) ... + if not self.sheet: #... + return False debug_print("Lade Daten aus Google Sheet...") try: - # Hol die rohen Daten - raw_values = self.sheet.get_all_values() - - # Prüfe, ob überhaupt Daten zurückkamen - if not raw_values: - debug_print("Warnung: Google Sheet scheint leer zu sein oder keine Daten zurückgegeben.") - self.sheet_values = [] - self.headers = [] - return True # Kein Fehler beim Laden, aber keine Daten - - self.sheet_values = raw_values # Speichere die kompletten Daten - - # Setze Header basierend auf der ersten Zeile - if len(self.sheet_values) >= 1: - self.headers = self.sheet_values[0] - else: - self.headers = [] # Sollte nicht passieren, wenn raw_values nicht leer war - + self.sheet_values = self.sheet.get_all_values() + if not self.sheet_values: #... + return True + if len(self.sheet_values) >= 1: self.headers = self.sheet_values[0] + else: self.headers = [] debug_print(f"Daten neu geladen: {len(self.sheet_values)} Zeilen insgesamt.") - return True # Signalisiert Erfolg - except gspread.exceptions.APIError as e: - debug_print(f"Google API Fehler beim Laden der Sheet Daten: Status {e.response.status_code} - {e.response.text[:200]}") - # self.sheet_values = [] # Im Fehlerfall alte Daten behalten oder leeren? Besser behalten. - # self.headers = [] - raise e # Damit retry greift - except Exception as e: - debug_print(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}") - # self.sheet_values = [] - # self.headers = [] - raise e # Damit retry greift - # return False # Wird nur bei Exception erreicht, die nicht weitergeworfen wird + return True + except Exception as e: #... + raise e def get_data(self): - """Gibt die aktuell im Handler gespeicherten Daten zurück (ohne die ersten 5 Header-Zeilen).""" - header_rows = 5 # Definiert die Anzahl der zu überspringenden Header-Zeilen - if not self.sheet_values or len(self.sheet_values) <= header_rows: - # Logge nur, wenn sheet_values existiert aber zu kurz ist - if self.sheet_values: - debug_print(f"Warnung in get_data: Nur {len(self.sheet_values)} Zeilen vorhanden, weniger als {header_rows} Header-Zeilen erwartet.") - return [] - # Gibt eine Slice der Liste zurück, die die Datenzeilen enthält + # ... (unverändert) ... + header_rows = 5 + if not self.sheet_values or len(self.sheet_values) <= header_rows: return [] return self.sheet_values[header_rows:] def get_all_data_with_headers(self): - """Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurück.""" - if not self.sheet_values: - debug_print("Warnung in get_all_data_with_headers: Keine Daten im Handler gespeichert.") + # ... (unverändert) ... + if not self.sheet_values: return [] return self.sheet_values def _get_col_letter(self, col_idx_1_based): - """ Konvertiert 1-basierten Spaltenindex in Buchstaben (A, B, ..., Z, AA, ...). """ - string = "" - n = col_idx_1_based - if n < 1: return None # Ungültiger Index - while n > 0: - n, remainder = divmod(n - 1, 26) - string = chr(65 + remainder) + string + # ... (unverändert) ... + string = ""; n = col_idx_1_based + if n < 1: return None + while n > 0: n, remainder = divmod(n - 1, 26); string = chr(65 + remainder) + string return string - def get_start_row_index(self, check_column_key, min_sheet_row=7): + # --- ANGEPASST: Sucht jetzt nach leerem String ODER 'k.A.' --- + def get_start_row_index(self, check_column_key, min_sheet_row=7, empty_values=None): """ Findet den Index der ersten Zeile (0-basiert für Daten nach Header), ab einer Mindestzeilennummer im Sheet, in der der Wert in der - Spalte (definiert durch check_column_key in COLUMN_MAP) fehlt oder leer ist. - Lädt die Daten vor der Prüfung neu. + Spalte (definiert durch check_column_key) als "leer" gilt. Args: check_column_key (str): Der Schlüssel in COLUMN_MAP für die zu prüfende Spalte. min_sheet_row (int): Die 1-basierte Zeilennummer im Sheet, ab der gesucht werden soll. + empty_values (list, optional): Eine Liste von Strings (lowercase), die als leer gelten sollen. + Standard ist ["", "k.a."]. Returns: int: Der 0-basierte Index in der Datenliste (ohne Header), oder -1 bei Fehler (z.B. Schlüssel nicht gefunden), oder der Index nach der letzten Zeile, wenn alle gefüllt sind. """ - # Lade Daten *vor* der Prüfung neu, um Aktualität sicherzustellen - if not self.load_data(): - debug_print("FEHLER beim Laden der Daten in get_start_row_index. Breche ab.") - return -1 # Fehlerindikator + if empty_values is None: + empty_values = ["", "k.a."] # Standardwerte, die als leer gelten + if not self.load_data(): return -1 # Fehlerindikator header_rows = 5 - data_rows = self.get_data() # Greift auf die neu geladenen Daten zu + data_rows = self.get_data() + if not data_rows: return 0 - if not data_rows: - debug_print("Keine Datenzeilen vorhanden für get_start_row_index nach Neuladen.") - return 0 # Index 0 signalisiert Start am Anfang (oder keine Daten) - - # Hole den Spaltenindex aus COLUMN_MAP check_column_index = COLUMN_MAP.get(check_column_key) if check_column_index is None: debug_print(f"FEHLER: Schlüssel '{check_column_key}' nicht in COLUMN_MAP gefunden!") - return -1 # Fehlerindikator + return -1 actual_col_letter = self._get_col_letter(check_column_index + 1) - - # Berechne den 0-basierten Startindex für die *Datenliste* data_rows search_start_index_in_data = max(0, min_sheet_row - header_rows - 1) - debug_print(f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} (Sheet-Zeile {search_start_index_in_data + header_rows + 1}) nach leerem Wert in Spalte '{check_column_key}' ({actual_col_letter}, Index {check_column_index}).") + debug_print(f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} (Sheet-Zeile {search_start_index_in_data + header_rows + 1}) nach leerem Wert (in {empty_values}) in Spalte '{check_column_key}' ({actual_col_letter}, Index {check_column_index}).") if search_start_index_in_data >= len(data_rows): debug_print(f"Start-Suchindex ({search_start_index_in_data}) liegt nach oder auf letzter Datenzeile ({len(data_rows)-1}). Alle vorherigen Zeilen scheinen gefüllt.") - return len(data_rows) # Index nach der letzten Zeile + return len(data_rows) - # Durchlaufe die Datenzeilen ab dem berechneten Startindex for i in range(search_start_index_in_data, len(data_rows)): row = data_rows[i] current_sheet_row = i + header_rows + 1 # Prüfe den Wert in der Zielspalte cell_value = None - is_empty = True + is_considered_empty = True # Annahme: Ist leer, bis Gegenteil bewiesen if len(row) > check_column_index: - cell_value = row[check_column_index] - # Prüft explizit auf None und leeren String nach strip() - if cell_value is not None and str(cell_value).strip(): - is_empty = False - # else: is_empty bleibt True, da Spalte nicht existiert + cell_value = str(row[check_column_index]).strip() # Immer als String behandeln und strippen + # Prüfe, ob der gestrippte Wert (lowercase) in der Liste der leeren Werte ist + if cell_value.lower() not in empty_values: + is_considered_empty = False + # else: is_considered_empty bleibt True (Spalte nicht vorhanden = leer) - # Debug Log für jede 1000ste Zeile oder relevante Übergänge - log_debug = (i == search_start_index_in_data or i % 1000 == 0 or current_sheet_row in [2121, 2122, 8926, 8927, 8928]) + log_debug = (i == search_start_index_in_data or i % 1000 == 0 or current_sheet_row in range(10050, 10060)) # Angepasste Log-Punkte if log_debug: - debug_print(f" -> Prüfe Daten-Index {i} (Sheet Zeile {current_sheet_row}): Wert in Spalte {actual_col_letter}='{cell_value}' -> Leer? {is_empty}") + debug_print(f" -> Prüfe Daten-Index {i} (Sheet Zeile {current_sheet_row}): Wert in Spalte {actual_col_letter}='{cell_value}' -> Gilt als leer? {is_considered_empty}") - if is_empty: - debug_print(f"Erste Zeile ab Zeile {min_sheet_row} ohne Wert in Spalte {actual_col_letter} gefunden: Zeile {current_sheet_row} (Daten-Index {i})") - return i # Gibt den 0-basierten Index *innerhalb der Datenliste* zurück + if is_considered_empty: + debug_print(f"Erste Zeile ab Zeile {min_sheet_row} mit leerem Wert (in {empty_values}) in Spalte {actual_col_letter} gefunden: Zeile {current_sheet_row} (Daten-Index {i})") + return i - # Wenn die Schleife durchläuft, sind alle Zeilen ab dem Start gefüllt last_index = len(data_rows) - debug_print(f"Alle Zeilen ab Daten-Index {search_start_index_in_data} (Sheet Zeile {search_start_index_in_data + header_rows + 1}) haben einen Wert in Spalte {actual_col_letter}. Nächster Daten-Index wäre {last_index}.") + debug_print(f"Alle Zeilen ab Daten-Index {search_start_index_in_data} haben einen nicht-leeren Wert in Spalte {actual_col_letter}. Nächster Daten-Index wäre {last_index}.") return last_index - # --- NEU HINZUGEFÜGTE METHODE --- - # retry_on_failure Decorator sollte hier angewendet werden @retry_on_failure def batch_update_cells(self, update_data): - """ - Führt ein Batch-Update im Google Sheet durch. Beinhaltet Fehlerbehandlung. - - Args: - update_data (list): Eine Liste von Dictionaries, jedes mit 'range' und 'values'. - z.B. [{'range': 'A1', 'values': [['Wert']]}, ...] - - Returns: - bool: True bei Erfolg, False bei Fehler nach Retries. - """ - if not self.sheet: - debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update.") + # ... (unverändert) ... + if not self.sheet: #... return False - if not update_data: - # debug_print("Keine Daten für Batch-Update vorhanden.") # Weniger Lärm - return True - + if not update_data: return True try: self.sheet.batch_update(update_data, value_input_option='USER_ENTERED') return True - except gspread.exceptions.APIError as e: - debug_print(f"Google API Fehler beim Batch-Update: Status {e.response.status_code} - {e.response.text[:500]}") - raise e - except Exception as e: - debug_print(f"Allgemeiner Fehler beim Batch-Update: {type(e).__name__} - {e}") + except Exception as e: #... raise e + # --- Ende GoogleSheetHandler Klasse --- @@ -2272,124 +2211,139 @@ def _process_batch(sheet, batches, row_numbers): def process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet): """ Batch-Prozess NUR für Website-Scraping (Rohtext AR). - Lädt Daten neu, prüft Timestamp AT und überspringt ggf. - Setzt AT + AP für bearbeitete Zeilen. Sendet Updates gebündelt. + Lädt Daten neu, prüft Spalte AR auf Inhalt ('', 'k.A.') und überspringt ggf. + Setzt AR + AP für bearbeitete Zeilen. Sendet Updates gebündelt. """ debug_print(f"Starte Website-Scraping NUR ROHDATEN (Batch) für Zeilen {start_row_index_in_sheet} bis {end_row_index_in_sheet}...") + # --- Konfiguration --- + MAX_SCRAPING_WORKERS = Config.MAX_SCRAPING_WORKERS # Aus Config holen + update_batch_row_limit = Config.UPDATE_BATCH_ROW_LIMIT # Aus Config holen + + # --- Lade Daten --- if not sheet_handler.load_data(): return all_data = sheet_handler.get_all_data_with_headers() if not all_data or len(all_data) <= 5: return + header_rows = 5 - # Indizes holen - timestamp_col_key = "Website Scrape Timestamp" - timestamp_col_index = COLUMN_MAP.get(timestamp_col_key) + # --- Indizes und Buchstaben --- + rohtext_col_key = "Website Rohtext" # Spalte AR + rohtext_col_index = COLUMN_MAP.get(rohtext_col_key) website_col_idx = COLUMN_MAP.get("CRM Website") - rohtext_col_idx = COLUMN_MAP.get("Website Rohtext") version_col_idx = COLUMN_MAP.get("Version") - if None in [timestamp_col_index, website_col_idx, rohtext_col_idx, version_col_idx]: - debug_print(f"FEHLER: Benötigte Indizes für process_website_batch (Scraping) fehlen.") + if None in [rohtext_col_index, website_col_idx, version_col_idx]: + debug_print(f"FEHLER: Benötigte Indizes für process_website_batch fehlen.") return - - ts_col_letter = sheet_handler._get_col_letter(timestamp_col_index + 1) - rohtext_col_letter = sheet_handler._get_col_letter(rohtext_col_idx + 1) + rohtext_col_letter = sheet_handler._get_col_letter(rohtext_col_index + 1) version_col_letter = sheet_handler._get_col_letter(version_col_idx + 1) - # --- NEU: Liste für gesammelte Updates (nur AR, AT, AP) --- - all_sheet_updates = [] - rows_in_current_update_batch = 0 - update_batch_row_limit = 50 # Sammle Updates für 50 Zeilen - - processed_count = 0 - skipped_count = 0 - skipped_url_count = 0 - error_count = 0 - - # --- Worker-Funktion nur für Scraping --- + # --- Worker-Funktion für Scraping (unverändert) --- def scrape_raw_text_task(task_info): row_num = task_info['row_num'] url = task_info['url'] raw_text = "k.A." error = None - try: - raw_text = get_website_raw(url) - except Exception as e: - error = f"Scraping Fehler Zeile {row_num}: {e}" - debug_print(error) + try: raw_text = get_website_raw(url) + except Exception as e: error = f"Scraping Fehler Zeile {row_num}: {e}"; debug_print(error) return {"row_num": row_num, "raw_text": raw_text, "error": error} - # --- Hauptschleife: Tasks sammeln --- - tasks_to_process = [] + # --- Hauptlogik: Iteriere und sammle Batches --- + tasks_for_processing_batch = [] + all_sheet_updates = [] + total_processed_count = 0 + total_skipped_count = 0 + total_skipped_url_count = 0 + total_error_count = 0 + for i in range(start_row_index_in_sheet, end_row_index_in_sheet + 1): row_index_in_list = i - 1 if row_index_in_list >= len(all_data): continue row = all_data[row_index_in_list] - # Timestamp-Prüfung (AT) + # --- Prüfung, ob AR schon Inhalt hat --- should_skip = False - if len(row) > timestamp_col_index and str(row[timestamp_col_index]).strip(): - should_skip = True + cell_value_ar = None + if len(row) > rohtext_col_index: + cell_value_ar = str(row[rohtext_col_index]).strip() + # Überspringen, wenn NICHT leer oder k.A. + if cell_value_ar and cell_value_ar.lower() not in ["", "k.a.", "k.a. (nur cookie-banner erkannt)", "k.a. (fehler)"]: + should_skip = True + # else: Spalte nicht vorhanden -> nicht überspringen + + # Debug Log + log_debug = (i < start_row_index_in_sheet + 5 or i > end_row_index_in_sheet - 5 or i % 500 == 0) + if log_debug: + debug_print(f"Zeile {i} (Website AR Check): Prüfe Inhalt Spalte {rohtext_col_letter}. Rohwert='{cell_value_ar}'. Überspringen? -> {should_skip}") if should_skip: - skipped_count += 1 + total_skipped_count += 1 continue + # --- Ende AR Prüfung --- # Gültige URL Prüfung website_url = row[website_col_idx] if len(row) > website_col_idx else "" if not website_url or website_url.strip().lower() == "k.a.": - skipped_url_count += 1 + total_skipped_url_count += 1 continue - tasks_to_process.append({"row_num": i, "url": website_url}) + tasks_for_processing_batch.append({"row_num": i, "url": website_url}) - # --- Paralleles Scraping der gesammelten Tasks --- - if not tasks_to_process: - debug_print("Keine Websites zum Scrapen in diesem Bereich gefunden (oder alle übersprungen).") - else: - debug_print(f"Starte paralleles Scraping für {len(tasks_to_process)} Websites...") - scraping_results = {} # {row_num: raw_text, ...} - with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_SCRAPING_WORKERS) as executor: - future_to_task = {executor.submit(scrape_raw_text_task, task): task for task in tasks_to_process} - for future in concurrent.futures.as_completed(future_to_task): - task = future_to_task[future] - try: - result = future.result() - scraping_results[result['row_num']] = result['raw_text'] # Speichere nur den Text - if result['error']: error_count += 1 - processed_count += 1 - except Exception as exc: - row_num = task['row_num'] - debug_print(f"Generischer Fehler Scraping Task Zeile {row_num}: {exc}") - scraping_results[row_num] = "k.A. (Fehler)" # Markiere als Fehler - error_count += 1 - processed_count += 1 # Zähle trotzdem als verarbeitet + # --- Verarbeitungs-Batch ausführen --- + if len(tasks_for_processing_batch) >= PROCESSING_BATCH_SIZE or i == end_row_index_in_sheet: + if tasks_for_processing_batch: + batch_start_row = tasks_for_processing_batch[0]['row_num'] + batch_end_row = tasks_for_processing_batch[-1]['row_num'] + batch_task_count = len(tasks_for_current_processing_batch) # Korrigiert + debug_print(f"\n--- Starte Scraping-Batch ({batch_task_count} Tasks, Zeilen {batch_start_row}-{batch_end_row}) ---") - debug_print(f"Paralleles Scraping beendet. {processed_count} Versuche, {error_count} Fehler.") + scraping_results = {} + debug_print(f" Scrape {batch_task_count} Websites parallel (max {MAX_SCRAPING_WORKERS} worker)...") # Korrigiert + with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_SCRAPING_WORKERS) as executor: + future_to_task = {executor.submit(scrape_raw_text_task, task): task for task in tasks_for_processing_batch} + for future in concurrent.futures.as_completed(future_to_task): + task = future_to_task[future] + try: + result = future.result() + scraping_results[result['row_num']] = result['raw_text'] + if result['error']: total_error_count += 1 + total_processed_count += 1 # Zähle hier jeden Versuch + except Exception as exc: + row_num = task['row_num']; err_msg = f"Generischer Fehler Scraping Task Zeile {row_num}: {exc}" + debug_print(err_msg); scraping_results[row_num] = "k.A. (Fehler)"; total_error_count +=1; total_processed_count += 1 - # --- Sheet Updates vorbereiten für gescrapte Texte --- - if scraping_results: - current_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - current_version = Config.VERSION - for row_num, raw_text_res in scraping_results.items(): - # Updates für AR, AT, AP - row_updates = [ - {'range': f'{rohtext_col_letter}{row_num}', 'values': [[raw_text_res]]}, - {'range': f'{ts_col_letter}{row_num}', 'values': [[current_timestamp]]}, - {'range': f'{version_col_letter}{row_num}', 'values': [[current_version]]} - ] - all_sheet_updates.extend(row_updates) + debug_print(f" Scraping für Batch beendet.") + + # --- Sheet Updates vorbereiten (NUR AR und AP) --- + if scraping_results: + current_version = Config.VERSION + batch_sheet_updates = [] + for row_num, raw_text_res in scraping_results.items(): + row_updates = [ + {'range': f'{rohtext_col_letter}{row_num}', 'values': [[raw_text_res]]}, + # {'range': f'{ts_col_letter}{row_num}', 'values': [[current_timestamp]]}, # AT wird NICHT mehr gesetzt + {'range': f'{version_col_letter}{row_num}', 'values': [[current_version]]} + ] + batch_sheet_updates.extend(row_updates) + all_sheet_updates.extend(batch_sheet_updates) # Sammle für größeren Batch-Update + + # Leere den Verarbeitungs-Batch + tasks_for_processing_batch = [] + + # --- Sheet Updates senden (wenn update_batch_row_limit erreicht) --- + # Hinweis: Diese Logik sendet jetzt seltener, erst wenn genug Updates gesammelt wurden + if len(all_sheet_updates) >= update_batch_row_limit * 2: # *2 weil 2 Updates pro Zeile + debug_print(f" Sende gesammelte Sheet-Updates ({len(all_sheet_updates)} Zellen)...") + success = sheet_handler.batch_update_cells(all_sheet_updates) + if success: debug_print(f" Sheet-Update bis Zeile {i} erfolgreich.") + else: debug_print(f" FEHLER beim Sheet-Update bis Zeile {i}.") + all_sheet_updates = [] # Zurücksetzen # --- Finale Sheet Updates senden --- if all_sheet_updates: - # Sende alle Updates auf einmal am Ende - debug_print(f"Sende finale Sheet-Updates für {len(scraping_results)} verarbeitete Zeilen...") - success = sheet_handler.batch_update_cells(all_sheet_updates) - if success: - debug_print(f"Sheet-Update für Website-Scraping erfolgreich.") - else: - debug_print(f"FEHLER beim finalen Sheet-Update für Website-Scraping.") + debug_print(f"Sende finale Sheet-Updates ({len(all_sheet_updates)} Zellen)...") + sheet_handler.batch_update_cells(all_sheet_updates) - debug_print(f"Website-Scraping NUR ROHDATEN abgeschlossen. {processed_count} Websites verarbeitet (inkl. Fehler), {error_count} Fehler, {skipped_count} Zeilen wg. Timestamp übersprungen, {skipped_url_count} Zeilen ohne URL übersprungen.") + debug_print(f"Website-Scraping NUR ROHDATEN abgeschlossen. {total_processed_count} Websites verarbeitet (inkl. Fehler), {total_error_count} Fehler, {total_skipped_count} Zeilen wg. Inhalt übersprungen, {total_skipped_url_count} Zeilen ohne URL übersprungen.") # NEUE Funktion process_website_summarization_batch @@ -2649,74 +2603,77 @@ def process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_ def run_dispatcher(mode, sheet_handler, row_limit=None): """ Wählt den passenden Batch-Prozess basierend auf dem Modus. - Ermittelt die Startzeile dynamisch basierend auf dem Timestamp in der relevanten Spalte. + Ermittelt die Startzeile dynamisch basierend auf der relevanten Spalte für den Modus. """ debug_print(f"Starte Dispatcher im Modus '{mode}' mit row_limit={row_limit}.") header_rows = 5 - # --- Startzeilen-Ermittlung --- + # --- Startzeilen-Ermittlung basierend auf Modus --- start_col_key = "Timestamp letzte Prüfung" # Standard (AO) min_start_row = 7 - if mode == "website": start_col_key = "Website Scrape Timestamp" # AT - elif mode == "wiki": start_col_key = "Wiki Verif. Timestamp" # AX (NEU) - elif mode == "branch": start_col_key = "Timestamp letzte Prüfung" # AO - elif mode == "summarize": start_col_key = "Timestamp letzte Prüfung" # AO (oder AS?) - Nehmen wir AO, damit es nach Scraping läuft - elif mode == "combined": start_col_key = "Timestamp letzte Prüfung" # AO + + # --- KORRIGIERT: Startspalte für jeden Modus --- + if mode == "website": + start_col_key = "Website Rohtext" # Spalte AR (NEU) + elif mode == "wiki": + start_col_key = "Wiki Verif. Timestamp" # Spalte AX + elif mode == "branch": + start_col_key = "Timestamp letzte Prüfung" # Spalte AO + elif mode == "summarize": + start_col_key = "Website Zusammenfassung" # Spalte AS (prüft ob Summary fehlt) + elif mode == "combined": + start_col_key = "Timestamp letzte Prüfung" # Spalte AO debug_print(f"Dispatcher: Ermittle Startzeile basierend auf Spalte '{start_col_key}'...") + # get_start_row_index prüft jetzt auf leere Werte oder 'k.a.' etc. start_data_index = sheet_handler.get_start_row_index(check_column_key=start_col_key, min_sheet_row=min_start_row) - if start_data_index == -1: return # Fehler geloggt in get_start_row_index + if start_data_index == -1: return # Fehler wurde geloggt start_row_index_in_sheet = start_data_index + header_rows + 1 total_sheet_rows = len(sheet_handler.sheet_values) + # --- Endzeilen-Ermittlung und Prüfungen (wie gehabt) --- if start_data_index >= len(sheet_handler.get_data()): return # Log in get_start_row_index if start_row_index_in_sheet > total_sheet_rows: return # Log in get_start_row_index - # --- Endzeilen-Ermittlung --- if row_limit is not None and row_limit > 0: end_row_index_in_sheet = min(start_row_index_in_sheet + row_limit - 1, total_sheet_rows) - elif row_limit == 0: - debug_print("Zeilenlimit ist 0. Keine Verarbeitung.") - return - else: - end_row_index_in_sheet = total_sheet_rows + elif row_limit == 0: return debug_print("Zeilenlimit ist 0.") + else: end_row_index_in_sheet = total_sheet_rows debug_print(f"Dispatcher: Verarbeitung geplant für Sheet-Zeilen {start_row_index_in_sheet} bis {end_row_index_in_sheet}.") - - if start_row_index_in_sheet > end_row_index_in_sheet: return # Log in get_start_row_index + if start_row_index_in_sheet > end_row_index_in_sheet: return debug_print("Start nach Ende.") # --- Modusauswahl und Aufruf --- try: if mode == "wiki": - process_verification_only(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AX, setzt AX + process_verification_only(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AX, Setzt AX elif mode == "website": - process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AT, setzt AT+AP + process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AR, Setzt AR+AP elif mode == "branch": - process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AO, setzt AO+AP - elif mode == "summarize": # NEUER MODUS - process_website_summarization_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AS, setzt AS+AP + process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AO, Setzt AO+AP + elif mode == "summarize": + process_website_summarization_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AS, Setzt AS+AP elif mode == "combined": debug_print("--- Start Combined Mode: Wiki ---") - process_verification_only(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AX, setzt AX - time.sleep(1) # Kurze Pause + process_verification_only(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AX, Setzt AX + time.sleep(1) debug_print("--- Start Combined Mode: Website Scraping ---") - process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AT, setzt AT+AP - time.sleep(1) # Kurze Pause - debug_print("--- Start Combined Mode: Website Summarization ---") # NEUER SCHRITT - process_website_summarization_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AS, setzt AS+AP - time.sleep(1) # Kurze Pause + process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AR, Setzt AR+AP + time.sleep(1) + debug_print("--- Start Combined Mode: Website Summarization ---") + process_website_summarization_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AS, Setzt AS+AP + time.sleep(1) debug_print("--- Start Combined Mode: Branch ---") - process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AO, setzt AO+AP + process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AO, Setzt AO+AP debug_print("--- Combined Mode abgeschlossen ---") else: debug_print(f"Ungültiger Modus '{mode}' wurde im Dispatcher übergeben.") except Exception as e: - debug_print(f"FEHLER im Dispatcher während der Ausführung von Modus '{mode}': {e}") - import traceback - debug_print(traceback.format_exc()) + debug_print(f"FEHLER im Dispatcher während Modus '{mode}': {e}") + import traceback; debug_print(traceback.format_exc()) # --- Ende run_dispatcher Funktion ---