v1.7.2: behebung OpenAI Scope, URL-Neusuche
feat: Verbesserte Fehlerbehandlung Website-Scraping & URL-Neusuche (v1.7.2)
Diese Version behebt kritische Fehler im Zusammenhang mit dem OpenAI-Modul und verbessert die Robustheit des Website-Scrapings erheblich.
**Fehlerbehebungen:**
- **OpenAI `NameError`:** Behoben durch expliziten globalen Import von `openai` und Anpassung der Exception-Behandlung im `retry_on_failure`-Decorator. OpenAI-Calls (Zusammenfassung, Branchenbewertung) funktionieren nun korrekt.
- **Wikipedia `TypeError`:** Behoben durch Deaktivieren der internen Ratenbegrenzung der `wikipedia`-Bibliothek, da diese bei der Initialisierung der Ratenbegrenzungsvariablen einen Fehler verursachte.
- **Doppelte Definitionen:** Redundante Codeblöcke entfernt.
- **Klassen-Logger:** Korrekte Initialisierung und Verwendung von `self.logger` in den Klassen `GoogleSheetHandler`, `WikipediaScraper` und `DataProcessor` implementiert, um `NameError` für `logger` zu beheben.
- **Funktionsaufrufe:** Korrektur kleinerer Fehler in Funktionsaufrufen (`summarize_batch_openai`, `_scrape_raw_text_task`, `get_numeric_filter_value`).
- **Tippfehler:** `selflogger` zu `self.logger` korrigiert.
**Neue Features & Verbesserungen:**
- **Verbesserte Fehlerbehandlung `get_website_raw`:**
- Standardmäßige Deaktivierung der SSL-Zertifikatsprüfung (`verify=False`) für pragmatischeres Scraping.
- Implementierung von spezifischeren Fehlermeldungen (z.B. "k.A. (Timeout)", "k.A. (SSL Fehler)", "k.A. (HTTP Error 403)") für eine bessere Fehleranalyse direkt im Sheet.
- Einführung eines Markers `URL_CHECK_NEEDED` für URLs, die beim Scraping auf fundamentale Probleme (ConnectionError, 404) hinweisen.
- **User-Agent Rotation:** Eingeführt in `get_website_raw`, um die Wahrscheinlichkeit von 403-Fehlern durch Bot-Erkennung zu reduzieren.
- **Neuer Modus `check_urls`:**
- Implementiert in `DataProcessor.process_url_check`.
- Sucht nach Zeilen mit dem `URL_CHECK_MARKER` oder generischen "k.A. (Fehler...)"-Einträgen in der Rohtext-Spalte (AR), bei denen der AY-Timestamp (SerpAPI Wiki Search Timestamp) noch nicht gesetzt ist.
- Führt für diese Zeilen `serp_website_lookup` aus, um eine neue URL zu finden.
- Bei Fund einer *neuen und anderen* URL: Aktualisiert Spalte D, leert AR, setzt ReEval-Flag (A) und löscht abhängige Timestamps (AT, AO, AN, AX, AP) zur erneuten Verarbeitung.
- Bei identischer oder keiner neuen URL: Aktualisiert AR mit entsprechender Info.
- Setzt immer den AY-Timestamp, um den Prüfversuch zu dokumentieren.
- **Funktion `is_valid_wikipedia_article_url`:** Globale Hilfsfunktion implementiert, um die Gültigkeit von Wikipedia-URLs zu prüfen (existierender Artikel, keine Begriffsklärung). Wird von `process_wiki_updates_from_chatgpt` verwendet.
**Bekannte offene Punkte:**
- ML-Modell und Imputer-Dateien müssen noch erstellt werden (`technician_decision_tree_model.pkl`, `median_imputer.pkl`). Aktuelle Fehler diesbezüglich sind erwartet.
- Implementierung der Platzhalter-Funktionen für FSM, Mitarbeiter- und Umsatzschätzung via OpenAI steht noch aus.
This commit is contained in:
@@ -8,7 +8,7 @@ von Unternehmensdaten, primär aus einem Google Sheet, ergänzt durch Web Scrapi
|
||||
Wikipedia, OpenAI (ChatGPT) und SerpAPI (Google Search, LinkedIn).
|
||||
|
||||
Autor: [Ihr Name/Pseudonym]
|
||||
Version: v1.7.1
|
||||
Version: v1.7.2
|
||||
|
||||
Hinweis zur Struktur:
|
||||
Dieser Code wird in logischen Bloecken uebermittelt. Fuegen Sie die Bloecke
|
||||
@@ -107,7 +107,7 @@ PATTERNS_FILE_JSON = "technician_patterns.json" # Neu (Empfohlen)
|
||||
# --- Globale Konfiguration Klasse ---
|
||||
class Config:
|
||||
"""Zentrale Konfigurationseinstellungen."""
|
||||
VERSION = "v1.7.1"
|
||||
VERSION = "v1.7.2"
|
||||
LANG = "de" # Sprache fuer Wikipedia etc.
|
||||
# ACHTUNG: SHEET_URL ist hier ein Platzhalter. Ersetzen Sie ihn durch Ihre tatsaechliche URL.
|
||||
SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" # <<< ERSETZEN SIE DIES!
|
||||
@@ -192,6 +192,9 @@ BRANCH_MAPPING = {}
|
||||
TARGET_SCHEMA_STRING = "Ziel-Branchenschema nicht verfuegbar."
|
||||
ALLOWED_TARGET_BRANCHES = []
|
||||
|
||||
# Marker für URLs, die erneut per SERP gesucht werden sollen
|
||||
URL_CHECK_MARKER = "URL_CHECK_NEEDED" # <<< NEU HINZUFÜGEN
|
||||
|
||||
# Liste gängiger User-Agents für Rotation
|
||||
USER_AGENTS = [
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
|
||||
@@ -2092,12 +2095,13 @@ def search_linkedin_contacts(company_name, website, position_query, crm_kurzform
|
||||
|
||||
# --- Globale Funktion zum Scrapen des Website Rohtextes ---
|
||||
# Basierend auf get_website_raw aus Teil 7. Global platziert.
|
||||
# Nutzt globale Helfer: simple_normalize_url, clean_text, re, requests, BeautifulSoup, Config, getattr, logger, retry_on_failure.
|
||||
# Nutzt globale Helfer: simple_normalize_url, clean_text, re, requests, BeautifulSoup, Config, getattr, logger, retry_on_failure, USER_AGENTS, URL_CHECK_MARKER.
|
||||
@retry_on_failure # Wende den Decorator auf diese Funktion an
|
||||
def get_website_raw(url, max_length=20000, verify_cert=True): # verify_cert Default bleibt True
|
||||
def get_website_raw(url, max_length=20000, verify_cert=False): # verify_cert Default ist jetzt False
|
||||
"""
|
||||
Holt Textinhalt von einer Website, versucht Cookie-Banner zu umgehen.
|
||||
Implementiert SSL-Fallback und gibt spezifischere Fehlerwerte zurueck.
|
||||
Ignoriert standardmäßig SSL-Zertifikatfehler und gibt spezifischere Fehlerwerte
|
||||
oder einen Marker fuer erneute URL-Suche zurueck.
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
if not url or not isinstance(url, str) or url.strip().lower() in ["k.a.", "kein artikel gefunden", "fehler bei suche", "http:"]:
|
||||
@@ -2110,72 +2114,65 @@ def get_website_raw(url, max_length=20000, verify_cert=True): # verify_cert Defa
|
||||
headers = {
|
||||
"User-Agent": random.choice(USER_AGENTS) # Wählt zufälligen User-Agent aus der Liste
|
||||
}
|
||||
# --- ANPASSUNG START: SSL Fallback & Spezifische Fehler ---
|
||||
response = None
|
||||
error_reason = "Unbekannter Fehler" # Default
|
||||
return_marker = False # Flag für URL_CHECK_MARKER
|
||||
|
||||
for ssl_verify_attempt in [True, False]: # Erst mit True, dann mit False versuchen
|
||||
if not ssl_verify_attempt and not verify_cert: # Wenn verify_cert schon False war, nicht nochmal versuchen
|
||||
break
|
||||
try:
|
||||
current_verify_setting = verify_cert if ssl_verify_attempt else False
|
||||
if not ssl_verify_attempt:
|
||||
logger.warning(f"SSL-Fehler bei verify=True. Versuche erneut mit verify=False fuer {url[:100]}...")
|
||||
try:
|
||||
logger.debug(f"Versuche Website abzurufen: {url[:100]}... (verify={verify_cert})")
|
||||
response = requests.get(
|
||||
url,
|
||||
timeout=getattr(Config, 'REQUEST_TIMEOUT', 20), # Timeout aus Config (Standard 20s)
|
||||
headers=headers,
|
||||
verify=verify_cert, # Nutzt den Default oder den übergebenen Wert
|
||||
allow_redirects=True,
|
||||
stream=False
|
||||
)
|
||||
response.raise_for_status() # Wirft HTTPError fuer 4xx/5xx
|
||||
error_reason = None # Kein Fehler, wenn erfolgreich
|
||||
|
||||
response = requests.get(
|
||||
url,
|
||||
timeout=getattr(Config, 'REQUEST_TIMEOUT', 30), # Timeout aus Config (erhöht auf 30s als Default)
|
||||
headers=headers,
|
||||
verify=current_verify_setting, # Aktuelle Einstellung verwenden
|
||||
allow_redirects=True, # Redirects folgen
|
||||
stream=False # Stream deaktivieren, da wir gesamten Inhalt brauchen
|
||||
)
|
||||
response.raise_for_status() # Wirft HTTPError fuer 4xx/5xx
|
||||
error_reason = None # Kein Fehler, wenn bis hierhin erfolgreich
|
||||
break # Erfolgreicher Request, Schleife verlassen
|
||||
except requests.exceptions.SSLError as e_ssl:
|
||||
error_reason = f"SSL Fehler: {str(e_ssl)[:100]}..."
|
||||
logger.warning(f"SSL Fehler (verify={verify_cert}) fuer {url[:100]}...: {e_ssl}")
|
||||
# Wenn verify=True war, hätte man hier nochmal mit False versuchen können,
|
||||
# aber da der Default jetzt False ist, ist ein erneuter Versuch meist redundant.
|
||||
|
||||
except requests.exceptions.SSLError as e_ssl:
|
||||
error_reason = f"SSL Fehler: {str(e_ssl)[:100]}..."
|
||||
logger.warning(f"SSL Fehler bei verify={current_verify_setting} fuer {url[:100]}...: {e_ssl}")
|
||||
if ssl_verify_attempt: # Wenn es der erste Versuch (verify=True) war
|
||||
verify_cert = False # Setze Flag für nächsten Versuch auf False
|
||||
continue # Mache nächsten Versuch mit verify=False
|
||||
else: # Wenn auch verify=False fehlschlägt
|
||||
logger.error(f"Endgueltiger SSL Fehler auch bei verify=False fuer {url[:100]}...")
|
||||
break # Beende Versuche nach SSL Fehler mit verify=False
|
||||
except requests.exceptions.Timeout as e_timeout:
|
||||
error_reason = f"Timeout ({getattr(Config, 'REQUEST_TIMEOUT', 20)}s)"
|
||||
logger.warning(f"{error_reason} fuer {url[:100]}...")
|
||||
|
||||
except requests.exceptions.Timeout as e_timeout:
|
||||
error_reason = f"Timeout ({getattr(Config, 'REQUEST_TIMEOUT', 30)}s)"
|
||||
logger.warning(f"{error_reason} fuer {url[:100]}...")
|
||||
break # Timeout ist endgültig für diesen Call (Decorator macht Retries)
|
||||
except requests.exceptions.ConnectionError as e_conn:
|
||||
error_reason = f"Connection Error: {str(e_conn)[:100]}..."
|
||||
logger.warning(f"{error_reason} fuer {url[:100]}...")
|
||||
# Prüfe, ob es ein 'Name or service not known' oder 'Connection refused' Fehler ist etc.
|
||||
if "[Errno -2] Name or service not known" in str(e_conn) or \
|
||||
"[Errno -3] Temporary failure in name resolution" in str(e_conn) or \
|
||||
"[Errno 111] Connection refused" in str(e_conn) or \
|
||||
"[Errno 113] No route to host" in str(e_conn) or \
|
||||
"Failed to establish a new connection" in str(e_conn):
|
||||
return_marker = True # Starker Hinweis auf falsche/unerreichbare URL
|
||||
|
||||
except requests.exceptions.ConnectionError as e_conn:
|
||||
error_reason = f"Connection Error: {str(e_conn)[:100]}..."
|
||||
logger.warning(f"{error_reason} fuer {url[:100]}...")
|
||||
break # Connection Error ist endgültig für diesen Call
|
||||
except requests.exceptions.HTTPError as e_http:
|
||||
status_code = e_http.response.status_code
|
||||
error_reason = f"HTTP Error {status_code} ({e_http.response.reason})"
|
||||
logger.warning(f"{error_reason} fuer {url[:100]}...")
|
||||
if status_code == 404: # Bei 404 Fehler auch Marker setzen
|
||||
return_marker = True
|
||||
|
||||
except requests.exceptions.HTTPError as e_http:
|
||||
status_code = e_http.response.status_code
|
||||
error_reason = f"HTTP Error {status_code} ({e_http.response.reason})"
|
||||
logger.warning(f"{error_reason} fuer {url[:100]}...")
|
||||
# Non-retryable HTTP errors werden bereits im Decorator behandelt
|
||||
break # HTTP Error ist endgültig für diesen Call
|
||||
except Exception as e_gen:
|
||||
error_reason = f"Allg. Fehler: {type(e_gen).__name__} - {str(e_gen)[:100]}..."
|
||||
logger.error(f"Allgemeiner Fehler beim Abrufen von {url[:100]}...: {e_gen}")
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
except Exception as e_gen:
|
||||
error_reason = f"Allg. Fehler: {type(e_gen).__name__} - {str(e_gen)[:100]}..."
|
||||
logger.error(f"Allgemeiner Fehler beim Abrufen von {url[:100]}...: {e_gen}")
|
||||
logger.debug(traceback.format_exc())
|
||||
break # Allgemeiner Fehler ist endgültig für diesen Call
|
||||
|
||||
# --- ANPASSUNG ENDE ---
|
||||
|
||||
# Wenn nach allen Versuchen keine gueltige Response erhalten wurde
|
||||
if response is None or error_reason:
|
||||
# Gebe spezifischen Fehlerwert zurueck
|
||||
if return_marker:
|
||||
logger.warning(f"Markiere URL {url[:100]}... zur erneuten Prüfung (Grund: {error_reason}).")
|
||||
return URL_CHECK_MARKER # <<< Speziellen Marker zurückgeben
|
||||
elif response is None or error_reason:
|
||||
return f"k.A. ({error_reason})"
|
||||
|
||||
|
||||
# --- Ab hier: Verarbeitung der erfolgreichen Response ---
|
||||
# --- Ab hier: Verarbeitung der erfolgreichen Response (Code bleibt gleich wie vorher) ---
|
||||
try:
|
||||
response.encoding = response.apparent_encoding
|
||||
soup = BeautifulSoup(response.text, getattr(Config, 'HTML_PARSER', 'html.parser'))
|
||||
@@ -2228,8 +2225,8 @@ def get_website_raw(url, max_length=20000, verify_cert=True): # verify_cert Defa
|
||||
logger.warning(f"WARNUNG: Extrahierter Text fuer {url[:100]}... scheint nur Cookie-Banner zu sein (Laenge {len(text)}, {keyword_hits} Keywords). Verwerfe Text.")
|
||||
return "k.A. (Nur Cookie-Banner erkannt)"
|
||||
|
||||
if len(text.split()) < 10 or len(text) < 50:
|
||||
pass
|
||||
if len(text.split()) < 10 or len(text) < 50: # Ursprünglich 50, aber das ist sehr restriktiv für kurze Seiten
|
||||
pass # Nur kurze Texte sind nicht unbedingt ein Fehler
|
||||
|
||||
result = text[:max_length]
|
||||
logger.debug(f"Website {url[:100]}... erfolgreich gescrapt. Extrahierter Text (Laenge {len(result)}).")
|
||||
@@ -2243,7 +2240,6 @@ def get_website_raw(url, max_length=20000, verify_cert=True): # verify_cert Defa
|
||||
logger.debug(traceback.format_exc())
|
||||
return f"k.A. (Fehler Parsing: {str(e_parse)[:50]}...)"
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Ende Website Raw Scraping Funktion Block
|
||||
# ==============================================================================
|
||||
@@ -2342,6 +2338,75 @@ def scrape_website_details(url):
|
||||
return f"k.A. (Fehler: {str(e)[:100]}...)" # Signalisiert Fehler (gekuerzt)
|
||||
|
||||
|
||||
def is_valid_wikipedia_article_url(url_to_check, lang=None):
|
||||
"""
|
||||
Prueft, ob eine gegebene URL zu einem gueltigen, existierenden Wikipedia-Artikel
|
||||
fuehrt (keine Begriffsklaerung, kein Fehler).
|
||||
Nutzt die wikipedia-Bibliothek.
|
||||
|
||||
Args:
|
||||
url_to_check (str): Die zu pruefende Wikipedia-URL.
|
||||
lang (str, optional): Die Sprache der Wikipedia (z.B. 'de', 'en').
|
||||
Wenn None, wird die aktuell in der wikipedia-Bibliothek
|
||||
gesetzte Sprache verwendet.
|
||||
|
||||
Returns:
|
||||
bool: True, wenn die URL zu einem gueltigen Artikel fuehrt, sonst False.
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
if not url_to_check or not isinstance(url_to_check, str) or "wikipedia.org/wiki/" not in url_to_check.lower():
|
||||
logger.debug(f"is_valid_wikipedia_article_url: Ungueltige URL-Struktur: {url_to_check[:100]}...")
|
||||
return False
|
||||
|
||||
original_lang = None
|
||||
if lang:
|
||||
try:
|
||||
original_lang = wikipedia.get_lang() # Speichere aktuelle Sprache
|
||||
wikipedia.set_lang(lang)
|
||||
logger.debug(f"Temporaer Wikipedia-Sprache auf '{lang}' gesetzt für Validierung.")
|
||||
except Exception as e_lang:
|
||||
logger.warning(f"Konnte Wikipedia-Sprache nicht auf '{lang}' setzen für Validierung: {e_lang}")
|
||||
# Fahre mit der global eingestellten Sprache fort
|
||||
|
||||
|
||||
is_valid = False
|
||||
try:
|
||||
# Extrahiere den Titel aus der URL
|
||||
title_part = url_to_check.split('/wiki/', 1)[1].split('#')[0]
|
||||
title = unquote(title_part).replace('_', ' ')
|
||||
|
||||
logger.debug(f"Validiere Wikipedia-Artikel: '{title[:100]}...' (URL: {url_to_check[:100]}...)")
|
||||
# Versuche, die Seite zu laden. auto_suggest=False, um keine alternativen Vorschlaege zu bekommen.
|
||||
# preload=True laedt den Inhalt direkt, um Fehler frueh zu erkennen.
|
||||
page = wikipedia.page(title, auto_suggest=False, preload=True)
|
||||
# Wenn keine Exception geworfen wurde, existiert die Seite.
|
||||
# Wir nehmen an, dass es ein gueltiger Artikel ist, wenn keine DisambiguationError auftritt.
|
||||
# Eine genauere Pruefung, ob es wirklich ein *Unternehmens*-Artikel ist,
|
||||
# wuerde die Logik von WikipediaScraper._validate_article erfordern.
|
||||
is_valid = True
|
||||
logger.debug(f" -> Artikel '{title[:100]}...' scheint valide zu sein (Seite geladen).")
|
||||
|
||||
except wikipedia.exceptions.PageError:
|
||||
logger.debug(f" -> Seite '{title[:100]}...' nicht gefunden (PageError).")
|
||||
is_valid = False
|
||||
except wikipedia.exceptions.DisambiguationError as e_disamb:
|
||||
logger.debug(f" -> Seite '{title[:100]}...' ist eine Begriffsklaerungsseite. Optionen: {str(e_disamb.options)[:100]}...")
|
||||
is_valid = False # Begriffsklaerungen sind keine direkten Artikel
|
||||
except Exception as e:
|
||||
logger.error(f" -> Unerwarteter Fehler bei Validierung von '{title[:100]}...': {type(e).__name__} - {e}")
|
||||
logger.debug(traceback.format_exc())
|
||||
is_valid = False
|
||||
finally:
|
||||
if original_lang: # Setze Sprache zurueck, falls sie geaendert wurde
|
||||
try:
|
||||
wikipedia.set_lang(original_lang)
|
||||
logger.debug(f"Wikipedia-Sprache zurueck auf '{original_lang}' gesetzt.")
|
||||
except Exception as e_lang_reset:
|
||||
logger.warning(f"Konnte Wikipedia-Sprache nicht zurueck auf '{original_lang}' setzen: {e_lang_reset}")
|
||||
|
||||
return is_valid
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Ende Website Details Scraping Funktion Block
|
||||
# ==============================================================================
|
||||
@@ -7253,6 +7318,173 @@ class DataProcessor:
|
||||
# Ende DataProcessor Klasse Batch: SerpAPI Suchen & Contacts Block
|
||||
# ==============================================================================
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# === Utility Methods (URL Check & Update) =================================
|
||||
# ==========================================================================
|
||||
|
||||
def process_url_check(self, start_sheet_row=None, end_sheet_row=None, limit=None):
|
||||
"""
|
||||
Sucht nach Zeilen, die in Spalte AR mit URL_CHECK_MARKER oder bekannten "k.A. (Fehler...)"
|
||||
Mustern markiert sind UND bei denen der AY-Timestamp (SerpAPI Wiki Search Timestamp) leer ist
|
||||
(außer bei URL_CHECK_MARKER, der immer eine Suche auslöst).
|
||||
Versucht, eine neue URL ueber SerpAPI zu finden.
|
||||
Wenn erfolgreich und URL ist NEU: Aktualisiert D, loescht AR, setzt ReEval-Flag (A) und loescht Timestamps.
|
||||
Wenn URL identisch oder keine neue URL gefunden: AR wird entsprechend aktualisiert.
|
||||
Setzt immer den AY-Timestamp (als Timestamp der URL-Prüfung).
|
||||
|
||||
Args:
|
||||
start_sheet_row (int, optional): Die 1-basierte Startzeile im Sheet. Defaults to None (ab Zeile 7).
|
||||
end_sheet_row (int, optional): Die 1-basierte Endzeile im Sheet. Defaults to None (bis Ende Sheet).
|
||||
limit (int, optional): Maximale Anzahl ZU VERARBEITENDER Zeilen. Defaults to None (Unbegrenzt).
|
||||
"""
|
||||
self.logger.info(f"Starte Modus 'check_urls'. Sucht nach '{URL_CHECK_MARKER}' oder 'k.A. (Fehler...)' in AR. Bereich: {start_sheet_row if start_sheet_row is not None else 'Start'}-{end_sheet_row if end_sheet_row is not None else 'Ende'}, Limit: {limit if limit is not None else 'Unbegrenzt'}...")
|
||||
|
||||
if not self.sheet_handler.load_data():
|
||||
self.logger.error("Fehler beim Laden der Daten fuer URL Check.")
|
||||
return
|
||||
|
||||
all_data = self.sheet_handler.get_all_data_with_headers()
|
||||
header_rows = self.sheet_handler._header_rows
|
||||
total_sheet_rows = len(all_data)
|
||||
|
||||
if start_sheet_row is None: start_sheet_row = header_rows + 1
|
||||
if end_sheet_row is None: end_sheet_row = total_sheet_rows
|
||||
|
||||
self.logger.info(f"Suchbereich fuer URL Checks: Sheet-Zeilen {start_sheet_row} bis {end_sheet_row}.")
|
||||
if start_sheet_row > end_sheet_row or start_sheet_row > total_sheet_rows:
|
||||
self.logger.info("Berechneter Start liegt nach dem Ende des Bereichs oder Sheets. Keine Zeilen zu verarbeiten.")
|
||||
return
|
||||
|
||||
required_keys = [
|
||||
"Website Rohtext", "CRM Name", "CRM Website", "ReEval Flag",
|
||||
"Website Scrape Timestamp", "Timestamp letzte Pruefung",
|
||||
"Wikipedia Timestamp", "Wiki Verif. Timestamp", "SerpAPI Wiki Search Timestamp",
|
||||
"Version"
|
||||
]
|
||||
col_indices = {key: COLUMN_MAP.get(key) for key in required_keys}
|
||||
if None in col_indices.values():
|
||||
missing = [k for k, v in col_indices.items() if v is None]
|
||||
self.logger.critical(f"FEHLER: Benoetigte Spaltenschluessel fehlen in COLUMN_MAP fuer process_url_check: {missing}. Breche ab.")
|
||||
return
|
||||
|
||||
ar_letter = self.sheet_handler._get_col_letter(col_indices["Website Rohtext"] + 1)
|
||||
d_letter = self.sheet_handler._get_col_letter(col_indices["CRM Website"] + 1)
|
||||
a_letter = self.sheet_handler._get_col_letter(col_indices["ReEval Flag"] + 1)
|
||||
at_letter = self.sheet_handler._get_col_letter(col_indices["Website Scrape Timestamp"] + 1)
|
||||
ao_letter = self.sheet_handler._get_col_letter(col_indices["Timestamp letzte Pruefung"] + 1)
|
||||
an_letter = self.sheet_handler._get_col_letter(col_indices["Wikipedia Timestamp"] + 1)
|
||||
ax_letter = self.sheet_handler._get_col_letter(col_indices["Wiki Verif. Timestamp"] + 1)
|
||||
ay_letter = self.sheet_handler._get_col_letter(col_indices["SerpAPI Wiki Search Timestamp"] + 1) # Timestamp dieser Funktion
|
||||
ap_letter = self.sheet_handler._get_col_letter(col_indices["Version"] + 1)
|
||||
|
||||
ka_error_patterns = [
|
||||
"k.A.", "k.A. (Extraktion leer)", "k.A. (Nur Cookie-Banner erkannt)",
|
||||
"k.A. (Kein Body gefunden)", "k.A. (Fehler Parsing:", "k.A. (Unerwarteter Fehler Task)",
|
||||
"k.A. (Fehler Scraping:", "k.A. (Timeout", "k.A. (SSL Fehler",
|
||||
"k.A. (Connection Error", "k.A. (HTTP Error", URL_CHECK_MARKER
|
||||
]
|
||||
|
||||
all_sheet_updates = []
|
||||
processed_count = 0
|
||||
skipped_count = 0
|
||||
found_new_url_count = 0
|
||||
now_timestamp_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
for i in range(start_sheet_row, end_sheet_row + 1):
|
||||
row_index_in_list = i - 1
|
||||
if row_index_in_list >= total_sheet_rows: break
|
||||
|
||||
row = all_data[row_index_in_list]
|
||||
if not any(cell and isinstance(cell, str) and cell.strip() for cell in row):
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
ar_value = self._get_cell_value_safe(row, "Website Rohtext").strip()
|
||||
ay_timestamp_value = self._get_cell_value_safe(row, "SerpAPI Wiki Search Timestamp").strip() # Verwende den spezifischen Timestamp für diese Funktion
|
||||
|
||||
processing_needed_for_row = False
|
||||
is_marker_case = ar_value == URL_CHECK_MARKER
|
||||
is_ka_error_case = any(pattern in ar_value for pattern in ka_error_patterns if pattern != URL_CHECK_MARKER)
|
||||
|
||||
if is_marker_case: # URL_CHECK_MARKER löst immer eine Suche aus
|
||||
processing_needed_for_row = True
|
||||
elif is_ka_error_case and not ay_timestamp_value: # Alte k.A.-Fehler nur, wenn AY-Timestamp noch nicht gesetzt wurde
|
||||
processing_needed_for_row = True
|
||||
|
||||
if not processing_needed_for_row:
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
processed_count += 1
|
||||
if limit is not None and isinstance(limit, int) and limit > 0 and processed_count > limit:
|
||||
self.logger.info(f"Verarbeitungslimit ({limit}) fuer process_url_check erreicht.")
|
||||
break
|
||||
|
||||
company_name = self._get_cell_value_safe(row, "CRM Name").strip()
|
||||
old_crm_website_url = self._get_cell_value_safe(row, "CRM Website").strip()
|
||||
normalized_old_crm_url = simple_normalize_url(old_crm_website_url)
|
||||
|
||||
if not company_name:
|
||||
self.logger.warning(f"Zeile {i}: Uebersprungen (kein Firmenname fuer Suche vorhanden).")
|
||||
skipped_count += 1
|
||||
updates_for_row_skip = [{'range': f'{ay_letter}{i}', 'values': [[now_timestamp_str]]}] # Timestamp trotzdem setzen
|
||||
all_sheet_updates.extend(updates_for_row_skip)
|
||||
continue
|
||||
|
||||
self.logger.info(f"Zeile {i}: AR='{ar_value[:50]}...'. Suche neue URL für '{company_name[:50]}...' (Aktuell D: '{old_crm_website_url[:50]}...')...")
|
||||
|
||||
updates_for_row = []
|
||||
new_url_found_str = "k.A."
|
||||
|
||||
try:
|
||||
new_url_found_str = serp_website_lookup(company_name)
|
||||
normalized_new_url = simple_normalize_url(new_url_found_str)
|
||||
|
||||
if new_url_found_str != "k.A." and normalized_new_url != "k.A.":
|
||||
if normalized_new_url != normalized_old_crm_url:
|
||||
self.logger.info(f" -> Neue, andere URL gefunden: {new_url_found_str}. Alte war: '{old_crm_website_url}'. Bereite Update vor.")
|
||||
found_new_url_count += 1
|
||||
updates_for_row.append({'range': f'{d_letter}{i}', 'values': [[new_url_found_str]]})
|
||||
updates_for_row.append({'range': f'{ar_letter}{i}', 'values': [['']]})
|
||||
updates_for_row.append({'range': f'{a_letter}{i}', 'values': [['x']]})
|
||||
updates_for_row.append({'range': f'{at_letter}{i}', 'values': [['']]})
|
||||
updates_for_row.append({'range': f'{ao_letter}{i}', 'values': [['']]})
|
||||
updates_for_row.append({'range': f'{an_letter}{i}', 'values': [['']]})
|
||||
updates_for_row.append({'range': f'{ax_letter}{i}', 'values': [['']]})
|
||||
updates_for_row.append({'range': f'{ay_letter}{i}', 'values': [['']]}) # Wird unten explizit neu gesetzt
|
||||
updates_for_row.append({'range': f'{ap_letter}{i}', 'values': [['']]})
|
||||
else:
|
||||
self.logger.info(f" -> SerpAPI fand URL '{new_url_found_str}', aber diese ist identisch mit der bereits vorhandenen URL in Spalte D. Keine Änderung in D.")
|
||||
updates_for_row.append({'range': f'{ar_letter}{i}', 'values': [["k.A. (URL via SerpAPI identisch mit alter URL)"]]})
|
||||
else:
|
||||
self.logger.warning(f" -> Keine neue gueltige URL via SerpAPI für '{company_name[:50]}...' gefunden. Setze AR auf 'k.A. (Keine URL bei Neusuche)'.")
|
||||
updates_for_row.append({'range': f'{ar_letter}{i}', 'values': [["k.A. (Keine URL bei Neusuche)"]]})
|
||||
|
||||
except Exception as e_serp_lookup:
|
||||
self.logger.error(f"FEHLER bei SERP Website Lookup für Zeile {i} ('{company_name[:50]}...'): {e_serp_lookup}")
|
||||
updates_for_row.append({'range': f'{ar_letter}{i}', 'values': [[f"k.A. (Fehler URL Suche)"]]})
|
||||
pass
|
||||
|
||||
updates_for_row.append({'range': f'{ay_letter}{i}', 'values': [[now_timestamp_str]]}) # AY Timestamp immer setzen
|
||||
all_sheet_updates.extend(updates_for_row)
|
||||
|
||||
if len(all_sheet_updates) >= update_batch_row_limit * 3 : # Angepasst, da Anzahl Updates variiert
|
||||
self.logger.debug(f" Sende gesammelte Sheet-Updates ({len(all_sheet_updates)} Operationen)...")
|
||||
success = self.sheet_handler.batch_update_cells(all_sheet_updates)
|
||||
if success: self.logger.info(f" Sheet-Update für {len(all_sheet_updates)} Operationen erfolgreich.")
|
||||
all_sheet_updates = []
|
||||
|
||||
serp_delay = getattr(Config, 'SERPAPI_DELAY', 1.5)
|
||||
time.sleep(serp_delay)
|
||||
|
||||
if all_sheet_updates:
|
||||
self.logger.info(f"Sende FINALE gesammelte Sheet-Updates ({len(all_sheet_updates)} Operationen)...")
|
||||
success = self.sheet_handler.batch_update_cells(all_sheet_updates)
|
||||
if success: self.logger.info(f"FINALES Sheet-Update erfolgreich.")
|
||||
|
||||
self.logger.info(f"Modus 'check_urls' abgeschlossen. {processed_count} Zeilen mit Marker/Fehler verarbeitet, {found_new_url_count} neue URLs gefunden, {skipped_count} Zeilen uebersprungen.")
|
||||
|
||||
# ==========================================================================
|
||||
# === Utility Methods (ML Data Prep & Training) ============================
|
||||
# ==========================================================================
|
||||
@@ -8969,6 +9201,7 @@ def main():
|
||||
"Einzelne Dienstprogramme / Suchen": [
|
||||
"find_wiki_serp", # Nutzt process_find_wiki_serp (Block 30)
|
||||
"website_lookup", # Nutzt process_serp_website_lookup (Block 30)
|
||||
"check_urls", # <<< NEUER MODUS HIER EINFÜGEN
|
||||
"contacts", # Nutzt process_contact_search (Block 30)
|
||||
"update_wiki_suggestions", # Nutzt process_wiki_updates_from_chatgpt (Block 32)
|
||||
"wiki_reextract_missing_an", # Nutzt process_wiki_reextract_missing_an (Block 32)
|
||||
@@ -9395,6 +9628,13 @@ def main():
|
||||
limit=limit_arg # Kann manuell gesetzt werden
|
||||
)
|
||||
|
||||
elif selected_mode == "check_urls":
|
||||
data_processor.process_url_check(
|
||||
start_sheet_row=start_row_arg,
|
||||
end_sheet_row=end_row_arg,
|
||||
limit=limit_arg
|
||||
)
|
||||
|
||||
elif selected_mode == "contacts": # Nutzt process_contact_search (Block 30)
|
||||
# contacts sucht leere AM. Nutzt limit. Start/Endzeile koennen manuell gesetzt werden.
|
||||
data_processor.process_contact_search(
|
||||
|
||||
Reference in New Issue
Block a user