bugfix
This commit is contained in:
@@ -1493,35 +1493,29 @@ class GoogleSheetHandler:
|
||||
class WikipediaScraper:
|
||||
"""
|
||||
Handles searching Wikipedia articles and extracting relevant company data.
|
||||
Version: 1.6.5 logic - Improved infobox parsing reliability and detailed logging.
|
||||
Version: 1.6.5 logic - Improved infobox parsing, disambiguation handling, and standard logging.
|
||||
"""
|
||||
def __init__(self, user_agent=None):
|
||||
"""
|
||||
Initialisiert den Scraper mit einer Requests-Session und Logger.
|
||||
"""
|
||||
# --- Logging explizit für diese Klasse holen und Level setzen ---
|
||||
# Holt Logger basierend auf dem Modulnamen (__name__ wird hier zu 'WikipediaScraper' oder dem Dateinamen)
|
||||
self.logger = logging.getLogger(__name__)
|
||||
# Das Level wird global in main() gesetzt, hier zur Sicherheit nochmal debug loggen
|
||||
self.logger.debug(f"Logger für WikipediaScraper ('{__name__}') initialisiert.")
|
||||
# --- Ende Logging-Anpassung ---
|
||||
|
||||
# Verwende User-Agent aus Config oder einen Standardwert
|
||||
self.user_agent = user_agent or getattr(Config, 'USER_AGENT', 'Mozilla/5.0 (compatible; Datenanreicherungsskript/1.0)')
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({'User-Agent': self.user_agent})
|
||||
self.logger.debug(f"Requests Session mit User-Agent '{self.user_agent}' initialisiert.")
|
||||
|
||||
# Erweiterte Keyword Map für robustere Infobox-Extraktion
|
||||
self.keywords_map = {
|
||||
'branche': ['branche', 'wirtschaftszweig', 'industry', 'tätigkeit', 'sektor', 'produkte', 'leistungen'],
|
||||
'umsatz': ['umsatz', 'erlös', 'revenue', 'jahresumsatz', 'konzernumsatz', 'ergebnis'],
|
||||
'mitarbeiter': ['mitarbeiter', 'mitarbeiterzahl', 'beschäftigte', 'employees', 'number of employees', 'personal', 'belegschaft']
|
||||
}
|
||||
try:
|
||||
wiki_lang = getattr(Config, 'LANG', 'de') # Sprache aus Config holen
|
||||
wiki_lang = getattr(Config, 'LANG', 'de')
|
||||
wikipedia.set_lang(wiki_lang)
|
||||
wikipedia.set_rate_limiting(True) # Respektiere Wikipedia API Limits
|
||||
wikipedia.set_rate_limiting(True)
|
||||
self.logger.info(f"Wikipedia library language set to '{wiki_lang}'. Rate limiting enabled.")
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Fehler beim Setzen der Wikipedia-Sprache oder Rate Limiting: {e}")
|
||||
@@ -1531,13 +1525,9 @@ class WikipediaScraper:
|
||||
if not website or not isinstance(website, str): return ""
|
||||
website_lower = website.lower().strip()
|
||||
if not website_lower or website_lower == 'k.a.': return ""
|
||||
# Schema entfernen
|
||||
website_lower = re.sub(r'^https?:\/\/', '', website_lower)
|
||||
# User:Pass entfernen
|
||||
if '@' in website_lower: website_lower = website_lower.split('@', 1)[1]
|
||||
# www. entfernen
|
||||
if website_lower.startswith('www.'): website_lower = website_lower[4:]
|
||||
# Pfad und Port entfernen
|
||||
domain = website_lower.split('/')[0].split(':')[0]
|
||||
return domain
|
||||
|
||||
@@ -1546,27 +1536,24 @@ class WikipediaScraper:
|
||||
if not company_name: return []
|
||||
terms = set()
|
||||
full_domain = self._get_full_domain(website)
|
||||
if full_domain: terms.add(full_domain) # Domain als Suchbegriff
|
||||
if full_domain: terms.add(full_domain)
|
||||
|
||||
# Normalisierten Namen hinzufügen (Annahme: Funktion existiert)
|
||||
normalized_name = normalize_company_name(company_name)
|
||||
normalized_name = normalize_company_name(company_name) # Annahme: existiert
|
||||
if normalized_name:
|
||||
name_parts = normalized_name.split()
|
||||
if len(name_parts) > 0: terms.add(name_parts[0]) # Erstes Wort
|
||||
if len(name_parts) > 1: terms.add(" ".join(name_parts[:2])) # Erste zwei Worte
|
||||
terms.add(normalized_name) # Ganzer normalisierter Name
|
||||
if len(name_parts) > 0: terms.add(name_parts[0])
|
||||
if len(name_parts) > 1: terms.add(" ".join(name_parts[:2]))
|
||||
terms.add(normalized_name)
|
||||
|
||||
# Originalnamen hinzufügen, falls signifikant anders
|
||||
company_name_lower = company_name.lower()
|
||||
if company_name_lower != normalized_name and company_name_lower not in terms:
|
||||
terms.add(company_name_lower)
|
||||
|
||||
# Nimm nur sinnvolle (nicht leere) und begrenzte Anzahl Begriffe
|
||||
final_terms = [term for term in list(terms) if term][:5] # Max 5 Begriffe
|
||||
final_terms = [term for term in list(terms) if term][:5]
|
||||
self.logger.debug(f"Generierte Suchbegriffe für '{company_name}': {final_terms}")
|
||||
return final_terms
|
||||
|
||||
@retry_on_failure # Annahme: Decorator existiert und behandelt Exceptions
|
||||
@retry_on_failure # Annahme: Decorator existiert
|
||||
def _get_page_soup(self, url):
|
||||
"""Holt HTML von einer URL und gibt ein BeautifulSoup-Objekt zurück."""
|
||||
if not url or not isinstance(url, str) or not url.startswith("http"):
|
||||
@@ -1574,23 +1561,20 @@ class WikipediaScraper:
|
||||
return None
|
||||
try:
|
||||
self.logger.debug(f"_get_page_soup: Rufe URL ab: {url}")
|
||||
response = self.session.get(url, timeout=20) # Timeout etwas höher
|
||||
response.raise_for_status() # Fehler für 4xx/5xx auslösen
|
||||
# Encoding explizit auf UTF-8 setzen (Standard für Wikipedia)
|
||||
response = self.session.get(url, timeout=20)
|
||||
response.raise_for_status()
|
||||
response.encoding = 'utf-8'
|
||||
soup = BeautifulSoup(response.text, getattr(Config, 'HTML_PARSER', 'html.parser')) # Nutze Parser aus Config
|
||||
soup = BeautifulSoup(response.text, getattr(Config, 'HTML_PARSER', 'html.parser'))
|
||||
self.logger.debug(f"_get_page_soup: Parsen von {url} erfolgreich.")
|
||||
return soup
|
||||
except requests.exceptions.Timeout:
|
||||
self.logger.error(f"_get_page_soup: Timeout beim Abrufen von {url}")
|
||||
raise # Fehler weitergeben für Retry
|
||||
raise
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.logger.error(f"_get_page_soup: Netzwerkfehler beim Abrufen von HTML von {url}: {e}")
|
||||
raise e # Fehler weitergeben für Retry
|
||||
raise e
|
||||
except Exception as e:
|
||||
self.logger.error(f"_get_page_soup: Fehler beim Parsen von HTML von {url}: {e}")
|
||||
# Parsing-Fehler nicht unbedingt wiederholen, None zurückgeben? Oder doch für Retry?
|
||||
# Fürs Erste weitergeben, falls temporäres Problem
|
||||
raise e
|
||||
|
||||
def _validate_article(self, page, company_name, website):
|
||||
@@ -1609,21 +1593,19 @@ class WikipediaScraper:
|
||||
similarity = SequenceMatcher(None, normalized_title, normalized_company).ratio()
|
||||
self.logger.debug(f" -> Titelähnlichkeit: {similarity:.2f} ('{normalized_title}' vs '{normalized_company}')")
|
||||
|
||||
# 2. Link-Prüfung (nur wenn Domain vorhanden)
|
||||
# 2. Link-Prüfung
|
||||
domain_found = False
|
||||
if full_domain:
|
||||
self.logger.debug(f" -> Suche nach Domain '{full_domain}' in Links von {page.url}...")
|
||||
soup = self._get_page_soup(page.url) # Hole Soup für Link-Prüfung
|
||||
soup = self._get_page_soup(page.url)
|
||||
if soup:
|
||||
# Suche zuerst in der Infobox
|
||||
infobox = soup.select_one('table[class*="infobox"]')
|
||||
if infobox:
|
||||
website_links = infobox.find_all('a', href=True)
|
||||
for link in website_links:
|
||||
href = link.get('href', '')
|
||||
if href.startswith('http') and full_domain in self._get_full_domain(href): # Vergleiche Domains
|
||||
if href.startswith('http') and full_domain in self._get_full_domain(href):
|
||||
link_text = link.get_text(strip=True).lower()
|
||||
# Prüfe, ob Keyword im Link-Text ODER in der Tabellenüberschrift der Zeile
|
||||
th = link.find_previous('th')
|
||||
th_text = th.get_text(strip=True).lower() if th else ""
|
||||
if any(kw in link_text for kw in ['website', 'webseite', 'offizielle']) or \
|
||||
@@ -1631,39 +1613,34 @@ class WikipediaScraper:
|
||||
self.logger.debug(f" -> Domain '{full_domain}' in Infobox-Link gefunden (TH: '{th_text}', Text: '{link_text}', URL: {href})")
|
||||
domain_found = True
|
||||
break
|
||||
else: # Akzeptiere auch ohne Keyword, wenn URL eindeutig scheint
|
||||
else:
|
||||
self.logger.debug(f" -> Domain '{full_domain}' in Infobox-Link gefunden (URL: {href}, kein Keyword-Match im Text/TH)")
|
||||
domain_found = True
|
||||
break
|
||||
# Wenn nicht in Infobox, suche in allen externen Links
|
||||
if not domain_found:
|
||||
self.logger.debug(" -> Domain nicht in Infobox-Links gefunden, suche in allen externen Links...")
|
||||
all_links = soup.find_all('a', href=True, class_=re.compile(r'.*\bexternal\b.*')) # Suche explizit externe Links
|
||||
if not all_links: # Fallback: alle Links prüfen
|
||||
all_links = soup.find_all('a', href=True)
|
||||
|
||||
all_links = soup.find_all('a', href=True, class_=re.compile(r'.*\bexternal\b.*'))
|
||||
if not all_links: all_links = soup.find_all('a', href=True)
|
||||
for link in all_links:
|
||||
href = link.get('href', '')
|
||||
if href.startswith('http') and full_domain in self._get_full_domain(href):
|
||||
# Ignoriere Links zu anderen Wikimedia-Projekten etc.
|
||||
if not any(site in href for site in ['wikipedia.org', 'wikimedia.org', 'wikidata.org', 'archive.org', 'webcitation.org']):
|
||||
self.logger.debug(f" -> Domain '{full_domain}' in externem Link gefunden (URL: {href})")
|
||||
domain_found = True
|
||||
break # Erster Treffer reicht
|
||||
break
|
||||
else:
|
||||
self.logger.warning(f" -> Konnte HTML für Link-Prüfung von {page.url} nicht laden.")
|
||||
else:
|
||||
self.logger.debug(" -> Keine Website-Domain für Link-Prüfung vorhanden.")
|
||||
|
||||
# 3. Entscheidung
|
||||
threshold = getattr(Config, 'SIMILARITY_THRESHOLD', 0.65) # Standard-Schwelle aus Config
|
||||
threshold = getattr(Config, 'SIMILARITY_THRESHOLD', 0.65)
|
||||
if domain_found:
|
||||
# Schwelle signifikant lockern, wenn Domain passt
|
||||
threshold = max(0.35, threshold - 0.3) # Beispiel: 0.65 -> 0.35
|
||||
threshold = max(0.35, threshold - 0.3)
|
||||
self.logger.debug(f" -> Domain gefunden, Ähnlichkeitsschwelle angepasst auf {threshold:.2f}")
|
||||
|
||||
is_valid = similarity >= threshold
|
||||
log_level = logging.INFO if is_valid else logging.DEBUG # Logge Erfolg als INFO
|
||||
log_level = logging.INFO if is_valid else logging.DEBUG
|
||||
self.logger.log(log_level, f" => Artikel '{page.title}' {'VALIDIERT' if is_valid else 'NICHT validiert'} (Ähnlichkeit={similarity:.2f}, Schwelle={threshold:.2f}, Domain gefunden? {domain_found})")
|
||||
return is_valid
|
||||
|
||||
@@ -1683,7 +1660,6 @@ class WikipediaScraper:
|
||||
else: self.logger.debug("Kein 'div#mw-normal-catlinks' gefunden.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fehler beim Extrahieren der Kategorien: {e}")
|
||||
|
||||
return ", ".join(cats_filtered) if cats_filtered else "k.A."
|
||||
|
||||
def _extract_first_paragraph_from_soup(self, soup):
|
||||
@@ -1691,40 +1667,30 @@ class WikipediaScraper:
|
||||
if not soup: return "k.A."
|
||||
paragraph_text = "k.A."
|
||||
try:
|
||||
# Suche bevorzugt im Hauptinhalt
|
||||
content_div = soup.find('div', class_='mw-parser-output')
|
||||
search_area = content_div if content_div else soup # Fallback auf ganzen Soup
|
||||
search_area = content_div if content_div else soup
|
||||
|
||||
# Finde alle <p>-Tags direkt unter dem Suchbereich (oft relevanter)
|
||||
paragraphs = search_area.find_all('p', recursive=False)
|
||||
if not paragraphs: # Fallback: Alle <p>-Tags suchen
|
||||
paragraphs = search_area.find_all('p', recursive=True)
|
||||
if not paragraphs: paragraphs = search_area.find_all('p', recursive=True)
|
||||
self.logger.debug(f"Suche ersten Absatz in {len(paragraphs)} gefundenen <p>-Tags...")
|
||||
|
||||
for idx, p in enumerate(paragraphs):
|
||||
# Überspringe leere oder zu kurze Absätze
|
||||
if not p.get_text(strip=True):
|
||||
self.logger.debug(f" -> Überspringe leeres <p> Tag (Index {idx})")
|
||||
continue
|
||||
# Überspringe Absätze, die nur Bilder/Tabellen etc. enthalten könnten
|
||||
if p.find(['img', 'table', 'figure', 'div'], recursive=False):
|
||||
self.logger.debug(f" -> Überspringe <p> Tag (Index {idx}), da er Blockelemente enthält.")
|
||||
continue
|
||||
|
||||
# Entferne Referenz-Links `[1]`, `[2]` etc.
|
||||
for sup in p.find_all('sup', class_='reference'):
|
||||
sup.decompose()
|
||||
# Entferne Koordinaten (oft am Anfang in <span>)
|
||||
for span in p.find_all('span', id='coordinates'):
|
||||
span.decompose()
|
||||
for sup in p.find_all('sup', class_='reference'): sup.decompose()
|
||||
for span in p.find_all('span', id='coordinates'): span.decompose()
|
||||
|
||||
text = clean_text(p.get_text(separator=' ', strip=True))
|
||||
text = clean_text(p.get_text(separator=' ', strip=True)) # Annahme: clean_text existiert
|
||||
|
||||
# Prüfe Mindestlänge
|
||||
if text and len(text) > 40: # Mindestlänge etwas erhöht
|
||||
if text and len(text) > 40:
|
||||
self.logger.debug(f" -> Ersten gültigen Absatz (Index {idx}) gefunden: {text[:100]}...")
|
||||
paragraph_text = text[:1000] # Begrenze Länge
|
||||
break # Nimm den ersten passenden
|
||||
paragraph_text = text[:1000]
|
||||
break
|
||||
else:
|
||||
self.logger.debug(f" -> Überspringe <p> Tag (Index {idx}), Text zu kurz oder leer nach clean_text: '{text[:50]}...'")
|
||||
|
||||
@@ -1738,7 +1704,7 @@ class WikipediaScraper:
|
||||
def _extract_infobox_value(self, soup, target):
|
||||
"""
|
||||
Extrahiert gezielt Branche, Umsatz oder Mitarbeiter aus der Infobox.
|
||||
Berücksichtigt jetzt auch Header in <td>-Tags (z.B. <td style="font-weight:bold">).
|
||||
Berücksichtigt Header in <th> oder fett formatierten <td>.
|
||||
"""
|
||||
self.logger.debug(f"--- Entering _extract_infobox_value for target '{target}' ---")
|
||||
|
||||
@@ -1761,28 +1727,23 @@ class WikipediaScraper:
|
||||
self.logger.debug(f" -> Analysiere {len(rows)} Zeilen in der Infobox.")
|
||||
for idx, row in enumerate(rows):
|
||||
self.logger.debug(f" --- Prüfe Roh-HTML Zeile {idx}: {str(row)[:150]}...")
|
||||
cells = row.find_all(['th', 'td'], recursive=False) # Finde ALLE th oder td Zellen direkt unter tr
|
||||
cells = row.find_all(['th', 'td'], recursive=False)
|
||||
|
||||
# --- NEUE LOGIK ZUR IDENTIFIZIERUNG VON HEADER UND WERT ---
|
||||
header_text = None
|
||||
value_cell = None
|
||||
|
||||
# Fall 1: Klassisch <th> + <td>
|
||||
# Strukturprüfung
|
||||
if len(cells) == 2 and cells[0].name == 'th' and cells[1].name == 'td':
|
||||
header_text = cells[0].get_text(strip=True)
|
||||
value_cell = cells[1]
|
||||
self.logger.debug(f" -> Zeile {idx}: Struktur TH + TD erkannt.")
|
||||
# Fall 2: Header in <td> (oft mit style="font-weight:bold;") + <td>
|
||||
elif len(cells) == 2 and cells[0].name == 'td' and cells[1].name == 'td':
|
||||
# Prüfe, ob die erste Zelle wie ein Header aussieht (fett oder starker Text)
|
||||
first_cell_is_header_like = False
|
||||
style = cells[0].get('style', '').lower()
|
||||
if 'font-weight' in style and ('bold' in style or '700' in style or '800' in style or '900' in style):
|
||||
first_cell_is_header_like = True
|
||||
# Fallback: Prüfe, ob der direkte Text fett ist (<b>, <strong>)
|
||||
elif cells[0].find(['b', 'strong'], recursive=False):
|
||||
first_cell_is_header_like = True
|
||||
|
||||
if first_cell_is_header_like:
|
||||
header_text = cells[0].get_text(strip=True)
|
||||
value_cell = cells[1]
|
||||
@@ -1792,9 +1753,7 @@ class WikipediaScraper:
|
||||
else:
|
||||
self.logger.debug(f" -> Zeile {idx}: Übersprungen (Struktur passt nicht, Zellen: {len(cells)}, Typen: {[c.name for c in cells]})")
|
||||
|
||||
# --- ENDE NEUE LOGIK ---
|
||||
|
||||
# Verarbeite nur, wenn Header und Wert gefunden wurden
|
||||
# Verarbeitung, wenn Struktur passt
|
||||
if header_text is not None and value_cell is not None:
|
||||
self.logger.debug(f" -> Verarbeite Zeile {idx} mit Header='{header_text}'")
|
||||
header_text_lower = header_text.lower()
|
||||
@@ -1808,35 +1767,31 @@ class WikipediaScraper:
|
||||
if matched_keyword:
|
||||
self.logger.debug(f" --> Keyword '{matched_keyword}' gefunden in Header '{header_text}'!")
|
||||
|
||||
# Störende Elemente entfernen
|
||||
for sup in value_cell.find_all(['sup', 'span']):
|
||||
if (sup.name == 'sup' and sup.has_attr('class') and 'reference' in sup['class']) or \
|
||||
(sup.name == 'span' and sup.get('style') and 'display:none' in sup['style']):
|
||||
self.logger.debug(f" -> Entferne störendes Element: {sup.get_text(strip=True)}")
|
||||
sup.decompose()
|
||||
|
||||
# Text extrahieren und bereinigen
|
||||
raw_value_text = value_cell.get_text(separator=' ', strip=True)
|
||||
self.logger.debug(f" -> Roher TD/Value-Text nach Decompose: '{raw_value_text}'")
|
||||
cleaned_raw_value = clean_text(raw_value_text)
|
||||
cleaned_raw_value = clean_text(raw_value_text) # Annahme: existiert
|
||||
|
||||
# Spezifische Verarbeitung
|
||||
if target == 'branche':
|
||||
clean_val = re.sub(r'\s*\([^)]*\)', '', cleaned_raw_value).strip()
|
||||
clean_val = clean_val.split('\n')[0].strip()
|
||||
value_found = clean_val if clean_val else "k.A."
|
||||
self.logger.info(f" --> Branche extrahiert: '{value_found}'") # INFO statt DEBUG für gefundene Werte
|
||||
self.logger.info(f" --> Branche extrahiert: '{value_found}'") # Logge Fund als INFO
|
||||
elif target == 'umsatz':
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=True)
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=True) # Annahme: existiert
|
||||
value_found = numeric_val
|
||||
self.logger.info(f" --> Umsatz extrahiert (aus '{cleaned_raw_value}'): '{value_found}'")
|
||||
elif target == 'mitarbeiter':
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=False)
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=False) # Annahme: existiert
|
||||
value_found = numeric_val
|
||||
self.logger.info(f" --> Mitarbeiter extrahiert (aus '{cleaned_raw_value}'): '{value_found}'")
|
||||
|
||||
# WICHTIG: Schleife für dieses Ziel beenden, da Wert gefunden wurde
|
||||
break
|
||||
break # Ersten Treffer nehmen
|
||||
|
||||
# Ende der Zeilenschleife
|
||||
if value_found != "k.A.":
|
||||
@@ -1852,7 +1807,7 @@ class WikipediaScraper:
|
||||
|
||||
def extract_company_data(self, page_url):
|
||||
"""
|
||||
Extrahiert Firmendaten von einer Wikipedia-URL (v1.6.5 Logik).
|
||||
Extrahiert Firmendaten von einer Wikipedia-URL.
|
||||
"""
|
||||
default_result = {'url': page_url if page_url else 'k.A.', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'}
|
||||
if not page_url or not isinstance(page_url, str) or "wikipedia.org" not in page_url.lower():
|
||||
@@ -1864,10 +1819,10 @@ class WikipediaScraper:
|
||||
|
||||
if not soup:
|
||||
self.logger.error(f" -> Fehler: Konnte Seite {page_url} nicht laden oder parsen.")
|
||||
default_result['url'] = page_url # Behalte URL im Ergebnis
|
||||
default_result['url'] = page_url
|
||||
return default_result
|
||||
|
||||
# --- Extrahiere Daten aus dem Soup-Objekt ---
|
||||
# Extrahiere Daten aus dem Soup-Objekt
|
||||
self.logger.debug(" -> Extrahiere ersten Absatz...")
|
||||
first_paragraph = self._extract_first_paragraph_from_soup(soup)
|
||||
self.logger.debug(" -> Extrahiere Kategorien...")
|
||||
@@ -1879,7 +1834,6 @@ class WikipediaScraper:
|
||||
self.logger.debug(" -> Extrahiere Mitarbeiter aus Infobox...")
|
||||
mitarbeiter_val = self._extract_infobox_value(soup, 'mitarbeiter')
|
||||
|
||||
# --- Ergebnis zusammenstellen ---
|
||||
result = {
|
||||
'url': page_url,
|
||||
'first_paragraph': first_paragraph,
|
||||
@@ -1888,14 +1842,14 @@ class WikipediaScraper:
|
||||
'mitarbeiter': mitarbeiter_val,
|
||||
'categories': categories_val
|
||||
}
|
||||
# Logge das Ergebnis (INFO Level für Übersicht)
|
||||
self.logger.info(f" -> Extrahierte Daten: P={first_paragraph[:30]}..., B='{branche_val}', U='{umsatz_val}', M='{mitarbeiter_val}', C={categories_val[:30]}...")
|
||||
return result
|
||||
|
||||
@retry_on_failure # Annahme: Decorator existiert
|
||||
def search_company_article(self, company_name, website=None):
|
||||
"""
|
||||
Sucht einen passenden Wikipedia-Artikel und gibt das page-Objekt zurück. (v1.6.5 Logik)
|
||||
Sucht einen passenden Wikipedia-Artikel und gibt das page-Objekt zurück.
|
||||
Behandelt jetzt explizit Begriffsklärungsseiten.
|
||||
"""
|
||||
if not company_name:
|
||||
self.logger.warning("Wikipedia search skipped: No company name provided.")
|
||||
@@ -1907,87 +1861,91 @@ class WikipediaScraper:
|
||||
return None
|
||||
|
||||
self.logger.info(f"Starte Wikipedia-Suche für '{company_name}' (Website: {website}) mit Begriffen: {search_terms}")
|
||||
processed_titles = set() # Verhindert doppelte Prüfung
|
||||
|
||||
# Versuche direkten Match zuerst
|
||||
try:
|
||||
self.logger.debug(f" -> Versuche direkten Match für '{company_name}'...")
|
||||
page = wikipedia.page(company_name, auto_suggest=False, preload=True)
|
||||
self.logger.debug(f" -> Direkten Match gefunden: '{page.title}'. Validiere...")
|
||||
if self._validate_article(page, company_name, website):
|
||||
# Erfolg bereits hier geloggt durch _validate_article
|
||||
return page
|
||||
else:
|
||||
self.logger.debug(f" -> Direkter Match '{page.title}' nicht validiert. Fahre mit Suche fort.")
|
||||
except wikipedia.exceptions.PageError:
|
||||
self.logger.debug(f" -> Kein direkter Artikel für '{company_name}' gefunden.")
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
self.logger.debug(f" -> '{company_name}' ist eine Begriffsklärungsseite. Optionen: {e.options[:3]}...")
|
||||
# Optional: Prüfe die erste Option
|
||||
if e.options:
|
||||
try:
|
||||
option_title = e.options[0]
|
||||
self.logger.debug(f" -> Prüfe erste Option der Begriffsklärung: '{option_title}'")
|
||||
page = wikipedia.page(option_title, auto_suggest=False, preload=True)
|
||||
if self._validate_article(page, company_name, website):
|
||||
return page # Erfolg wird von _validate_article geloggt
|
||||
else:
|
||||
self.logger.debug(f" -> Erste Option '{page.title}' nicht validiert.")
|
||||
except Exception as e_disamb:
|
||||
self.logger.warning(f" -> Fehler beim Laden/Validieren der Disambiguation-Option '{e.options[0]}': {e_disamb}")
|
||||
except Exception as e_direct:
|
||||
# Fehler beim direkten Zugriff loggen
|
||||
self.logger.error(f" -> Unerwarteter Fehler beim direkten Zugriff auf '{company_name}': {e_direct}")
|
||||
# Fehler weitergeben, da es ein Problem mit der Lib/Verbindung sein könnte
|
||||
raise e_direct
|
||||
# --- Interne Hilfsfunktion zum Prüfen einer Seite ---
|
||||
def check_page(title_to_check):
|
||||
if title_to_check in processed_titles:
|
||||
self.logger.debug(f" -> Titel '{title_to_check}' bereits geprüft, überspringe.")
|
||||
return None
|
||||
processed_titles.add(title_to_check)
|
||||
try:
|
||||
self.logger.debug(f" -> Prüfe potenziellen Artikel: '{title_to_check}'")
|
||||
page = wikipedia.page(title_to_check, auto_suggest=False, preload=True)
|
||||
if self._validate_article(page, company_name, website):
|
||||
return page # Erfolg wird von _validate_article geloggt
|
||||
else:
|
||||
self.logger.debug(f" -> Titel '{title_to_check}' nicht validiert.")
|
||||
return None
|
||||
except wikipedia.exceptions.PageError:
|
||||
self.logger.debug(f" -> Seite '{title_to_check}' nicht gefunden (PageError).")
|
||||
return None
|
||||
except wikipedia.exceptions.DisambiguationError as e_inner:
|
||||
self.logger.info(f" -> Begriffsklärung '{title_to_check}' gefunden. Prüfe Optionen...")
|
||||
self.logger.debug(f" Optionen: {e_inner.options}")
|
||||
best_option_page = None
|
||||
for option in e_inner.options:
|
||||
option_lower = option.lower()
|
||||
is_company_candidate = False
|
||||
if "(unternehmen)" in option_lower:
|
||||
is_company_candidate = True
|
||||
self.logger.debug(f" -> Option mit '(Unternehmen)' gefunden: '{option}'")
|
||||
elif any(form in option_lower for form in [' gmbh', ' ag', ' kg', ' ltd', ' inc', ' corp', ' s.a.', ' se']):
|
||||
is_company_candidate = True
|
||||
self.logger.debug(f" -> Option mit Firmen-Keyword gefunden: '{option}'")
|
||||
# --- Hinzugefügt: Prüfe Ähnlichkeit zum Firmennamen als Indikator ---
|
||||
elif SequenceMatcher(None, normalize_company_name(option), normalize_company_name(company_name)).ratio() > 0.7:
|
||||
is_company_candidate = True
|
||||
self.logger.debug(f" -> Option mit hoher Namensähnlichkeit gefunden: '{option}'")
|
||||
|
||||
# Wenn direkter Match fehlschlägt, nutze die generierten Suchbegriffe
|
||||
self.logger.debug(f" -> Starte Suche mit generierten Begriffen: {search_terms}")
|
||||
if is_company_candidate:
|
||||
validated_option_page = check_page(option) # Rekursiver Check
|
||||
if validated_option_page:
|
||||
self.logger.info(f" -> Option '{option}' erfolgreich validiert!")
|
||||
if best_option_page is None: # Nimm die erste validierte Unternehmensoption
|
||||
best_option_page = validated_option_page
|
||||
# Optional: Weitere Logik zur Auswahl der "besten" Option, falls mehrere passen
|
||||
# break # Oder direkt die erste passende nehmen
|
||||
if best_option_page:
|
||||
return best_option_page
|
||||
else:
|
||||
self.logger.warning(f" -> Keine passende/validierte Unternehmens-Option in Begriffsklärung '{title_to_check}' gefunden.")
|
||||
return None
|
||||
except requests.exceptions.RequestException as e_req:
|
||||
self.logger.warning(f" -> Netzwerkfehler beim Laden/Validieren von '{title_to_check}': {e_req}. Überspringe Titel.")
|
||||
time.sleep(1)
|
||||
return None
|
||||
except Exception as e_page:
|
||||
self.logger.error(f" -> Fehler bei Verarbeitung von Titel '{title_to_check}': {type(e_page).__name__} - {e_page}")
|
||||
return None # Fehler bei dieser Seite
|
||||
|
||||
# --- Haupt-Suchlogik ---
|
||||
self.logger.debug(f" -> Versuche direkten Match für '{company_name}'...")
|
||||
validated_page = check_page(company_name)
|
||||
if validated_page: return validated_page
|
||||
|
||||
self.logger.debug(f" -> Kein direkter Treffer/validiert. Starte Suche mit generierten Begriffen: {search_terms}")
|
||||
for term in search_terms:
|
||||
try:
|
||||
self.logger.debug(f" -> Suche mit Begriff: '{term}'...")
|
||||
# Begrenze Anzahl Suchergebnisse
|
||||
search_results = wikipedia.search(term, results=getattr(Config, 'WIKIPEDIA_SEARCH_RESULTS', 5))
|
||||
self.logger.debug(f" -> Suchergebnisse für '{term}': {search_results}")
|
||||
if not search_results: continue
|
||||
|
||||
for title in search_results:
|
||||
# Heuristische Filterung
|
||||
if any(ignore in title.lower() for ignore in ['liste von', 'begriffsklärung', '(Begriffsklärung)']):
|
||||
self.logger.debug(f" -> Überspringe wahrscheinlichen Nicht-Artikel/BK: '{title}'")
|
||||
continue
|
||||
try:
|
||||
self.logger.debug(f" -> Prüfe potenziellen Artikel: '{title}'")
|
||||
page = wikipedia.page(title, auto_suggest=False, preload=True)
|
||||
if self._validate_article(page, company_name, website):
|
||||
# Erfolg wird von _validate_article geloggt
|
||||
return page
|
||||
time.sleep(0.1) # Kleines Delay
|
||||
|
||||
except wikipedia.exceptions.PageError:
|
||||
self.logger.debug(f" -> Seite '{title}' nicht gefunden (PageError).")
|
||||
continue
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
self.logger.debug(f" -> Seite '{title}' ist Begriffsklärung: {e.options[:3]}...")
|
||||
continue
|
||||
except requests.exceptions.RequestException as e_req:
|
||||
self.logger.warning(f" -> Netzwerkfehler beim Laden/Validieren von '{title}': {e_req}. Überspringe Titel.")
|
||||
time.sleep(1)
|
||||
continue
|
||||
except Exception as e_page:
|
||||
self.logger.error(f" -> Fehler bei Verarbeitung von Titel '{title}': {type(e_page).__name__} - {e_page}")
|
||||
continue # Zum nächsten Titel
|
||||
validated_page = check_page(title)
|
||||
if validated_page: return validated_page
|
||||
time.sleep(0.1)
|
||||
|
||||
except requests.exceptions.RequestException as e_search_req:
|
||||
self.logger.error(f"Netzwerkfehler während Wikipedia-Suche für '{term}': {e_search_req}")
|
||||
time.sleep(2) # Längere Pause bei Suchfehler
|
||||
# Fehler weitergeben für Retry
|
||||
time.sleep(2)
|
||||
raise e_search_req
|
||||
except Exception as e_search:
|
||||
self.logger.error(f"Allgemeiner Fehler während Wikipedia-Suche für '{term}': {e_search}")
|
||||
# Fehler weitergeben für Retry? Eher nicht, nächsten Begriff versuchen.
|
||||
continue
|
||||
|
||||
self.logger.warning(f"Kein passender & validierter Wikipedia-Artikel für '{company_name}' gefunden nach Prüfung aller Begriffe.")
|
||||
self.logger.warning(f"Kein passender & validierter Wikipedia-Artikel für '{company_name}' gefunden nach Prüfung aller Begriffe und Optionen.")
|
||||
return None
|
||||
|
||||
|
||||
@@ -3624,171 +3582,195 @@ class DataProcessor:
|
||||
self.wiki_scraper = WikipediaScraper() # Eigene Instanz des Scrapers
|
||||
|
||||
# @retry_on_failure # Vorsicht mit Retry auf dieser Ebene für die ganze Zeile
|
||||
# @retry_on_failure # Retry auf der gesamten Zeile ist riskant
|
||||
def _process_single_row(self, row_num_in_sheet, row_data, process_wiki=True, process_chatgpt=True, process_website=True):
|
||||
"""
|
||||
Verarbeitet die Daten für eine einzelne Zeile, prüft Timestamps für jeden Teilbereich
|
||||
und stellt sicher, dass aktuelle Wiki-Daten für Branch-Eval verwendet werden.
|
||||
Verarbeitet die Daten für eine einzelne Zeile.
|
||||
Priorisiert jetzt die Wiki-Artikelsuche/-Validierung VOR der Extraktion.
|
||||
Prüft Timestamps für jeden Teilbereich.
|
||||
"""
|
||||
debug_print(f"--- Starte Verarbeitung für Zeile {row_num_in_sheet} ---")
|
||||
logging.info(f"--- Starte Verarbeitung für Zeile {row_num_in_sheet} ---")
|
||||
updates = []
|
||||
now_timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
any_processing_done = False
|
||||
wiki_data_updated_in_this_run = False # Flag, ob Wiki-Daten (M-R) neu geschrieben wurden
|
||||
|
||||
# Hilfsfunktion
|
||||
# Hilfsfunktion für sicheren Zellenzugriff
|
||||
def get_cell_value(key):
|
||||
idx = COLUMN_MAP.get(key)
|
||||
if idx is not None and len(row_data) > idx: return row_data[idx]
|
||||
return ""
|
||||
|
||||
# Lese initiale Werte
|
||||
# Lese initiale Werte für spätere Verwendung
|
||||
company_name = get_cell_value("CRM Name")
|
||||
website_url = get_cell_value("CRM Website"); original_website = website_url
|
||||
crm_branche = get_cell_value("CRM Branche"); crm_beschreibung = get_cell_value("CRM Beschreibung")
|
||||
crm_wiki_url = get_cell_value("CRM Vorschlag Wiki URL")
|
||||
konsistenz_s = get_cell_value("Chat Wiki Konsistenzprüfung")
|
||||
website_raw = get_cell_value("Website Rohtext") or "k.A."
|
||||
website_summary = get_cell_value("Website Zusammenfassung") or "k.A."
|
||||
# Initialisiere wiki_data mit Werten aus dem Sheet (Fallback)
|
||||
wiki_data = {
|
||||
'url': get_cell_value("Wiki URL") or 'k.A.', 'first_paragraph': get_cell_value("Wiki Absatz") or 'k.A.',
|
||||
'branche': get_cell_value("Wiki Branche") or 'k.A.', 'umsatz': get_cell_value("Wiki Umsatz") or 'k.A.',
|
||||
'mitarbeiter': get_cell_value("Wiki Mitarbeiter") or 'k.A.', 'categories': get_cell_value("Wiki Kategorien") or 'k.A.'
|
||||
|
||||
# Initialisiere finale Wiki-Daten (werden evtl. überschrieben)
|
||||
final_wiki_data = {
|
||||
'url': get_cell_value("Wiki URL") or 'k.A.',
|
||||
'first_paragraph': get_cell_value("Wiki Absatz") or 'k.A.',
|
||||
'branche': get_cell_value("Wiki Branche") or 'k.A.',
|
||||
'umsatz': get_cell_value("Wiki Umsatz") or 'k.A.',
|
||||
'mitarbeiter': get_cell_value("Wiki Mitarbeiter") or 'k.A.',
|
||||
'categories': get_cell_value("Wiki Kategorien") or 'k.A.'
|
||||
}
|
||||
wiki_data_updated_in_this_run = False # Flag, ob Wiki neu geparst wurde
|
||||
final_page_object = None # Das validierte Page-Objekt
|
||||
|
||||
# --- 1. Website Handling (prüft AT) ---
|
||||
website_ts_needed = process_website and not get_cell_value("Website Scrape Timestamp").strip()
|
||||
if website_ts_needed:
|
||||
any_processing_done = True; debug_print(f"Zeile {row_num_in_sheet}: Starte Website Verarbeitung...")
|
||||
# --- Lookup & Scraping ---
|
||||
any_processing_done = True
|
||||
logging.info(f"Zeile {row_num_in_sheet}: Starte Website Verarbeitung (Lookup, Scrape, Summarize)...")
|
||||
if not website_url or website_url.strip().lower() == "k.a.":
|
||||
new_website = serp_website_lookup(company_name)
|
||||
if new_website != "k.A.": website_url = new_website;
|
||||
if website_url != original_website: updates.append({'range': f'D{row_num_in_sheet}', 'values': [[website_url]]})
|
||||
logging.debug(" -> Suche Website via SERP...")
|
||||
new_website = serp_website_lookup(company_name) # Annahme: nutzt logging
|
||||
if new_website != "k.A.":
|
||||
website_url = new_website
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["CRM Website"] + 1)}{row_num_in_sheet}', 'values': [[website_url]]})
|
||||
if website_url and website_url.strip().lower() != "k.a.":
|
||||
new_website_raw = get_website_raw(website_url); new_website_summary = summarize_website_content(new_website_raw)
|
||||
if new_website_raw != website_raw: updates.append({'range': f'AR{row_num_in_sheet}', 'values': [[new_website_raw]]}); website_raw = new_website_raw
|
||||
if new_website_summary != website_summary: updates.append({'range': f'AS{row_num_in_sheet}', 'values': [[new_website_summary]]}); website_summary = new_website_summary
|
||||
logging.debug(f" -> Scrape Rohtext von {website_url}...")
|
||||
new_website_raw = get_website_raw(website_url) # Annahme: nutzt logging
|
||||
logging.debug(f" -> Fasse Rohtext zusammen (Länge: {len(new_website_raw)})...")
|
||||
new_website_summary = summarize_website_content(new_website_raw) # Annahme: nutzt logging
|
||||
# Aktualisiere globale Variablen für spätere Schritte (ChatGPT)
|
||||
website_raw = new_website_raw
|
||||
website_summary = new_website_summary
|
||||
# Füge Updates für AR und AS hinzu
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [[website_raw]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [[website_summary]]})
|
||||
else:
|
||||
if website_raw != "k.A.": updates.append({'range': f'AR{row_num_in_sheet}', 'values': [['k.A.']]})
|
||||
if website_summary != "k.A.": updates.append({'range': f'AS{row_num_in_sheet}', 'values': [['k.A.']]})
|
||||
logging.warning(f" -> Keine gültige Website gefunden/vorhanden für {company_name}.")
|
||||
website_raw, website_summary = "k.A.", "k.A."
|
||||
updates.append({'range': f'AT{row_num_in_sheet}', 'values': [[now_timestamp]]})
|
||||
elif process_website: debug_print(f"Zeile {row_num_in_sheet}: Überspringe Website (AT vorhanden).")
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Rohtext"] + 1)}{row_num_in_sheet}', 'values': [['k.A.']]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Zusammenfassung"] + 1)}{row_num_in_sheet}', 'values': [['k.A.']]})
|
||||
# Setze AT Timestamp
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Website Scrape Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]})
|
||||
elif process_website:
|
||||
logging.debug(f"Zeile {row_num_in_sheet}: Überspringe Website (AT vorhanden).")
|
||||
|
||||
# --- 2. Wikipedia Handling (prüft AN oder S='X (URL Copied)') ---
|
||||
|
||||
# --- 2. Wikipedia Artikel Findung/Validierung (prüft AN oder S='X(Copied)') ---
|
||||
# Diese Logik bestimmt, OB und WELCHE Seite extrahiert werden soll.
|
||||
wiki_ts_an_missing = not get_cell_value("Wikipedia Timestamp").strip()
|
||||
status_s_indicates_reparse = konsistenz_s.strip().upper() == "X (URL COPIED)"
|
||||
reparse_wiki_needed = process_wiki and (wiki_ts_an_missing or status_s_indicates_reparse)
|
||||
wiki_processing_needed = process_wiki and (wiki_ts_an_missing or status_s_indicates_reparse)
|
||||
url_to_potentially_parse = get_cell_value("Wiki URL").strip() # Die URL, die aktuell in Spalte M steht
|
||||
|
||||
if reparse_wiki_needed:
|
||||
any_processing_done = True; debug_print(f"Zeile {row_num_in_sheet}: Starte Wikipedia Verarbeitung (AN fehlt? {wiki_ts_an_missing}, S='X(Copied)'? {status_s_indicates_reparse})...")
|
||||
new_wiki_data_extracted = None
|
||||
if wiki_processing_needed:
|
||||
any_processing_done = True
|
||||
logging.info(f"Zeile {row_num_in_sheet}: Starte Wikipedia Artikel Findung/Validierung (AN fehlt? {wiki_ts_an_missing}, S='X(Copied)'? {status_s_indicates_reparse})...")
|
||||
# --- NEUE LOGIK: Suche/Validierung zuerst ---
|
||||
validated_page = None
|
||||
# Prüfe zuerst, ob die URL in M direkt valide ist
|
||||
if url_to_potentially_parse and url_to_potentially_parse.lower() not in ["k.a.", "kein artikel gefunden"] and url_to_potentially_parse.lower().startswith("http"):
|
||||
logging.debug(f" -> Prüfe Validität der vorhandenen URL aus Spalte M: {url_to_potentially_parse}")
|
||||
try:
|
||||
# Wir brauchen das Page-Objekt für _validate_article
|
||||
page_from_m = wikipedia.page(url_to_potentially_parse.split('/wiki/')[-1].replace('_', ' '), auto_suggest=False, preload=True)
|
||||
if self.wiki_scraper._validate_article(page_from_m, company_name, website_url):
|
||||
validated_page = page_from_m
|
||||
logging.info(f" -> Vorhandene URL aus M '{validated_page.url}' ist valide.")
|
||||
else:
|
||||
logging.debug(f" -> Vorhandene URL aus M '{page_from_m.title}' ist NICHT valide.")
|
||||
except wikipedia.exceptions.PageError:
|
||||
logging.warning(f" -> Seite für vorhandene URL aus M '{url_to_potentially_parse}' nicht gefunden (PageError).")
|
||||
except wikipedia.exceptions.DisambiguationError as e_disamb_m:
|
||||
logging.info(f" -> Vorhandene URL aus M '{url_to_potentially_parse}' ist eine Begriffsklärung. Starte Suche...")
|
||||
# Wenn M eine BKL ist, explizit neu suchen
|
||||
validated_page = self.wiki_scraper.search_company_article(company_name, website_url)
|
||||
except Exception as e_val_m:
|
||||
logging.error(f" -> Fehler beim Prüfen der URL aus M '{url_to_potentially_parse}': {e_val_m}")
|
||||
|
||||
# --- Priorisiere URL aus Spalte M ---
|
||||
url_to_parse = get_cell_value("Wiki URL").strip() # Holt die URL, die ggf. von update_wiki kopiert wurde
|
||||
if url_to_parse and url_to_parse.lower() not in ["k.a.", "kein artikel gefunden"] and url_to_parse.lower().startswith("http"):
|
||||
debug_print(f" -> Nutze vorhandene URL aus Spalte M: {url_to_parse}")
|
||||
new_wiki_data_extracted = self.wiki_scraper.extract_company_data(url_to_parse)
|
||||
# Wenn URL aus M nicht valide war oder keine vorhanden war, starte die Suche
|
||||
if not validated_page:
|
||||
logging.info(f" -> Keine valide URL in M gefunden oder Prüfung fehlgeschlagen. Starte Wikipedia-Suche für '{company_name}'...")
|
||||
validated_page = self.wiki_scraper.search_company_article(company_name, website_url) # Nutzt die verbesserte Suche inkl. Disambiguation
|
||||
|
||||
# --- Datenextraktion NACH erfolgreicher Findung/Validierung ---
|
||||
if validated_page:
|
||||
logging.info(f" -> Valider Artikel gefunden/bestätigt: {validated_page.url}. Extrahiere Daten...")
|
||||
final_page_object = validated_page # Speichere für spätere Verwendung
|
||||
extracted_data = self.wiki_scraper.extract_company_data(validated_page.url) # Extrahiere Daten von der KORREKTEN Seite
|
||||
final_wiki_data = extracted_data # Überschreibe die initialen Daten
|
||||
wiki_data_updated_in_this_run = True # Setze Flag, da M-R neu geschrieben wird
|
||||
logging.info(f" -> Datenextraktion für '{validated_page.title}' abgeschlossen.")
|
||||
else:
|
||||
debug_print(f" -> Spalte M ('{url_to_parse}') ungültig/leer. Starte Wiki-Suche...")
|
||||
valid_crm_wiki_url = crm_wiki_url if crm_wiki_url and crm_wiki_url.strip() not in ["", "k.A."] else None
|
||||
article_page = None # Initialisiere article_page
|
||||
current_website_for_validation = website_url if website_url and website_url != 'k.A.' else original_website
|
||||
logging.warning(f" -> Konnte keinen validen Wikipedia Artikel für '{company_name}' finden/bestätigen.")
|
||||
# Setze Wiki-Daten auf "Nicht gefunden" / "k.A."
|
||||
final_wiki_data = {'url': 'Kein Artikel gefunden', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'}
|
||||
wiki_data_updated_in_this_run = True # Auch hier Flag setzen, da wir M-R überschreiben
|
||||
|
||||
# --- KORREKTE EINRÜCKUNG HIER ---
|
||||
if valid_crm_wiki_url:
|
||||
debug_print(f" -> Prüfe CRM Vorschlag L: {valid_crm_wiki_url}")
|
||||
page = self.wiki_scraper._fetch_page_content(valid_crm_wiki_url.split('/')[-1])
|
||||
if page and self.wiki_scraper._validate_article(page, company_name, current_website_for_validation):
|
||||
article_page = page
|
||||
else:
|
||||
debug_print(f" -> CRM Vorschlag L nicht validiert. Starte Suche...")
|
||||
# Wenn CRM-Vorschlag nicht validiert, Suche trotzdem starten
|
||||
article_page = self.wiki_scraper.search_company_article(company_name, current_website_for_validation)
|
||||
else:
|
||||
# --- DIESE ZEILE IST JETZT KORREKT EINGERÜCKT UNTER DEM ELSE ---
|
||||
debug_print(f" -> Kein CRM Vorschlag L. Starte Suche...")
|
||||
article_page = self.wiki_scraper.search_company_article(company_name, current_website_for_validation)
|
||||
# --- ENDE KORREKTE EINRÜCKUNG ---
|
||||
# Füge Updates für M-R und AN hinzu
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki URL"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('url', 'k.A.')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Absatz"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('first_paragraph', 'k.A.')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Branche"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('branche', 'k.A.')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Umsatz"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('umsatz', 'k.A.')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Mitarbeiter"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('mitarbeiter', 'k.A.')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wiki Kategorien"] + 1)}{row_num_in_sheet}', 'values': [[final_wiki_data.get('categories', 'k.A.')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Wikipedia Timestamp"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]}) # Setze AN Timestamp
|
||||
|
||||
if article_page:
|
||||
debug_print(f" -> Artikel gefunden durch Suche: {article_page.url}")
|
||||
new_wiki_data_extracted = self.wiki_scraper.extract_company_data(article_page.url)
|
||||
else:
|
||||
debug_print(f" -> Kein passender Wikipedia Artikel durch Suche gefunden.")
|
||||
new_wiki_data_extracted = {'url': 'Kein Artikel gefunden', 'first_paragraph': 'k.A.', 'branche': 'k.A.', 'umsatz': 'k.A.', 'mitarbeiter': 'k.A.', 'categories': 'k.A.'}
|
||||
|
||||
# --- WICHTIG: Überschreibe wiki_data mit den NEUEN Ergebnissen ---
|
||||
if new_wiki_data_extracted:
|
||||
wiki_data = new_wiki_data_extracted # <-- Hier werden die Daten für den Branch-Teil aktualisiert!
|
||||
wiki_data_updated_in_this_run = True # Setze Flag
|
||||
# Füge Updates für M-R und AN hinzu
|
||||
updates.append({'range': f'M{row_num_in_sheet}', 'values': [[wiki_data.get('url', 'k.A.')]]})
|
||||
updates.append({'range': f'N{row_num_in_sheet}', 'values': [[wiki_data.get('first_paragraph', 'k.A.')]]})
|
||||
# ... (Updates für O, P, Q, R) ...
|
||||
updates.append({'range': f'O{row_num_in_sheet}', 'values': [[wiki_data.get('branche', 'k.A.')]]})
|
||||
updates.append({'range': f'P{row_num_in_sheet}', 'values': [[wiki_data.get('umsatz', 'k.A.')]]})
|
||||
updates.append({'range': f'Q{row_num_in_sheet}', 'values': [[wiki_data.get('mitarbeiter', 'k.A.')]]})
|
||||
updates.append({'range': f'R{row_num_in_sheet}', 'values': [[wiki_data.get('categories', 'k.A.')]]})
|
||||
updates.append({'range': f'AN{row_num_in_sheet}', 'values': [[now_timestamp]]}) # Setze AN neu
|
||||
|
||||
# Wenn der Trigger "X (URL Copied)" war, setze S zurück
|
||||
if status_s_indicates_reparse:
|
||||
s_idx = COLUMN_MAP.get("Chat Wiki Konsistenzprüfung")
|
||||
if s_idx is not None:
|
||||
s_let = self.sheet_handler._get_col_letter(s_idx + 1)
|
||||
updates.append({'range': f'{s_let}{row_num_in_sheet}', 'values': [["?"]]})
|
||||
debug_print(f" -> Status S zurückgesetzt auf '?' für erneute Verifikation.")
|
||||
else:
|
||||
debug_print(f" -> FEHLER: Keine neuen Wiki-Daten extrahiert.")
|
||||
# wiki_data behält die alten/default Werte
|
||||
# Setze S zurück, wenn Trigger 'X(Copied)' war oder wenn URL sich geändert hat
|
||||
if status_s_indicates_reparse or (url_to_potentially_parse != final_wiki_data.get('url')):
|
||||
s_idx = COLUMN_MAP.get("Chat Wiki Konsistenzprüfung")
|
||||
if s_idx is not None:
|
||||
s_let = self.sheet_handler._get_col_letter(s_idx + 1)
|
||||
updates.append({'range': f'{s_let}{row_num_in_sheet}', 'values': [["?"]]})
|
||||
logging.info(f" -> Status S zurückgesetzt auf '?' für erneute Verifikation.")
|
||||
|
||||
elif process_wiki:
|
||||
debug_print(f"Zeile {row_num_in_sheet}: Überspringe Wikipedia Verarbeitung (AN vorhanden UND S != 'X (URL Copied)').")
|
||||
logging.debug(f"Zeile {row_num_in_sheet}: Überspringe Wikipedia Verarbeitung (AN vorhanden UND S != 'X (URL Copied)').")
|
||||
# WICHTIG: Obwohl wir nicht neu parsen, müssen wir die final_wiki_data
|
||||
# mit den bereits im Sheet stehenden Werten für den nächsten Schritt befüllen.
|
||||
# Das ist oben bei der Initialisierung von final_wiki_data bereits geschehen.
|
||||
|
||||
# --- 3. ChatGPT Evaluationen (Branch etc.) ---
|
||||
# Trigger: AO fehlt ODER Wiki wurde in DIESEM Lauf neu geparsed
|
||||
chat_ts_ao_missing = not get_cell_value("Timestamp letzte Prüfung").strip()
|
||||
run_chat_eval = process_chatgpt and (chat_ts_ao_missing or wiki_data_updated_in_this_run) # <-- Nutze das neue Flag
|
||||
# Trigger: AO fehlt ODER Wiki-Daten wurden in DIESEM Lauf neu geschrieben (M-R, AN)
|
||||
run_chat_eval = process_chatgpt and (chat_ts_ao_missing or wiki_data_updated_in_this_run)
|
||||
|
||||
if run_chat_eval:
|
||||
debug_print(f"Zeile {row_num_in_sheet}: Starte ChatGPT Evaluationen (Grund: AO fehlt? {chat_ts_ao_missing}, Wiki gerade aktualisiert? {wiki_data_updated_in_this_run})...")
|
||||
logging.info(f"Zeile {row_num_in_sheet}: Starte ChatGPT Evaluationen (Grund: AO fehlt? {chat_ts_ao_missing}, Wiki gerade aktualisiert? {wiki_data_updated_in_this_run})...")
|
||||
any_processing_done = True
|
||||
|
||||
# 3.1 Branchenevaluierung (Nutzt IMMER die aktuelle 'wiki_data' Variable)
|
||||
# Nutze IMMER die 'final_wiki_data' für die Evaluation
|
||||
branch_result = evaluate_branche_chatgpt(
|
||||
crm_branche, crm_beschreibung,
|
||||
wiki_data.get('branche', 'k.A.'), # Nimmt die potenziell neuen Daten
|
||||
wiki_data.get('categories', 'k.A.'),# Nimmt die potenziell neuen Daten
|
||||
website_summary
|
||||
)
|
||||
updates.append({'range': f'W{row_num_in_sheet}', 'values': [[branch_result.get('branch', 'Fehler')]]})
|
||||
updates.append({'range': f'X{row_num_in_sheet}', 'values': [[branch_result.get('consistency', 'Fehler')]]})
|
||||
updates.append({'range': f'Y{row_num_in_sheet}', 'values': [[branch_result.get('justification', 'Fehler')]]})
|
||||
final_wiki_data.get('branche', 'k.A.'),
|
||||
final_wiki_data.get('categories', 'k.A.'),
|
||||
website_summary # Kommt aus Schritt 1 oder initialen Werten
|
||||
) # Annahme: nutzt logging
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Vorschlag Branche"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get('branch', 'Fehler')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Konsistenz Branche"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get('consistency', 'Fehler')]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Chat Begründung Abweichung Branche"] + 1)}{row_num_in_sheet}', 'values': [[branch_result.get('justification', 'Fehler')]]})
|
||||
|
||||
# ... (Hier weitere ChatGPT Evaluationen) ...
|
||||
# ... (Hier weitere ChatGPT Evaluationen, immer mit final_wiki_data und aktuellen website_*) ...
|
||||
|
||||
# Setze Timestamp letzte Prüfung (AO)
|
||||
updates.append({'range': f'AO{row_num_in_sheet}', 'values': [[now_timestamp]]})
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Timestamp letzte Prüfung"] + 1)}{row_num_in_sheet}', 'values': [[now_timestamp]]})
|
||||
|
||||
elif process_chatgpt:
|
||||
debug_print(f"Zeile {row_num_in_sheet}: Überspringe ChatGPT Evaluationen (AO vorhanden UND Wiki nicht gerade aktualisiert).")
|
||||
logging.debug(f"Zeile {row_num_in_sheet}: Überspringe ChatGPT Evaluationen (AO vorhanden UND Wiki nicht gerade aktualisiert).")
|
||||
|
||||
# --- 4. Abschließende Updates ---
|
||||
if any_processing_done:
|
||||
updates.append({'range': f'AP{row_num_in_sheet}', 'values': [[Config.VERSION]]})
|
||||
# Setze Version nur, wenn *irgendetwas* in dieser Zeile gemacht wurde
|
||||
updates.append({'range': f'{self.sheet_handler._get_col_letter(COLUMN_MAP["Version"] + 1)}{row_num_in_sheet}', 'values': [[Config.VERSION]]})
|
||||
|
||||
# --- 5. Batch Update ---
|
||||
# --- 5. Batch Update für diese Zeile ---
|
||||
if updates:
|
||||
logging.info(f"Zeile {row_num_in_sheet}: Sende Batch-Update mit {len(updates)} Operationen...")
|
||||
success = self.sheet_handler.batch_update_cells(updates)
|
||||
if success: debug_print(f"Zeile {row_num_in_sheet}: Batch-Update erfolgreich ({len(updates)} Zellen/Bereiche).")
|
||||
else: debug_print(f"Zeile {row_num_in_sheet}: FEHLER beim Batch-Update.")
|
||||
if not success: logging.error(f"Zeile {row_num_in_sheet}: FEHLER beim Batch-Update.")
|
||||
else:
|
||||
debug_print(f"Zeile {row_num_in_sheet}: Keine Updates zum Schreiben.")
|
||||
logging.info(f"Zeile {row_num_in_sheet}: Keine Updates zum Schreiben.")
|
||||
|
||||
debug_print(f"--- Verarbeitung für Zeile {row_num_in_sheet} abgeschlossen ---")
|
||||
time.sleep(max(0.1, Config.RETRY_DELAY / 25)) # Noch kürzere Pause hier
|
||||
logging.info(f"--- Verarbeitung für Zeile {row_num_in_sheet} abgeschlossen ---")
|
||||
# Kurze Pause zwischen den Zeilen im sequenziellen Modus
|
||||
time.sleep(max(0.1, getattr(Config, 'RETRY_DELAY', 5) / 20)) # Kleine Pause
|
||||
|
||||
|
||||
def process_rows_sequentially(self, start_row_index, num_rows_to_process, process_wiki=True, process_chatgpt=True, process_website=True):
|
||||
|
||||
Reference in New Issue
Block a user