From fd57db914df8dd6bfbf71e57897d347ba0c44721 Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 6 May 2025 12:53:53 +0000 Subject: [PATCH] bugfix --- brancheneinstufung.py | 57 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 8f94bc79..86c439b7 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -252,7 +252,7 @@ decorator_logger = logging.getLogger(__name__ + ".Retry") # --- Retry Decorator --- -# KORRIGIERTE Version (Behandelt SpreadsheetNotFound und 404/400 HTTPError explizit) +# KORRIGIERTE Version (Behandelt SpreadsheetNotFound und 404/400/401/403 HTTPError explizit) def retry_on_failure(func): """ Decorator, der eine Funktion bei bestimmten Fehlern mehrmals wiederholt. @@ -426,7 +426,7 @@ def simple_normalize_url(url): """Normalisiert URL zu domain.tld oder k.A. (ohne www, ohne Pfad).""" if not url or not isinstance(url, str): return "k.A." url = url.strip() - if not url or url.lower() == 'k.a.': return "k.A." + if not url or url.lower() == 'k.A.': return "k.A." # Prüfe auf Kleinbuchstaben "k.A." # Falls kein Schema vorhanden, hinzufügen (HTTPS bevorzugen) if not url.lower().startswith(("http://", "https://")): url = "https://" + url try: @@ -479,7 +479,7 @@ def normalize_company_name(name): """Entfernt gängige Rechtsformzusätze etc. für Vergleiche.""" if not name: return "" name = clean_text(name) - forms = [ r'gmbh', r'ges\.?\s*m\.?\s*b\.?\s*h\.?', r'gesellschaft mit beschränkter haftung', r'ug', r'u\.g\.', r'unternehmergesellschaft', r'haftungsbeschränkt', r'ag', r'a\.g\.', r'aktiengesellschaft', r'ohg', r'o\.h\.g\.', r'offene handelsgesellschaft', r'kg', r'k\.g\.', r'kommanditgesellschaft', r'gmbh\s*&\s*co\.?\s*kg', r'ges\.?\s*m\.?\s*b\.?\s*h\.?\s*&\s*co\.?\s*k\.g\.?', r'ag\s*&\s*co\.?\s*kg', r'a\.g\.?\s*&\s*co\.?\s*k\.g\.?', r'e\.k\.', r'e\.kfm\.', r'e\.kfr\.', r'eingetragene[rn]? kauffrau', r'eingetragene[rn]? kaufmann', r'ltd\.?', r'limited', r'ltd\s*&\s*co\.?\s*kg', r's\.?a\.?r\.?l\.?', r'sàrl', r'sagl', r's\.?a\.?', r'société anonyme', r'sociedad anónima', r's\.?p\.?a\.?', r'società per azioni', r'b\.?v\.?', r'besloten vennootschap', r'n\.?v\.?', r'naamloze vennootschap', r'plc\.?', r'public limited company', 'inc', 'incorporated', r'corp\.?', 'corporation', 'llc', 'limited liability company', r'kgaa', r'kommanditgesellschaft auf aktien', 'se', 'societas europaea', r'e\.?g\.?', r'eingetragene genossenschaft', 'genossenschaft', 'genmbh', r'e\.?v\.?', r'eingetragener verein', 'verein', 'stiftung', 'ggmbh', r'gemeinnützige gmbh', r'gemeinnützige[rn]? gmbh', 'gug', 'partg', 'partnerschaftsgesellschaft', 'partgmbb', 'og', r'o\.g\.', 'offene gesellschaft', r'e\.u\.', 'eingetragenes unternehmen', r'ges\.?n\.?b\.?r\.?', r'gesellschaft nach bürgerlichem recht', 'kollektivgesellschaft', 'einzelfirma', 'gruppe', 'holding', 'international', 'systeme', 'technik', 'logistik', 'solutions', 'services', 'management', 'consulting', 'produktion', 'vertrieb', 'entwicklung', 'maschinenbau', 'anlagenbau' + forms = [ r'gmbh', r'ges\.?\s*m\.?\s*b\.?\s*h\.?', r'gesellschaft mit beschränkter haftung', r'ug', r'u\.g\.', r'unternehmergesellschaft', r'haftungsbeschränkt', r'ag', r'a\.g\.', r'aktiengesellschaft', r'ohg', r'o\.h\.g\.', r'offene handelsgesellschaft', r'kg', r'k\.g\.', r'kommanditgesellschaft', r'gmbh\s*&\s*co\.?\s*kg', r'ges\.?\s*m\.?\s*b\.?\s*h\.?\s*&\s*co\.?\s*k\.g\.?', r'ag\s*&\s*co\.?\s*kg', r'a\.g\.?\s*&\s*co\.?\s*k\.g\.?', r'e\.k\.', r'e\.kfm\.', r'e\.kfr\.', r'eingetragene[rn]? kauffrau', r'eingetragene[rn]? kaufmann', r'ltd\.?', 'limited', r'ltd\s*&\s*co\.?\s*kg', r's\.?a\.?r\.?l\.?', 'sàrl', 'sagl', r's\.?a\.?', 'société anonyme', 'sociedad anónima', r's\.?p\.?a\.?', 'società per azioni', r'b\.?v\.?', 'besloten vennootschap', r'n\.?v\.?', 'naamloze vennootschap', r'plc\.?', 'public limited company', 'inc', 'incorporated', r'corp\.?', 'corporation', 'llc', 'limited liability company', r'kgaa', r'kommanditgesellschaft auf aktien', 'se', 'societas europaea', r'e\.?g\.?', 'eingetragene genossenschaft', 'genossenschaft', 'genmbh', r'e\.?v\.?', 'eingetragener verein', 'verein', 'stiftung', 'ggmbh', r'gemeinnützige gmbh', r'gemeinnützige[rn]? gmbh', 'gug', 'partg', 'partnerschaftsgesellschaft', 'partgmbb', 'og', r'o\.g\.', 'offene gesellschaft', r'e\.u\.', 'eingetragenes unternehmen', r'ges\.?n\.?b\.?r\.?', r'gesellschaft nach bürgerlichem recht', 'kollektivgesellschaft', 'einzelfirma', 'gruppe', 'holding', 'international', 'systeme', 'technik', 'logistik', 'solutions', 'services', 'management', 'consulting', 'produktion', 'vertrieb', 'entwicklung', 'maschinenbau', 'anlagenbau' ] # Pattern für ganze Wörter (case-insensitive) # Fügen Sie \b hinzu, um sicherzustellen, dass ganze Wörter gematcht werden (z.B. nicht "ag" in "manage") @@ -1545,13 +1545,14 @@ def scrape_website_details(url): # --- Globale Funktion zum Scrapen des Website Rohtextes --- # Übernommen aus get_website_raw in Teil 7. Global platziert. +# Nutzt globale Helfer: simple_normalize_url, clean_text, re, requests, BeautifulSoup, Config, getattr. @retry_on_failure def get_website_raw(url, max_length=20000, verify_cert=True): # Längeres Default Limit, SSL-Zertifikat standardmäßig prüfen """ Holt Textinhalt von einer Website, versucht Cookie-Banner zu umgehen. Gibt den Rohtext zurück oder einen Fehlerwert ("k.A.", "k.A. (Fehler)", etc.). """ - if not url or not isinstance(url, str) or url.strip().lower() in ["k.a.", "kein artikel gefunden", "fehler bei suche"]: + if not url or not isinstance(url, str) or url.strip().lower() in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"]: # Füge "http:" hinzu basierend auf Log logger.debug(f"get_website_raw skipped: Ungültige oder leere URL '{url}'.") return "k.A." @@ -4285,6 +4286,54 @@ class DataProcessor: all_sheet_updates = [] # Gesammelte Updates für Batch-Schreiben update_batch_row_limit = getattr(Config, 'UPDATE_BATCH_ROW_LIMIT', 50) # Nutze die Update-Batch-Größe aus Config + +# --- Worker-Funktion für Scraping (intern) --- + # Diese Funktion wird vom ThreadPoolExecutor aufgerufen und hat Zugriff auf den umgebenden Scope. + def scrape_raw_text_task(task_info): + """ + Scrapt den Rohtext einer Website in einem separaten Thread. + Wird vom ThreadPoolExecutor in process_website_scraping_batch aufgerufen. + Nutzt die globale Funktion get_website_raw. + + Args: + task_info (dict): Enthält {'row_num': int, 'url': str}. + + Returns: + dict: Enthält {'row_num': int, 'raw_text': str, 'error': str}. + """ + row_num = task_info['row_num'] + url = task_info['url'] + raw_text = "k.A." + error = None + + try: + # RUFT die globale Funktion get_website_raw auf. + # Der retry_on_failure Decorator auf get_website_raw behandelt Retries und Fehler. + raw_text = get_website_raw(url) # <<< Hier wird die globale Funktion aufgerufen + + # Wenn get_website_raw einen Fehler loggt und einen Fehlerstring zurückgibt, + # wird dies im Ergebnisdict als raw_text gespeichert. + # Wir können hier prüfen, ob der raw_text einen Fehlerwert signalisiert. + if str(raw_text).startswith("k.A. (Fehler") or str(raw_text).startswith("FEHLER:"): + error = f"Scraping Fehler (Details im Rohtext): {raw_text[:100]}..." + # self.logger.error(error) # Wird bereits in get_website_raw geloggt + pass # Fehler wurde bereits im Rückgabewert signalisiert + + + except Exception as e: + # Dieser Block sollte jetzt seltener erreicht werden, da get_website_raw + # die meisten Fehler intern fängt und mit retry behandelt. + # Wenn eine Exception hier durchkommt, ist es ein unerwarteter Fehler im Task-Handling selbst. + error = f"Unerwarteter Fehler im Scraping Task Zeile {row_num} ({url}): {type(e).__name__} - {e}" + # self.logger.error(error) # Loggen Sie diesen unerwarteten Fehler + raw_text = "k.A. (Unerwarteter Fehler Task)" # Setze einen spezifischen Fehlerwert + + + # logger.debug(f"Scraping Task Zeile {row_num} abgeschlossen. Textlänge: {len(str(raw_text))}.") # Zu viel Lärm + return {"row_num": row_num, "raw_text": raw_text, "error": error} + + # --- Hauptlogik: Iteriere und sammle Batches --- + processed_count = 0 # Zählt Zeilen, die im Batch verarbeitet (versucht) wurden skipped_count = 0 # Zählt Zeilen, die übersprungen wurden (wegen Inhalt oder fehlender URL)