bugfix
This commit is contained in:
@@ -252,7 +252,7 @@ decorator_logger = logging.getLogger(__name__ + ".Retry")
|
|||||||
|
|
||||||
|
|
||||||
# --- Retry Decorator ---
|
# --- Retry Decorator ---
|
||||||
# KORRIGIERTE Version (Behandelt SpreadsheetNotFound und 404 HTTPError explizit)
|
# KORRIGIERTE Version (Behandelt SpreadsheetNotFound und 404/400 HTTPError explizit)
|
||||||
def retry_on_failure(func):
|
def retry_on_failure(func):
|
||||||
"""
|
"""
|
||||||
Decorator, der eine Funktion bei bestimmten Fehlern mehrmals wiederholt.
|
Decorator, der eine Funktion bei bestimmten Fehlern mehrmals wiederholt.
|
||||||
@@ -292,8 +292,7 @@ def retry_on_failure(func):
|
|||||||
|
|
||||||
return func(*args, **kwargs) # Call the original function
|
return func(*args, **kwargs) # Call the original function
|
||||||
|
|
||||||
# Spezifische Exceptions, die ein Retry rechtfertigen
|
# Spezifische Exceptions, die ein Retry nicht rechtfertigen (permanente Fehler)
|
||||||
# Fangen Sie nicht-wiederholbare Fehler separat
|
|
||||||
except (gspread.exceptions.SpreadsheetNotFound, openai.error.AuthenticationError, ValueError) as e:
|
except (gspread.exceptions.SpreadsheetNotFound, openai.error.AuthenticationError, ValueError) as e:
|
||||||
# Diese Fehler deuten auf ein permanentes Problem hin (falsche URL, falscher Key, falsche Eingabe)
|
# Diese Fehler deuten auf ein permanentes Problem hin (falsche URL, falscher Key, falsche Eingabe)
|
||||||
decorator_logger.critical(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}': Permanentes Problem erkannt. {type(e).__name__} - {str(e)[:150]}...")
|
decorator_logger.critical(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}': Permanentes Problem erkannt. {type(e).__name__} - {str(e)[:150]}...")
|
||||||
@@ -302,9 +301,12 @@ def retry_on_failure(func):
|
|||||||
|
|
||||||
# Fangen Sie Requests HTTP Errors (wie 404)
|
# Fangen Sie Requests HTTP Errors (wie 404)
|
||||||
except requests.exceptions.HTTPError as e:
|
except requests.exceptions.HTTPError as e:
|
||||||
if e.response is not None:
|
if hasattr(e, 'response') and e.response is not None:
|
||||||
status_code = e.response.status_code
|
status_code = e.response.status_code
|
||||||
if status_code in [404, 400]: # Fügen Sie hier weitere Status-Codes hinzu, die NICHT wiederholt werden sollen
|
# Definieren Sie hier eine Liste von Status-Codes, die NICHT wiederholt werden sollen
|
||||||
|
non_retryable_status_codes = [404, 400, 401, 403] # Not Found, Bad Request, Unauthorized, Forbidden
|
||||||
|
|
||||||
|
if status_code in non_retryable_status_codes:
|
||||||
decorator_logger.critical(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}': HTTP Fehler {status_code} erhalten ({e.response.reason}). Nicht wiederholbar. {str(e)[:150]}...")
|
decorator_logger.critical(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}': HTTP Fehler {status_code} erhalten ({e.response.reason}). Nicht wiederholbar. {str(e)[:150]}...")
|
||||||
decorator_logger.exception("Details:") # Log traceback
|
decorator_logger.exception("Details:") # Log traceback
|
||||||
raise e # Leiten Sie diese nicht-wiederholbare Exception sofort weiter
|
raise e # Leiten Sie diese nicht-wiederholbare Exception sofort weiter
|
||||||
@@ -338,8 +340,6 @@ def retry_on_failure(func):
|
|||||||
time.sleep(wait_time) # Warte vor dem nächsten Versuch
|
time.sleep(wait_time) # Warte vor dem nächsten Versuch
|
||||||
else: # Letzter Versuch fehlgeschlagen
|
else: # Letzter Versuch fehlgeschlagen
|
||||||
decorator_logger.error(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}' nach {max_retries_config} Versuchen.")
|
decorator_logger.error(f"❌ ENDGÜLTIGER FEHLER bei '{effective_func_name}' nach {max_retries_config} Versuchen.")
|
||||||
# Log traceback für erwartete Fehler beim endgültigen Scheitern
|
|
||||||
# decorator_logger.exception("Details zum endgültigen Fehler:") # Kann zu viel Lärm machen
|
|
||||||
raise e # Leite die ursprüngliche Exception weiter
|
raise e # Leite die ursprüngliche Exception weiter
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -351,8 +351,6 @@ def retry_on_failure(func):
|
|||||||
|
|
||||||
# Dieser Teil sollte theoretisch nicht erreicht werden, wenn max_retries_config > 0
|
# Dieser Teil sollte theoretisch nicht erreicht werden, wenn max_retries_config > 0
|
||||||
# und eine Exception immer zu einer raise e Anweisung führt.
|
# und eine Exception immer zu einer raise e Anweisung führt.
|
||||||
# Wenn die Schleife endet, ohne zurückzukehren oder eine Exception zu werfen,
|
|
||||||
# deutet dies auf einen Logikfehler im Decorator hin.
|
|
||||||
raise RuntimeError(f"Retry decorator logic error: Loop completed unexpectedly for {effective_func_name}. This should not happen.")
|
raise RuntimeError(f"Retry decorator logic error: Loop completed unexpectedly for {effective_func_name}. This should not happen.")
|
||||||
|
|
||||||
|
|
||||||
@@ -815,6 +813,7 @@ def load_target_schema(csv_filepath=BRANCH_MAPPING_FILE):
|
|||||||
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfügbar (Keine gültigen Branchen in Datei gefunden)."
|
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfügbar (Keine gültigen Branchen in Datei gefunden)."
|
||||||
logger.warning("Keine gültigen Zielbranchen im Schema gefunden. Branchenbewertung ist nicht möglich.")
|
logger.warning("Keine gültigen Zielbranchen im Schema gefunden. Branchenbewertung ist nicht möglich.")
|
||||||
|
|
||||||
|
|
||||||
# map_external_branch ist in dieser Struktur nicht mehr notwendig,
|
# map_external_branch ist in dieser Struktur nicht mehr notwendig,
|
||||||
# da die Branchenevaluation über ChatGPT (evaluate_branche_chatgpt)
|
# da die Branchenevaluation über ChatGPT (evaluate_branche_chatgpt)
|
||||||
# direkt gegen ALLOWED_TARGET_BRANCHES validiert.
|
# direkt gegen ALLOWED_TARGET_BRANCHES validiert.
|
||||||
@@ -1490,9 +1489,10 @@ def search_linkedin_contacts(company_name, website, position_query, crm_kurzform
|
|||||||
|
|
||||||
|
|
||||||
# --- Experimentelle Website Details Scraping Funktion ---
|
# --- Experimentelle Website Details Scraping Funktion ---
|
||||||
# Diese Funktion wurde in DataProcessor.process_website_details aufgerufen
|
# Diese Funktion wurde in DataProcessor.process_website_details aufgerufen.
|
||||||
|
# Sie ist hier global platziert, da sie nicht spezifisch von DataProcessor state abhängt,
|
||||||
|
# sondern nur von globalen Helfern und Requests.
|
||||||
# Ihre Implementierung hängt stark von der Struktur der Zielwebsites ab.
|
# Ihre Implementierung hängt stark von der Struktur der Zielwebsites ab.
|
||||||
# HIER ist ein Platzhalter für diese Funktion. Sie MUSS implementiert werden.
|
|
||||||
def scrape_website_details(url):
|
def scrape_website_details(url):
|
||||||
"""
|
"""
|
||||||
EXPERIMENTELL: Scrapt eine Website und extrahiert spezifische Details.
|
EXPERIMENTELL: Scrapt eine Website und extrahiert spezifische Details.
|
||||||
@@ -1504,14 +1504,10 @@ def scrape_website_details(url):
|
|||||||
Returns:
|
Returns:
|
||||||
str: Extrahierte Details als String oder Fehler/k.A.
|
str: Extrahierte Details als String oder Fehler/k.A.
|
||||||
"""
|
"""
|
||||||
logger.warning(f"Platzhalter Funktion 'scrape_website_details' für URL {url} aufgerufen.")
|
logger.warning(f"Ausführe 'scrape_website_details' für URL {url}.")
|
||||||
# Beispiel: Einfaches Abrufen des <title> Tags
|
# Beispiel: Einfaches Abrufen des <title> Tags
|
||||||
try:
|
try:
|
||||||
# Nutzt get_page_soup aus der WikipediaScraper Klasse, aber das ist eine Methode.
|
# Hilfsfunktion zum Abrufen des Soup-Objekts mit Retry
|
||||||
# Wir brauchen hier eine separate Funktion oder eine Instanz.
|
|
||||||
# Oder wir verschieben diese Logik in die DataProcessor Klasse, die den Scraper hat.
|
|
||||||
# Am besten: Lassen Sie diese Funktion global, aber nutzen Sie requests direkt oder eine Hilfsfunktion.
|
|
||||||
# Nutzen wir requests direkt mit retry_on_failure
|
|
||||||
@retry_on_failure
|
@retry_on_failure
|
||||||
def get_soup_for_details(target_url):
|
def get_soup_for_details(target_url):
|
||||||
response = requests.get(target_url, timeout=getattr(Config, 'REQUEST_TIMEOUT', 15))
|
response = requests.get(target_url, timeout=getattr(Config, 'REQUEST_TIMEOUT', 15))
|
||||||
@@ -1527,6 +1523,7 @@ def scrape_website_details(url):
|
|||||||
h1 = soup.find('h1')
|
h1 = soup.find('h1')
|
||||||
|
|
||||||
details_list = []
|
details_list = []
|
||||||
|
# clean_text nutzt globale Funktion
|
||||||
if title: details_list.append(f"Title: {clean_text(title.get_text())}")
|
if title: details_list.append(f"Title: {clean_text(title.get_text())}")
|
||||||
if meta_desc and meta_desc.get('content'): details_list.append(f"Description: {clean_text(meta_desc['content'])}")
|
if meta_desc and meta_desc.get('content'): details_list.append(f"Description: {clean_text(meta_desc['content'])}")
|
||||||
if h1: details_list.append(f"H1: {clean_text(h1.get_text())}")
|
if h1: details_list.append(f"H1: {clean_text(h1.get_text())}")
|
||||||
@@ -1537,13 +1534,144 @@ def scrape_website_details(url):
|
|||||||
return "k.A. (Keine Standard-Details gefunden)"
|
return "k.A. (Keine Standard-Details gefunden)"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return "k.A. (Scraping fehlgeschlagen)" # Fehler wurde bereits geloggt
|
# Fehler wurde bereits in get_soup_for_details oder retry geloggt
|
||||||
|
return "k.A. (Scraping fehlgeschlagen)"
|
||||||
|
|
||||||
except Exception as e: # retry_on_failure wirft am Ende Exception
|
except Exception as e: # retry_on_failure wirft am Ende Exception
|
||||||
|
# Dieser Fehler wird bereits vom retry_on_failure geloggt
|
||||||
logger.error(f"FEHLER in scrape_website_details für {url}: {e}")
|
logger.error(f"FEHLER in scrape_website_details für {url}: {e}")
|
||||||
return f"FEHLER: {str(e)[:100]}" # Rückgabe der Fehlermeldung
|
return f"FEHLER: {str(e)[:100]}" # Rückgabe der Fehlermeldung
|
||||||
|
|
||||||
|
|
||||||
|
# --- Globale Funktion zum Scrapen des Website Rohtextes ---
|
||||||
|
# Übernommen aus get_website_raw in Teil 7. Global platziert.
|
||||||
|
@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"]:
|
||||||
|
logger.debug(f"get_website_raw skipped: Ungültige oder leere URL '{url}'.")
|
||||||
|
return "k.A."
|
||||||
|
|
||||||
|
# Falls kein Schema vorhanden ist, hinzufügen (HTTPS bevorzugen)
|
||||||
|
if not url.lower().startswith(("http://", "https://")):
|
||||||
|
#logger.debug(f"Kein Schema in URL '{url}', füge https:// hinzu.") # Zu viel Lärm
|
||||||
|
url = "https://" + url
|
||||||
|
|
||||||
|
# Verwenden Sie eine Requests Session oder requests direkt.
|
||||||
|
# Eine Session in DataProcessor könnte besser sein, aber globale Funktion nutzt requests direkt.
|
||||||
|
headers = {
|
||||||
|
"User-Agent": getattr(Config, 'USER_AGENT', 'Mozilla/5.0 (compatible; UnternehmenSkript/1.0; +http://www.example.com/bot)') # Nutzt Config oder Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Der Requests Call wird vom retry_on_failure Decorator behandelt.
|
||||||
|
# Timeout sollte aus Config kommen.
|
||||||
|
response = requests.get(url, timeout=getattr(Config, 'REQUEST_TIMEOUT', 15), headers=headers, verify=verify_cert)
|
||||||
|
response.raise_for_status() # Wirft HTTPError für 4xx/5xx Antworten. Wird vom Decorator gefangen.
|
||||||
|
|
||||||
|
# Versuche, das Encoding aus dem Header oder dem Content zu erraten
|
||||||
|
response.encoding = response.apparent_encoding
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.text, getattr(Config, 'HTML_PARSER', 'html.parser')) # Nutzt Config oder Fallback
|
||||||
|
|
||||||
|
# --- Versuch 1: Hauptinhalt-Tags finden ---
|
||||||
|
# Verwenden Sie eine Liste von Selektoren
|
||||||
|
content_selectors = [
|
||||||
|
'main', 'article', '#content', '#main-content', '.main-content', '.content',
|
||||||
|
'div[role="main"]', 'div.page-content', 'div.container' # Weitere gängige Selektoren
|
||||||
|
]
|
||||||
|
content_area = None
|
||||||
|
for selector in content_selectors:
|
||||||
|
content_area = soup.select_one(selector)
|
||||||
|
if content_area:
|
||||||
|
#logger.debug(f"Gezielten Inhaltsbereich gefunden mit Selektor '{selector}' für {url}.") # Zu viel Lärm
|
||||||
|
break # Ersten gefundenen Bereich nehmen
|
||||||
|
|
||||||
|
if not content_area:
|
||||||
|
# --- Fallback: Body nehmen, ABER Banner versuchen zu entfernen ---
|
||||||
|
#logger.debug(f"Kein spezifischer Inhaltsbereich gefunden für {url}. Nutze Body und versuche Banner zu entfernen.") # Zu viel Lärm
|
||||||
|
content_area = soup.find('body')
|
||||||
|
|
||||||
|
if content_area:
|
||||||
|
# Versuche, häufige Cookie-Banner Strukturen zu entfernen
|
||||||
|
# Diese Selektoren sollten angepasst werden, wenn spezifische Banner Probleme machen
|
||||||
|
banner_selectors = [
|
||||||
|
'[id*="cookie"]', '[class*="cookie"]', '[id*="consent"]', '[class*="consent"]',
|
||||||
|
'.cookie-banner', '.consent-banner', '.modal', '#modal', '.popup', '#popup',
|
||||||
|
'[role="dialog"]', '[aria-modal="true"]'
|
||||||
|
]
|
||||||
|
banners_removed_count = 0
|
||||||
|
# Gehe rückwärts durch die gefundenen Elemente, um Decompose sicher zu machen
|
||||||
|
for selector in banner_selectors:
|
||||||
|
try:
|
||||||
|
# select findet alle passenden Elemente
|
||||||
|
potential_banners = content_area.select(selector)
|
||||||
|
for banner in potential_banners:
|
||||||
|
# Zusätzliche Prüfung: Enthält das Element typischen Banner-Text?
|
||||||
|
# Vermeiden Sie das Entfernen von echtem Inhalt, der zufällig das Wort "cookie" enthält.
|
||||||
|
banner_text = banner.get_text(" ", strip=True).lower()
|
||||||
|
keywords = ["cookie", "zustimm", "ablehnen", "einverstanden", "datenschutz", "privacy", "akzeptier", "einstellung", "partner", "analyse", "marketing"]
|
||||||
|
# Prüfe, ob ein Keyword im Text ODER im class/id Namen vorkommt
|
||||||
|
if any(keyword in banner_text for keyword in keywords) or any(keyword in (banner.get('id', '') + banner.get('class', '')).lower() for keyword in keywords):
|
||||||
|
#logger.debug(f"Entferne potenzielles Banner ({selector}) mit Text: {banner_text[:100]}...") # Zu viel Lärm
|
||||||
|
banner.decompose() # Entferne das Element aus dem Baum
|
||||||
|
banners_removed_count += 1
|
||||||
|
except Exception as e_select:
|
||||||
|
# Logge Fehler bei der Banner-Entfernung, aber fahre fort
|
||||||
|
logger.debug(f"Fehler beim Versuch Banner mit Selektor '{selector}' zu entfernen: {e_select}")
|
||||||
|
if banners_removed_count > 0:
|
||||||
|
logger.debug(f"{banners_removed_count} potenzielle Banner-Elemente für {url} entfernt.")
|
||||||
|
|
||||||
|
# --- Text extrahieren aus gefundenem Bereich (oder Body) ---
|
||||||
|
if content_area:
|
||||||
|
# Entferne Skripte und Styles, bevor der Text extrahiert wird
|
||||||
|
for script_or_style in content_area(["script", "style"]):
|
||||||
|
script_or_style.decompose()
|
||||||
|
|
||||||
|
# Extrahiere Text mit Leerzeichen als Trenner
|
||||||
|
text = content_area.get_text(separator=' ', strip=True)
|
||||||
|
text = re.sub(r'\s+', ' ', text).strip() # Normalisiere und trimme Whitespace
|
||||||
|
|
||||||
|
# --- Zusätzliche Prüfung: Ist der extrahierte Text *nur* Banner-Text? ---
|
||||||
|
# Diese Heuristik ist eine Fallback-Maßnahme, wenn die Decompose-Logik nicht perfekt war.
|
||||||
|
banner_keywords_strict = ["cookie", "zustimmen", "ablehnen", "einverstanden", "datenschutz", "privacy", "akzeptier", "einstellung", "partner", "analyse", "marketing"]
|
||||||
|
text_lower = text.lower()
|
||||||
|
keyword_hits = sum(1 for keyword in banner_keywords_strict if keyword in text_lower)
|
||||||
|
|
||||||
|
# Heuristik: Wenn der Text kurz ist UND viele Banner-Keywords enthält -> Verwerfen
|
||||||
|
# Passen Sie die Schwellenwerte an
|
||||||
|
if len(text) < 500 and keyword_hits >= 3: # Wenn Text kürzer als 500 Zeichen und >= 3 Keywords
|
||||||
|
logger.warning(f"WARNUNG: Extrahierter Text für {url} scheint nur Cookie-Banner zu sein (Länge {len(text)}, {keyword_hits} Keywords). Verwerfe Text.")
|
||||||
|
return "k.A. (Nur Cookie-Banner erkannt)"
|
||||||
|
|
||||||
|
# Wenn der Text nach Bereinigung immer noch sehr kurz ist (z.B. nur ein paar Worte)
|
||||||
|
if len(text.split()) < 10 or len(text) < 50:
|
||||||
|
#logger.debug(f"Extrahierter Text für {url} ist sehr kurz ({len(text.split())} Worte, {len(text)} Zeichen).") # Zu viel Lärm
|
||||||
|
# Kann immer noch valide sein, aber ist oft kein relevanter Inhalt.
|
||||||
|
# Geben wir ihn trotzdem zurück, gekürzt.
|
||||||
|
|
||||||
|
pass # Behalte den Text, keine weitere Filterung
|
||||||
|
|
||||||
|
# Begrenzen Sie die Länge des zurückgegebenen Rohtextes
|
||||||
|
result = text[:max_length]
|
||||||
|
logger.debug(f"Website {url} erfolgreich gescrapt. Extrahierter Text (Länge {len(result)}).")
|
||||||
|
# logger.debug(f"Extrahierter Text Anfang: {result[:100]}...") # Zu viel Lärm
|
||||||
|
return result if result else "k.A. (Extraktion leer)" # Rückgabe des gekürzten Textes
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.warning(f"Kein <body> oder spezifischer Inhaltsbereich gefunden in {url}.")
|
||||||
|
return "k.A. (Kein Body gefunden)"
|
||||||
|
|
||||||
|
# Exceptions (wie RequestsErrors) werden vom retry_on_failure Decorator behandelt.
|
||||||
|
# Wenn eine Exception hier durchkommt, hat der Decorator aufgegeben.
|
||||||
|
except Exception as e: # Fangen Sie alle verbleibenden Exceptions, die nicht vom Decorator behandelt wurden
|
||||||
|
logger.error(f"Allgemeiner Fehler beim Scraping von {url}: {type(e).__name__} - {e}")
|
||||||
|
# Die Exception wurde bereits vom Decorator geloggt
|
||||||
|
return f"k.A. (Fehler: {str(e)[:100]}...)" # Signalisiert Fehler
|
||||||
|
|
||||||
# TODO: Weitere globale Helferfunktionen (z.B. für FSM, Emp, Umsatz Schätzung Prompts und Parsing) müssen hier implementiert werden,
|
# TODO: Weitere globale Helferfunktionen (z.B. für FSM, Emp, Umsatz Schätzung Prompts und Parsing) müssen hier implementiert werden,
|
||||||
# falls sie nicht in den DataProcessor integriert wurden. Platzhalter wurden in DataProcessor._process_single_row hinzugefügt.
|
# falls sie nicht in den DataProcessor integriert wurden. Platzhalter wurden in DataProcessor._process_single_row hinzugefügt.
|
||||||
|
|
||||||
@@ -3659,7 +3787,7 @@ class DataProcessor:
|
|||||||
# process_contact_search method... (kommt in Teil 13)
|
# process_contact_search method... (kommt in Teil 13)
|
||||||
# process_wiki_updates_from_chatgpt method... (kommt in Teil 14)
|
# process_wiki_updates_from_chatgpt method... (kommt in Teil 14)
|
||||||
# process_wiki_reextract_missing_an method... (kommt in Teil 14)
|
# process_wiki_reextract_missing_an method... (kommt in Teil 14)
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# === Batch Processing Methods =============================================
|
# === Batch Processing Methods =============================================
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
@@ -4133,22 +4261,22 @@ class DataProcessor:
|
|||||||
|
|
||||||
# --- Worker-Funktion für Scraping ---
|
# --- Worker-Funktion für Scraping ---
|
||||||
# Diese Funktion läuft in einem separaten Thread
|
# Diese Funktion läuft in einem separaten Thread
|
||||||
def scrape_raw_text_task(task_info):
|
# def scrape_raw_text_task(task_info):
|
||||||
row_num = task_info['row_num']
|
# row_num = task_info['row_num']
|
||||||
url = task_info['url']
|
# url = task_info['url']
|
||||||
raw_text = "k.A."
|
# raw_text = "k.A."
|
||||||
error = None
|
# error = None
|
||||||
try:
|
# try:
|
||||||
# Nutzt die globale Funktion get_website_raw mit Retry Decorator
|
# # Nutzt die globale Funktion get_website_raw mit Retry Decorator
|
||||||
raw_text = get_website_raw(url) # Annahme: get_website_raw in utils.py
|
# raw_text = get_website_raw(url) # Annahme: get_website_raw in utils.py
|
||||||
except Exception as e:
|
# except Exception as e:
|
||||||
# Fängt Fehler beim Scraping, damit der Thread nicht abstürzt
|
# # Fängt Fehler beim Scraping, damit der Thread nicht abstürzt
|
||||||
error = f"Scraping Fehler Zeile {row_num} ({url}): {e}"
|
# error = f"Scraping Fehler Zeile {row_num} ({url}): {e}"
|
||||||
self.logger.error(error)
|
# self.logger.error(error)
|
||||||
raw_text = "k.A. (Fehler)" # Setze einen Fehlerwert in den Rohtext
|
# raw_text = "k.A. (Fehler)" # Setze einen Fehlerwert in den Rohtext
|
||||||
|
|
||||||
#logger.debug(f"Scraping Task Zeile {row_num} abgeschlossen. Textlänge: {len(str(raw_text))}.") # Zu viel Lärm
|
#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}
|
# return {"row_num": row_num, "raw_text": raw_text, "error": error}
|
||||||
|
|
||||||
|
|
||||||
# --- Hauptlogik: Iteriere und sammle Batches ---
|
# --- Hauptlogik: Iteriere und sammle Batches ---
|
||||||
@@ -4226,7 +4354,7 @@ class DataProcessor:
|
|||||||
# Nutzt concurrent.futures für paralleles Scraping
|
# Nutzt concurrent.futures für paralleles Scraping
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_scraping_workers) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=max_scraping_workers) as executor:
|
||||||
# Map tasks to futures
|
# Map tasks to futures
|
||||||
future_to_task = {executor.submit(scrape_raw_text_task, task): task for task in tasks_for_processing_batch}
|
future_to_task = {executor.submit(_scrape_raw_text_task_global, task): task for task in tasks_for_processing_batch} # Auf globalen Namen geändert
|
||||||
|
|
||||||
# Process results as they complete
|
# Process results as they complete
|
||||||
for future in concurrent.futures.as_completed(future_to_task):
|
for future in concurrent.futures.as_completed(future_to_task):
|
||||||
@@ -5493,7 +5621,7 @@ class DataProcessor:
|
|||||||
# process_website_details method... (kommt in Teil 16) # Optional/Experimentell
|
# process_website_details method... (kommt in Teil 16) # Optional/Experimentell
|
||||||
# process_wiki_updates_from_chatgpt method... (kommt in Teil 16)
|
# process_wiki_updates_from_chatgpt method... (kommt in Teil 16)
|
||||||
# process_wiki_reextract_missing_an method... (kommt in Teil 16)
|
# process_wiki_reextract_missing_an method... (kommt in Teil 16)
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
# === Utility Methods (ML Data Prep & Training) ============================
|
# === Utility Methods (ML Data Prep & Training) ============================
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user