bugfix
This commit is contained in:
@@ -953,38 +953,48 @@ class GoogleSheetHandler:
|
||||
self.sheet_values = []
|
||||
self.headers = [] # Um Header-Zeilen zu speichern
|
||||
self._connect()
|
||||
self._load_data()
|
||||
# _load_data wird jetzt nach erfolgreicher Verbindung aufgerufen
|
||||
if self.sheet:
|
||||
self._load_data()
|
||||
|
||||
@retry_on_failure
|
||||
def _connect(self):
|
||||
"""Stellt Verbindung zum Google Sheet her."""
|
||||
# Setze self.sheet initial auf None
|
||||
self.sheet = None
|
||||
debug_print("Verbinde mit Google Sheets...")
|
||||
scope = ["https://www.googleapis.com/auth/spreadsheets"]
|
||||
creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
|
||||
gc = gspread.authorize(creds)
|
||||
sh = gc.open_by_url(Config.SHEET_URL)
|
||||
self.sheet = sh.sheet1
|
||||
debug_print("Verbindung zu Google Sheets erfolgreich.")
|
||||
try:
|
||||
scope = ["https://www.googleapis.com/auth/spreadsheets"]
|
||||
creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
|
||||
gc = gspread.authorize(creds)
|
||||
sh = gc.open_by_url(Config.SHEET_URL)
|
||||
self.sheet = sh.sheet1 # Zugriff auf das erste Blatt (Sheet1)
|
||||
debug_print("Verbindung zu Google Sheets erfolgreich.")
|
||||
except Exception as e:
|
||||
debug_print(f"FEHLER bei der Google Sheets Verbindung: {e}")
|
||||
# Hier könnte man den Fehler weitergeben oder None lassen
|
||||
raise # Fehler weitergeben, damit retry greift oder main abbricht
|
||||
|
||||
@retry_on_failure
|
||||
def _load_data(self):
|
||||
"""Lädt alle Daten aus dem Sheet."""
|
||||
if not self.sheet:
|
||||
debug_print("Fehler: Keine Sheet-Verbindung zum Laden der Daten.")
|
||||
self.sheet_values = []
|
||||
self.headers = []
|
||||
return
|
||||
debug_print("Lade Daten aus Google Sheet...")
|
||||
self.sheet_values = self.sheet.get_all_values()
|
||||
if len(self.sheet_values) >= 5:
|
||||
self.headers = self.sheet_values[:5] # Zeilen 1-5 als Header speichern
|
||||
if len(self.sheet_values) >= 1: # Mindestens Header sollte da sein
|
||||
self.headers = self.sheet_values[:5] # Annahme: Zeilen 1-5 sind Header
|
||||
else:
|
||||
self.headers = self.sheet_values[:] # Alle Zeilen als Header, falls weniger als 5
|
||||
self.headers = []
|
||||
debug_print("Warnung: Google Sheet scheint leer zu sein.")
|
||||
debug_print(f"Daten geladen: {len(self.sheet_values)} Zeilen insgesamt.")
|
||||
# Hier könnte die COLUMN_MAP dynamisch erstellt werden, falls gewünscht
|
||||
|
||||
def get_data(self):
|
||||
"""Gibt die geladenen Daten zurück (ohne Header)."""
|
||||
# Annahme: Die ersten 5 Zeilen sind Header
|
||||
header_rows = 5
|
||||
header_rows = 5 # Annahme: Zeile 1-5 sind Header
|
||||
if len(self.sheet_values) <= header_rows:
|
||||
return []
|
||||
return self.sheet_values[header_rows:]
|
||||
@@ -993,131 +1003,86 @@ class GoogleSheetHandler:
|
||||
"""Gibt alle Daten inklusive Header zurück."""
|
||||
return self.sheet_values
|
||||
|
||||
# Angepasste Methode, um den Startindex basierend auf einer spezifischen Spalte zu finden
|
||||
def get_start_row_index(self, check_column_index=COLUMN_MAP["Website Scrape Timestamp"], min_sheet_row=7):
|
||||
"""
|
||||
Findet den Index der ersten Zeile (0-basiert für Daten nach Header),
|
||||
ab einer Mindestzeilennummer im Sheet, in der der Timestamp in der
|
||||
angegebenen Spalte fehlt.
|
||||
|
||||
Args:
|
||||
check_column_index (int): Der 0-basierte Index der zu prüfenden Spalte.
|
||||
min_sheet_row (int): Die 1-basierte Zeilennummer im Sheet, ab der gesucht werden soll.
|
||||
|
||||
Returns:
|
||||
int: Der 0-basierte Index in der Datenliste (ohne Header),
|
||||
oder der Index nach der letzten Zeile, wenn alle gefüllt sind.
|
||||
"""
|
||||
header_rows = 5 # Annahme: Zeilen 1-5 sind Header
|
||||
data_rows = self.get_data() # Holt Daten ohne Header
|
||||
header_rows = 5
|
||||
data_rows = self.get_data()
|
||||
|
||||
if not data_rows:
|
||||
debug_print("Keine Datenzeilen vorhanden.")
|
||||
debug_print("Keine Datenzeilen vorhanden für get_start_row_index.")
|
||||
return 0
|
||||
|
||||
# Berechne den 0-basierten Startindex für die *Datenliste*,
|
||||
# der der min_sheet_row entspricht.
|
||||
search_start_index_in_data = max(0, min_sheet_row - header_rows - 1)
|
||||
|
||||
# Stelle sicher, dass der Startindex nicht außerhalb der Liste liegt
|
||||
if search_start_index_in_data >= len(data_rows):
|
||||
col_letter = self._get_col_letter(check_column_index + 1)
|
||||
debug_print(f"Start-Suchindex ({search_start_index_in_data}) liegt nach der letzten Datenzeile ({len(data_rows)-1}). Suche ab letzter Zeile für Spalte {col_letter}.")
|
||||
search_start_index_in_data = len(data_rows) -1 # Beginne bei der letzten Zeile falls Start zu weit
|
||||
if search_start_index_in_data < 0: return 0 # Falls gar keine Datenzeilen
|
||||
|
||||
|
||||
for i, row in enumerate(data_rows[search_start_index_in_data:], start=search_start_index_in_data):
|
||||
# Sicherheitscheck für Zeilenlänge
|
||||
if len(row) <= check_column_index or not row[check_column_index].strip():
|
||||
actual_sheet_row = i + header_rows + 1 # 1-basierte Zeilennummer im Sheet
|
||||
# Finde den Spaltenbuchstaben für die Log-Ausgabe
|
||||
actual_sheet_row = i + header_rows + 1
|
||||
col_letter = self._get_col_letter(check_column_index + 1)
|
||||
debug_print(f"Erste Zeile ab Zeile {min_sheet_row} ohne Zeitstempel in Spalte {col_letter} (Index {check_column_index}) gefunden: Zeile {actual_sheet_row} (Daten-Index {i})")
|
||||
return i # Gibt den 0-basierten Index *innerhalb der Datenliste* zurück
|
||||
return i
|
||||
|
||||
# Wenn alle Zeilen ab min_sheet_row einen Zeitstempel haben
|
||||
last_index = len(data_rows)
|
||||
col_letter = self._get_col_letter(check_column_index + 1)
|
||||
debug_print(f"Alle Zeilen ab Zeile {min_sheet_row} haben einen Zeitstempel in Spalte {col_letter}. Nächster Daten-Index wäre {last_index}.")
|
||||
return last_index # Gibt den Index nach der letzten Datenzeile zurück
|
||||
return last_index
|
||||
|
||||
# Hilfsfunktion zur Umwandlung von Spaltenindex in Buchstaben (für Logs)
|
||||
def _get_col_letter(self, col_idx):
|
||||
""" Konvertiert 1-basierten Spaltenindex in Buchstaben (A, B, ..., Z, AA, ...). """
|
||||
""" Konvertiert 1-basierten Spaltenindex in Buchstaben. """
|
||||
string = ""
|
||||
while col_idx > 0:
|
||||
col_idx, remainder = divmod(col_idx - 1, 26)
|
||||
string = chr(65 + remainder) + string
|
||||
return string
|
||||
|
||||
# Anpassung in run_dispatcher: Verwende die neue Methode mit Spalte AT
|
||||
def run_dispatcher(mode, sheet_handler, row_limit=None):
|
||||
"""Wählt den passenden Batch-Prozess basierend auf dem Modus."""
|
||||
debug_print(f"Starte Dispatcher im Modus '{mode}' mit row_limit={row_limit}.")
|
||||
|
||||
# Finde Startzeile basierend auf Timestamp in Spalte AT (Index 45)
|
||||
# Verwende die neue Methode des Handlers
|
||||
start_data_index = sheet_handler.get_start_row_index(check_column_index=COLUMN_MAP["Website Scrape Timestamp"], min_sheet_row=7)
|
||||
header_rows = 5
|
||||
start_row_index_in_sheet = start_data_index + header_rows + 1
|
||||
|
||||
all_data = sheet_handler.get_all_data_with_headers() # Hole alle Daten
|
||||
total_sheet_rows = len(all_data)
|
||||
|
||||
if start_row_index_in_sheet > total_sheet_rows:
|
||||
debug_print(f"Startzeile ({start_row_index_in_sheet}) liegt hinter der letzten Sheet-Zeile ({total_sheet_rows}). Dispatcher beendet.")
|
||||
return
|
||||
|
||||
# Bestimme Endzeile
|
||||
if row_limit is not None and row_limit > 0:
|
||||
# Berechne Endzeile basierend auf Startzeile und Limit
|
||||
end_row_index_in_sheet = min(start_row_index_in_sheet + row_limit - 1, total_sheet_rows)
|
||||
else:
|
||||
end_row_index_in_sheet = total_sheet_rows # Bis zum Ende des Sheets
|
||||
|
||||
debug_print(f"Dispatcher: Verarbeitung startet ab Zeile {start_row_index_in_sheet}, bis Zeile {end_row_index_in_sheet}.")
|
||||
|
||||
if start_row_index_in_sheet > end_row_index_in_sheet:
|
||||
debug_print("Startzeile liegt nach Endzeile. Keine Verarbeitung.")
|
||||
return
|
||||
|
||||
# --- Modusausführung (bleibt gleich, ABER die aufgerufenen Funktionen müssen den Timestamp prüfen!) ---
|
||||
if mode == "wiki":
|
||||
# process_verification_only muss AN Timestamp prüfen
|
||||
process_verification_only(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet)
|
||||
elif mode == "website":
|
||||
# process_website_batch muss AT Timestamp prüfen
|
||||
process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet)
|
||||
elif mode == "branch":
|
||||
# process_branch_batch muss AO Timestamp prüfen
|
||||
process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet)
|
||||
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 AN
|
||||
debug_print("--- Start Combined Mode: Website ---")
|
||||
process_website_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AT
|
||||
debug_print("--- Start Combined Mode: Branch ---")
|
||||
process_branch_batch(sheet_handler, start_row_index_in_sheet, end_row_index_in_sheet) # Prüft AO
|
||||
debug_print("--- Combined Mode abgeschlossen ---")
|
||||
else:
|
||||
debug_print(f"Ungültiger Modus '{mode}' im Dispatcher.")
|
||||
|
||||
# --- NEU HINZUGEFÜGTE METHODE ---
|
||||
@retry_on_failure
|
||||
def batch_update_cells(self, update_data):
|
||||
"""
|
||||
Führt ein Batch-Update im Google Sheet durch.
|
||||
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.")
|
||||
return False
|
||||
debug_print("FEHLER: Keine Sheet-Verbindung für Batch-Update.")
|
||||
return False # Kein Erfolg, da keine Verbindung
|
||||
if not update_data:
|
||||
debug_print("Keine Daten für Batch-Update vorhanden.")
|
||||
# debug_print("Keine Daten für Batch-Update vorhanden.") # Weniger Lärm im Log
|
||||
return True # Kein Fehler, aber nichts zu tun
|
||||
|
||||
try:
|
||||
self.sheet.batch_update(update_data)
|
||||
debug_print(f"Batch-Update erfolgreich ({len(update_data)} Zellen/Bereiche aktualisiert).")
|
||||
# Der eigentliche API-Aufruf
|
||||
self.sheet.batch_update(update_data, value_input_option='USER_ENTERED')
|
||||
# debug_print(f"Batch-Update erfolgreich ({len(update_data)} Zellen/Bereiche aktualisiert).") # Optional weniger Lärm
|
||||
return True
|
||||
except gspread.exceptions.APIError as e:
|
||||
debug_print(f"Google API Fehler beim Batch-Update: {e}")
|
||||
# Hier könnte spezifische Fehlerbehandlung erfolgen (z.B. RateLimit)
|
||||
raise # Fehler weitergeben, damit retry greifen kann
|
||||
# Spezifische Fehlerbehandlung für gspread/Google API Fehler
|
||||
debug_print(f"Google API Fehler beim Batch-Update: {e.response.status_code} - {e.response.text[:500]}")
|
||||
# Der @retry_on_failure Decorator sollte diesen Fehler fangen und Wiederholungen versuchen
|
||||
raise e # Fehler weitergeben, damit der Decorator ihn fängt
|
||||
except Exception as e:
|
||||
debug_print(f"Allgemeiner Fehler beim Batch-Update: {e}")
|
||||
raise # Fehler weitergeben
|
||||
# Allgemeine Fehlerbehandlung
|
||||
debug_print(f"Allgemeiner Fehler beim Batch-Update: {type(e).__name__} - {e}")
|
||||
raise e # Fehler weitergeben
|
||||
|
||||
|
||||
|
||||
# ==================== WIKIPEDIA SCRAPER ====================
|
||||
|
||||
Reference in New Issue
Block a user