bugfix
This commit is contained in:
@@ -1505,13 +1505,24 @@ class GoogleSheetHandler:
|
||||
class WikipediaScraper:
|
||||
"""
|
||||
Handles searching Wikipedia articles and extracting relevant company data.
|
||||
Version: 1.6.5 - Improved infobox parsing reliability.
|
||||
Version: 1.6.5 logic - Improved infobox parsing reliability and detailed logging.
|
||||
"""
|
||||
def __init__(self, user_agent=None): # Optional User-Agent übergeben
|
||||
# Verwende User-Agent aus Config, falls nicht anders angegeben
|
||||
self.user_agent = user_agent or getattr(Config, 'USER_AGENT', 'Mozilla/5.0 (compatible; YourBotName/1.0; +http://yourwebsite.com/botinfo)')
|
||||
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 = {
|
||||
@@ -1520,348 +1531,351 @@ class WikipediaScraper:
|
||||
'mitarbeiter': ['mitarbeiter', 'mitarbeiterzahl', 'beschäftigte', 'employees', 'number of employees', 'personal', 'belegschaft']
|
||||
}
|
||||
try:
|
||||
wikipedia.set_lang(Config.LANG)
|
||||
wiki_lang = getattr(Config, 'LANG', 'de') # Sprache aus Config holen
|
||||
wikipedia.set_lang(wiki_lang)
|
||||
wikipedia.set_rate_limiting(True) # Respektiere Wikipedia API Limits
|
||||
debug_print(f"Wikipedia library language set to '{Config.LANG}'.")
|
||||
self.logger.info(f"Wikipedia library language set to '{wiki_lang}'. Rate limiting enabled.")
|
||||
except Exception as e:
|
||||
debug_print(f"WARNUNG: Fehler beim Setzen der Wikipedia-Sprache oder Rate Limiting: {e}")
|
||||
self.logger.warning(f"Fehler beim Setzen der Wikipedia-Sprache oder Rate Limiting: {e}")
|
||||
|
||||
# Beibehalten: Hilfsfunktion für Domain
|
||||
def _get_full_domain(self, website):
|
||||
"""Extrahiert die normalisierte Domain (ohne www, ohne Pfad) aus einer URL."""
|
||||
if not website or not isinstance(website, str): return ""
|
||||
website = website.lower().strip()
|
||||
website = re.sub(r'^https?:\/\/', '', website)
|
||||
website = re.sub(r'^www\.', '', website)
|
||||
return website.split('/')[0]
|
||||
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
|
||||
|
||||
# Beibehalten: Suchbegriffe generieren
|
||||
def _generate_search_terms(self, company_name, website):
|
||||
terms = set(); full_domain = self._get_full_domain(website)
|
||||
if full_domain: terms.add(full_domain)
|
||||
normalized_name = normalize_company_name(company_name) # Annahme: normalize_company_name existiert
|
||||
"""Generiert eine Liste von Suchbegriffen für die Wikipedia-Suche."""
|
||||
if not company_name: return []
|
||||
terms = set()
|
||||
full_domain = self._get_full_domain(website)
|
||||
if full_domain: terms.add(full_domain) # Domain als Suchbegriff
|
||||
|
||||
# Normalisierten Namen hinzufügen (Annahme: Funktion existiert)
|
||||
normalized_name = normalize_company_name(company_name)
|
||||
if normalized_name:
|
||||
name_parts = normalized_name.split()
|
||||
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)
|
||||
# Füge Originalnamen hinzu, falls er signifikant anders ist
|
||||
if company_name and company_name.lower() != normalized_name and company_name.lower() not in terms:
|
||||
terms.add(company_name.lower())
|
||||
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
|
||||
|
||||
# Nimm nur die ersten paar sinnvollen Begriffe
|
||||
# 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
|
||||
debug_print(f"Generierte Suchbegriffe für '{company_name}': {final_terms}")
|
||||
self.logger.debug(f"Generierte Suchbegriffe für '{company_name}': {final_terms}")
|
||||
return final_terms
|
||||
|
||||
# retry_on_failure Decorator hier hinzufügen!
|
||||
@retry_on_failure
|
||||
@retry_on_failure # Annahme: Decorator existiert und behandelt Exceptions
|
||||
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"):
|
||||
logger.warning(f"_get_page_soup: Ungültige URL '{url}'.")
|
||||
self.logger.warning(f"_get_page_soup: Ungültige URL '{url}'.")
|
||||
return None
|
||||
try:
|
||||
logger.debug(f"_get_page_soup: Rufe URL ab: {url}")
|
||||
response = self.session.get(url, timeout=15) # Timeout erhöhen
|
||||
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, da Wikipedia dies verwendet
|
||||
# Encoding explizit auf UTF-8 setzen (Standard für Wikipedia)
|
||||
response.encoding = 'utf-8'
|
||||
soup = BeautifulSoup(response.text, Config.HTML_PARSER) # Nutze Parser aus Config
|
||||
logger.debug(f"_get_page_soup: Parsen von {url} erfolgreich.")
|
||||
soup = BeautifulSoup(response.text, getattr(Config, 'HTML_PARSER', 'html.parser')) # Nutze Parser aus Config
|
||||
self.logger.debug(f"_get_page_soup: Parsen von {url} erfolgreich.")
|
||||
return soup
|
||||
except requests.exceptions.Timeout:
|
||||
logger.error(f"_get_page_soup: Timeout beim Abrufen von {url}")
|
||||
return None
|
||||
self.logger.error(f"_get_page_soup: Timeout beim Abrufen von {url}")
|
||||
raise # Fehler weitergeben für Retry
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"_get_page_soup: Fehler beim Abrufen von HTML von {url}: {e}")
|
||||
# Hier keinen Fehler weitergeben, damit der Prozess weiterläuft, aber None zurückgeben
|
||||
return None
|
||||
self.logger.error(f"_get_page_soup: Netzwerkfehler beim Abrufen von HTML von {url}: {e}")
|
||||
raise e # Fehler weitergeben für Retry
|
||||
except Exception as e:
|
||||
logger.error(f"_get_page_soup: Fehler beim Parsen von HTML von {url}: {e}")
|
||||
return None
|
||||
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
|
||||
|
||||
# Überarbeitete Validierung
|
||||
def _validate_article(self, page, company_name, website):
|
||||
"""
|
||||
Validiert, ob ein Wikipedia-Artikel zum Unternehmen passt.
|
||||
Prüft Titelähnlichkeit und ob die Firmenwebsite verlinkt ist.
|
||||
Nutzt jetzt _get_page_soup für die Link-Prüfung.
|
||||
"""
|
||||
if not page or not company_name: return False
|
||||
debug_print(f"Validiere Artikel '{page.title}' für Firma '{company_name}' (Website: {website})...")
|
||||
self.logger.debug(f"Validiere Artikel '{page.title}' für Firma '{company_name}' (Website: {website})...")
|
||||
|
||||
full_domain = self._get_full_domain(website)
|
||||
normalized_company = normalize_company_name(company_name) # Annahme: normalize_company_name existiert
|
||||
normalized_company = normalize_company_name(company_name)
|
||||
normalized_title = normalize_company_name(page.title)
|
||||
|
||||
# 1. Titelähnlichkeit prüfen
|
||||
# 1. Titelähnlichkeit
|
||||
similarity = SequenceMatcher(None, normalized_title, normalized_company).ratio()
|
||||
debug_print(f" -> Titelähnlichkeit: {similarity:.2f} ('{normalized_title}' vs '{normalized_company}')")
|
||||
self.logger.debug(f" -> Titelähnlichkeit: {similarity:.2f} ('{normalized_title}' vs '{normalized_company}')")
|
||||
|
||||
# 2. Link-Prüfung (nur wenn Domain vorhanden)
|
||||
domain_found = False
|
||||
if full_domain:
|
||||
debug_print(f" -> Suche nach Domain '{full_domain}' in Links...")
|
||||
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
|
||||
if soup:
|
||||
# Suche zuerst in der Infobox (häufigster Ort für offizielle Links)
|
||||
infobox = soup.select_one('table[class*="infobox"]') # Flexibler Selektor
|
||||
# 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', '')
|
||||
# Prüfe, ob der Link auf eine externe Seite geht und die Domain enthält
|
||||
if href.startswith('http') and full_domain in href.lower():
|
||||
# Zusätzliche Prüfung: Ist der Link-Text relevant? (Optional)
|
||||
if href.startswith('http') and full_domain in self._get_full_domain(href): # Vergleiche Domains
|
||||
link_text = link.get_text(strip=True).lower()
|
||||
if any(kw in link_text for kw in ['website', 'webseite', 'offizielle']):
|
||||
debug_print(f" -> Domain '{full_domain}' in Infobox-Link gefunden (Text: '{link_text}', URL: {href})")
|
||||
# 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 \
|
||||
any(kw in th_text for kw in ['website', 'webseite', 'webauftritt']):
|
||||
self.logger.debug(f" -> Domain '{full_domain}' in Infobox-Link gefunden (TH: '{th_text}', Text: '{link_text}', URL: {href})")
|
||||
domain_found = True
|
||||
break
|
||||
else: # Auch akzeptieren, wenn der Text nicht passt, aber die URL klar ist
|
||||
debug_print(f" -> Domain '{full_domain}' in Infobox-Link gefunden (URL: {href})")
|
||||
else: # Akzeptiere auch ohne Keyword, wenn URL eindeutig scheint
|
||||
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 des Artikels
|
||||
# Wenn nicht in Infobox, suche in allen externen Links
|
||||
if not domain_found:
|
||||
# Die wikipedia library liefert externe Links manchmal nicht zuverlässig, daher besser alle Links parsen
|
||||
all_links = soup.find_all('a', href=True)
|
||||
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)
|
||||
|
||||
for link in all_links:
|
||||
href = link.get('href', '')
|
||||
# Klasse 'external text' ist ein guter Indikator, aber nicht immer vorhanden
|
||||
is_external = link.has_attr('class') and any(c in ['external', 'text'] for c in link['class'])
|
||||
# Prüfe, ob es ein externer Link ist und die Domain enthält
|
||||
if href.startswith('http') and full_domain in href.lower():
|
||||
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']):
|
||||
debug_print(f" -> Domain '{full_domain}' in externem Link gefunden (URL: {href}, External Class? {is_external})")
|
||||
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
|
||||
else:
|
||||
debug_print(f" -> Konnte HTML für Link-Prüfung von {page.url} nicht laden.")
|
||||
self.logger.warning(f" -> Konnte HTML für Link-Prüfung von {page.url} nicht laden.")
|
||||
else:
|
||||
debug_print(" -> Keine Website-Domain für Link-Prüfung vorhanden.")
|
||||
self.logger.debug(" -> Keine Website-Domain für Link-Prüfung vorhanden.")
|
||||
|
||||
# 3. Entscheidung basierend auf Ähnlichkeit und Link
|
||||
threshold = Config.SIMILARITY_THRESHOLD # Standard-Schwelle
|
||||
# 3. Entscheidung
|
||||
threshold = getattr(Config, 'SIMILARITY_THRESHOLD', 0.65) # Standard-Schwelle aus Config
|
||||
if domain_found:
|
||||
threshold = max(0.4, threshold - 0.2) # Schwelle lockern, wenn Domain passt (aber nicht zu sehr)
|
||||
debug_print(f" -> Domain gefunden, Schwelle angepasst auf {threshold:.2f}")
|
||||
# Schwelle signifikant lockern, wenn Domain passt
|
||||
threshold = max(0.35, threshold - 0.3) # Beispiel: 0.65 -> 0.35
|
||||
self.logger.debug(f" -> Domain gefunden, Ähnlichkeitsschwelle angepasst auf {threshold:.2f}")
|
||||
|
||||
is_valid = similarity >= threshold
|
||||
if is_valid:
|
||||
debug_print(f" => Artikel '{page.title}' VALIDiert (Ähnlichkeit >= {threshold:.2f}, Domain gefunden? {domain_found})")
|
||||
else:
|
||||
debug_print(f" => Artikel '{page.title}' NICHT validiert (Ähnlichkeit < {threshold:.2f}, Domain gefunden? {domain_found})")
|
||||
log_level = logging.INFO if is_valid else logging.DEBUG # Logge Erfolg als INFO
|
||||
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
|
||||
|
||||
# Beibehalten: Extrahiere Kategorien
|
||||
def extract_categories(self, soup):
|
||||
"""Extrahiert Wikipedia-Kategorien aus dem Soup-Objekt."""
|
||||
if not soup: return "k.A."
|
||||
cats_filtered = []
|
||||
try:
|
||||
cat_div = soup.find('div', id="mw-normal-catlinks")
|
||||
if cat_div:
|
||||
ul = cat_div.find('ul')
|
||||
if ul:
|
||||
# Extrahiere Text, bereinige ihn und filtere den "Kategorien:" Link selbst
|
||||
cats = [clean_text(li.get_text()) for li in ul.find_all('li')]
|
||||
cats_filtered = [c for c in cats if c and "kategorien:" not in c.lower()]
|
||||
return ", ".join(cats_filtered) if cats_filtered else "k.A."
|
||||
self.logger.debug(f"Kategorien gefunden: {cats_filtered}")
|
||||
else: self.logger.debug("Kein 'ul' in 'mw-normal-catlinks' gefunden.")
|
||||
else: self.logger.debug("Kein 'div#mw-normal-catlinks' gefunden.")
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Extrahieren der Kategorien: {e}")
|
||||
return "k.A."
|
||||
self.logger.error(f"Fehler beim Extrahieren der Kategorien: {e}")
|
||||
|
||||
return ", ".join(cats_filtered) if cats_filtered else "k.A."
|
||||
|
||||
# Beibehalten: Extrahiere ersten Absatz
|
||||
def _extract_first_paragraph_from_soup(self, soup):
|
||||
"""Extrahiert den ersten aussagekräftigen Absatz aus dem Soup-Objekt."""
|
||||
if not soup: return "k.A."
|
||||
paragraph_text = "k.A."
|
||||
try:
|
||||
# Finde das Haupt-Inhaltsdiv (üblich bei MediaWiki)
|
||||
# Suche bevorzugt im Hauptinhalt
|
||||
content_div = soup.find('div', class_='mw-parser-output')
|
||||
if not content_div:
|
||||
content_div = soup.find('div', id='bodyContent') # Fallback
|
||||
if not content_div:
|
||||
content_div = soup # Fallback auf ganzen Soup
|
||||
search_area = content_div if content_div else soup # Fallback auf ganzen Soup
|
||||
|
||||
# Suche nach dem ersten <p>-Tag, der direkten Text enthält (keine leeren <p>)
|
||||
paragraphs = content_div.find_all('p', recursive=False) # Nur direkte Kinder suchen? Oft besser
|
||||
if not paragraphs:
|
||||
paragraphs = content_div.find_all('p', recursive=True) # Fallback: alle <p>
|
||||
# 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)
|
||||
self.logger.debug(f"Suche ersten Absatz in {len(paragraphs)} gefundenen <p>-Tags...")
|
||||
|
||||
for p in paragraphs:
|
||||
# Ignoriere <p>-Tags, die nur ein Bild oder eine Tabelle enthalten (heuristisch)
|
||||
if p.find(['img', 'table', 'figure'], recursive=False): continue
|
||||
# Entferne Referenz-Links [1], [2] etc. VOR der Textextraktion
|
||||
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()
|
||||
# Extrahiere und bereinige Text
|
||||
text = p.get_text(separator=' ', strip=True)
|
||||
text = clean_text(text) # Annahme: clean_text entfernt u.a. doppelte Leerzeichen
|
||||
# Nimm den ersten Absatz mit signifikanter Länge
|
||||
if text and len(text) > 30: # Mindestlänge, um leere oder irrelevante <p> zu überspringen
|
||||
logger.debug(f"Ersten Absatz gefunden: {text[:100]}...")
|
||||
return text[:1000] # Begrenze Länge
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Extrahieren des ersten Absatzes: {e}")
|
||||
return "k.A."
|
||||
# Entferne Koordinaten (oft am Anfang in <span>)
|
||||
for span in p.find_all('span', id='coordinates'):
|
||||
span.decompose()
|
||||
|
||||
text = clean_text(p.get_text(separator=' ', strip=True))
|
||||
|
||||
# Prüfe Mindestlänge
|
||||
if text and len(text) > 40: # Mindestlänge etwas erhöht
|
||||
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
|
||||
else:
|
||||
self.logger.debug(f" -> Überspringe <p> Tag (Index {idx}), Text zu kurz oder leer nach clean_text: '{text[:50]}...'")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fehler beim Extrahieren des ersten Absatzes: {e}")
|
||||
|
||||
if paragraph_text == "k.A.":
|
||||
self.logger.warning("Kein passender erster Absatz gefunden.")
|
||||
return paragraph_text
|
||||
|
||||
# --- NEUE, VERBESSERTE Infobox-Extraktion ---
|
||||
def _extract_infobox_value(self, soup, target):
|
||||
"""
|
||||
Extrahiert gezielt Branche, Umsatz oder Mitarbeiter aus der Infobox.
|
||||
Robuster gegen Variationen in HTML und Keyword-Bezeichnungen.
|
||||
|
||||
Args:
|
||||
soup (BeautifulSoup): Das geparste HTML der Seite.
|
||||
target (str): Welcher Wert gesucht wird ('branche', 'umsatz', 'mitarbeiter').
|
||||
|
||||
Returns:
|
||||
str: Der gefundene und bereinigte Wert oder "k.A.".
|
||||
Extrahiert gezielt Branche, Umsatz oder Mitarbeiter aus der Infobox. (v1.6.5 Logik)
|
||||
"""
|
||||
# --- DEBUG LOG: Funktion betreten ---
|
||||
self.logger.debug(f"--- Entering _extract_infobox_value for target '{target}' ---")
|
||||
|
||||
if not soup or target not in self.keywords_map:
|
||||
logger.debug(f"_extract_infobox_value: Ungültiger Input (Soup: {soup is not None}, Target: {target})")
|
||||
self.logger.debug(f"_extract_infobox_value: Ungültiger Input (Soup: {soup is not None}, Target: {target})")
|
||||
return "k.A."
|
||||
|
||||
keywords = self.keywords_map[target]
|
||||
logger.debug(f"_extract_infobox_value: Suche nach '{target}' mit Keywords: {keywords}")
|
||||
self.logger.debug(f"_extract_infobox_value: Suche nach '{target}' mit Keywords: {keywords}")
|
||||
|
||||
# Flexiblere Suche nach der Infobox
|
||||
infobox = soup.select_one('table[class*="infobox"]')
|
||||
if not infobox:
|
||||
logger.debug(" -> Keine Infobox mit Klasse '*infobox*' gefunden.")
|
||||
# Fallback: Suche nach Tabellen mit typischen Attributen, falls Klasse fehlt
|
||||
infobox = soup.find('table', {'summary': re.compile(r'Infobox', re.I)})
|
||||
if not infobox:
|
||||
logger.debug(" -> Keine Infobox über summary='Infobox...' gefunden.")
|
||||
return "k.A."
|
||||
else:
|
||||
logger.debug(" -> Infobox über summary='Infobox...' gefunden.")
|
||||
# --- DEBUG LOG: Infobox gefunden? ---
|
||||
if infobox:
|
||||
self.logger.debug(f" -> Infobox gefunden (via select_one 'table[class*=\"infobox\"]')")
|
||||
else:
|
||||
logger.debug(f" -> Infobox gefunden (Selektor 'table[class*=\"infobox\"]'). Erste Zeilen: {str(infobox.find('tr'))[:100]}...")
|
||||
self.logger.debug(" -> KEINE Infobox via select_one 'table[class*=\"infobox\"]' gefunden.")
|
||||
# Optional: Fallback-Suche (hier ausgelassen für Klarheit)
|
||||
return "k.A." # Frühzeitiger Ausstieg
|
||||
|
||||
|
||||
value_found = "k.A." # Standardwert
|
||||
|
||||
# Iteriere durch alle Zeilen (tr) der Infobox
|
||||
value_found = "k.A."
|
||||
try:
|
||||
rows = infobox.find_all('tr')
|
||||
logger.debug(f" -> Analysiere {len(rows)} Zeilen in der Infobox.")
|
||||
self.logger.debug(f" -> Analysiere {len(rows)} Zeilen in der Infobox.")
|
||||
for idx, row in enumerate(rows):
|
||||
header = row.find('th') # Finde die Header-Zelle
|
||||
value_cell = row.find('td') # Finde die Daten-Zelle
|
||||
header_cells = row.find_all('th', recursive=False) # Nur direkte th Kinder
|
||||
value_cells = row.find_all('td', recursive=False) # Nur direkte td Kinder
|
||||
|
||||
# Wir erwarten meistens ein th und ein td pro Zeile für Key-Value Paare
|
||||
if len(header_cells) == 1 and len(value_cells) == 1:
|
||||
header = header_cells[0]
|
||||
value_cell = value_cells[0]
|
||||
|
||||
if header and value_cell:
|
||||
# Normalisiere den Header-Text für den Keyword-Vergleich
|
||||
header_text = header.get_text(strip=True)
|
||||
header_text_lower = header_text.lower()
|
||||
# Entferne Doppelpunkte etc. für besseren Match
|
||||
# Normalisierte Version für flexibleren Match
|
||||
header_text_normalized = re.sub(r'[\s:]', '', header_text_lower)
|
||||
|
||||
# Detailliertes Log für jede geprüfte Zeile (Header+Value)
|
||||
# Zeige den Wert nur gekürzt an, um Logs nicht zu fluten
|
||||
raw_value_preview = value_cell.get_text(strip=True)[:50]
|
||||
logger.debug(f" -> Prüfe Zeile {idx}: TH='{header_text}' (Norm: '{header_text_normalized}') | TD Preview='{raw_value_preview}...'")
|
||||
self.logger.debug(f" -> Prüfe Zeile {idx}: TH='{header_text}' (Norm: '{header_text_normalized}') | TD Preview='{raw_value_preview}...'")
|
||||
|
||||
# Prüfe, ob *irgendein* Keyword im normalisierten Header enthalten ist
|
||||
matched_keyword = None
|
||||
for kw in keywords:
|
||||
# Suche nach dem Keyword als ganzes Wort oder am Anfang/Ende?
|
||||
# Einfacher: `in`-Operator prüft auf Substring
|
||||
if kw in header_text_lower: # Prüfe im weniger aggressiv normalisierten Text
|
||||
# Prüfe ob Keyword im (nicht-normalisierten) Lowercase-Header vorkommt
|
||||
if kw in header_text_lower:
|
||||
matched_keyword = kw
|
||||
break
|
||||
|
||||
if matched_keyword:
|
||||
logger.debug(f" --> Keyword '{matched_keyword}' gefunden in TH '{header_text}'!")
|
||||
self.logger.debug(f" --> Keyword '{matched_keyword}' gefunden in TH '{header_text}'!")
|
||||
|
||||
# --- Wert extrahieren und bereinigen ---
|
||||
# 1. Störende Elemente entfernen (Referenzen etc.)
|
||||
for sup in value_cell.find_all(['sup', 'span']): # Entferne <sup> und ggf. überflüssige <span>
|
||||
# Spezifischer: Nur Referenzen entfernen?
|
||||
if sup.has_attr('class') and 'reference' in sup['class']:
|
||||
logger.debug(f" -> Entferne Referenz: {sup.get_text(strip=True)}")
|
||||
sup.decompose()
|
||||
# Entferne unsichtbare Elemente (manchmal in <span>)
|
||||
elif sup.name == 'span' and sup.get('style') and 'display:none' in sup['style']:
|
||||
logger.debug(f" -> Entferne unsichtbares Span: {sup.get_text(strip=True)}")
|
||||
sup.decompose()
|
||||
# Störende Elemente (Referenzen, unsichtbare Spans) im Value 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()
|
||||
|
||||
|
||||
# 2. Text extrahieren (mit Leerzeichen für <br>)
|
||||
# Text extrahieren (mit Leerzeichen für <br>) und bereinigen
|
||||
raw_value_text = value_cell.get_text(separator=' ', strip=True)
|
||||
logger.debug(f" -> Roher TD-Text nach Decompose: '{raw_value_text}'")
|
||||
self.logger.debug(f" -> Roher TD-Text nach Decompose: '{raw_value_text}'")
|
||||
cleaned_raw_value = clean_text(raw_value_text)
|
||||
|
||||
# 3. Generelle Bereinigung (clean_text)
|
||||
cleaned_raw_value = clean_text(raw_value_text) # Annahme: clean_text ist definiert
|
||||
|
||||
# 4. Spezifische Verarbeitung je nach Zieltyp
|
||||
# Spezifische Verarbeitung
|
||||
if target == 'branche':
|
||||
# Entferne oft vorkommende Zusätze wie (Stand: ...), [1], etc.
|
||||
clean_val = re.sub(r'\s*\[\d+\]', '', cleaned_raw_value) # Referenzen (falls clean_text sie nicht erwischt)
|
||||
clean_val = re.sub(r'\s*\([^)]*\)', '', clean_val).strip() # Klammerzusätze
|
||||
value_found = clean_val if clean_val else "k.A."
|
||||
logger.debug(f" --> Branche extrahiert: '{value_found}'")
|
||||
|
||||
# Einfache Bereinigung für Branche
|
||||
clean_val = re.sub(r'\s*\([^)]*\)', '', cleaned_raw_value).strip() # Klammerzusätze entfernen
|
||||
# Nimm nur den Teil vor einem möglichen Zeilenumbruch (manchmal gibt es Listen)
|
||||
clean_val = clean_val.split('\n')[0].strip()
|
||||
value_found = clean_val if clean_val else "k.A."
|
||||
self.logger.debug(f" --> Branche extrahiert: '{value_found}'")
|
||||
elif target == 'umsatz':
|
||||
# extract_numeric_value sollte robust sein
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=True) # Annahme: existiert
|
||||
value_found = numeric_val
|
||||
logger.debug(f" --> Umsatz extrahiert (aus '{cleaned_raw_value}'): '{value_found}'")
|
||||
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=True)
|
||||
value_found = numeric_val
|
||||
self.logger.debug(f" --> Umsatz extrahiert (aus '{cleaned_raw_value}'): '{value_found}'")
|
||||
elif target == 'mitarbeiter':
|
||||
# extract_numeric_value sollte robust sein
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=False) # Annahme: existiert
|
||||
value_found = numeric_val
|
||||
logger.debug(f" --> Mitarbeiter extrahiert (aus '{cleaned_raw_value}'): '{value_found}'")
|
||||
numeric_val = extract_numeric_value(cleaned_raw_value, is_umsatz=False)
|
||||
value_found = numeric_val
|
||||
self.logger.debug(f" --> Mitarbeiter extrahiert (aus '{cleaned_raw_value}'): '{value_found}'")
|
||||
|
||||
# Wenn ein Wert für das gesuchte Ziel gefunden wurde, beende die Schleife für dieses Ziel
|
||||
break # WICHTIG: Verhindert Überschreiben durch spätere Zeilen
|
||||
|
||||
# else: # Optional: Loggen, wenn kein Keyword passte
|
||||
# logger.debug(f" --- Kein passendes Keyword in TH '{header_text}' gefunden.")
|
||||
break # WICHTIG: Ersten Treffer für das gesuchte Ziel nehmen und Zeilenschleife verlassen
|
||||
else:
|
||||
# Logge Zeilen, die nicht dem Key-Value-Muster entsprechen (optional auf DEBUG)
|
||||
# self.logger.debug(f" -> Überspringe Zeile {idx}: Struktur passt nicht (TH:{len(header_cells)}, TD:{len(value_cells)})")
|
||||
pass
|
||||
|
||||
# Ende der Zeilenschleife
|
||||
if value_found != "k.A.":
|
||||
logger.debug(f" -> Finaler Wert für '{target}' gefunden: '{value_found}'")
|
||||
self.logger.debug(f" -> Finaler Wert für '{target}' gefunden: '{value_found}'")
|
||||
else:
|
||||
logger.debug(f" -> Kein passender Eintrag für '{target}' in der gesamten Infobox gefunden.")
|
||||
self.logger.debug(f" -> Kein passender Eintrag für '{target}' in der gesamten Infobox gefunden.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Durchlaufen der Infobox-Zeilen für '{target}': {e}")
|
||||
# Gib "k.A." zurück, wenn Fehler beim Parsen auftreten
|
||||
return "k.A."
|
||||
self.logger.exception(f"Fehler beim Durchlaufen der Infobox-Zeilen für '{target}': {e}")
|
||||
return "k.A." # Fehler beim Parsen
|
||||
|
||||
return value_found
|
||||
|
||||
# --- Kombinierte Datenextraktion (ruft Helfer auf) ---
|
||||
def extract_company_data(self, page_url):
|
||||
"""
|
||||
Extrahiert Firmendaten von einer Wikipedia-URL. Holt die Seite nur einmal
|
||||
und verwendet die verbesserte Infobox-Extraktion.
|
||||
Extrahiert Firmendaten von einer Wikipedia-URL (v1.6.5 Logik).
|
||||
"""
|
||||
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:
|
||||
logger.warning(f"extract_company_data: Ungültige URL '{page_url}'.")
|
||||
if not page_url or not isinstance(page_url, str) or "wikipedia.org" not in page_url.lower():
|
||||
self.logger.warning(f"extract_company_data: Ungültige URL '{page_url}'.")
|
||||
return default_result
|
||||
|
||||
logger.info(f"Extrahiere Daten für Wiki-URL: {page_url}")
|
||||
soup = self._get_page_soup(page_url) # Hole und parse die Seite EINMAL
|
||||
self.logger.info(f"Extrahiere Daten für Wiki-URL: {page_url}")
|
||||
soup = self._get_page_soup(page_url)
|
||||
|
||||
if not soup:
|
||||
logger.error(f" -> Fehler: Konnte Seite {page_url} nicht laden oder parsen.")
|
||||
# Gib Default zurück, aber behalte die URL
|
||||
default_result['url'] = page_url
|
||||
self.logger.error(f" -> Fehler: Konnte Seite {page_url} nicht laden oder parsen.")
|
||||
default_result['url'] = page_url # Behalte URL im Ergebnis
|
||||
return default_result
|
||||
|
||||
# --- Extrahiere Daten aus dem Soup-Objekt ---
|
||||
logger.debug(" -> Extrahiere ersten Absatz...")
|
||||
self.logger.debug(" -> Extrahiere ersten Absatz...")
|
||||
first_paragraph = self._extract_first_paragraph_from_soup(soup)
|
||||
logger.debug(" -> Extrahiere Kategorien...")
|
||||
self.logger.debug(" -> Extrahiere Kategorien...")
|
||||
categories_val = self.extract_categories(soup)
|
||||
logger.debug(" -> Extrahiere Branche aus Infobox...")
|
||||
self.logger.debug(" -> Extrahiere Branche aus Infobox...")
|
||||
branche_val = self._extract_infobox_value(soup, 'branche')
|
||||
logger.debug(" -> Extrahiere Umsatz aus Infobox...")
|
||||
self.logger.debug(" -> Extrahiere Umsatz aus Infobox...")
|
||||
umsatz_val = self._extract_infobox_value(soup, 'umsatz')
|
||||
logger.debug(" -> Extrahiere Mitarbeiter aus Infobox...")
|
||||
self.logger.debug(" -> Extrahiere Mitarbeiter aus Infobox...")
|
||||
mitarbeiter_val = self._extract_infobox_value(soup, 'mitarbeiter')
|
||||
|
||||
# --- Ergebnis zusammenstellen ---
|
||||
@@ -1873,113 +1887,106 @@ class WikipediaScraper:
|
||||
'mitarbeiter': mitarbeiter_val,
|
||||
'categories': categories_val
|
||||
}
|
||||
logger.info(f" -> Extrahierte Daten: P={first_paragraph[:30]}..., B='{branche_val}', U='{umsatz_val}', M='{mitarbeiter_val}', C={categories_val[:30]}...")
|
||||
# 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
|
||||
|
||||
# --- Artikelsuche (ruft Helfer auf) ---
|
||||
# retry_on_failure hier sinnvoll, da es API-Calls (wikipedia.search/page) macht
|
||||
@retry_on_failure
|
||||
@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.
|
||||
Nutzt generierte Suchbegriffe und Validierung.
|
||||
Sucht einen passenden Wikipedia-Artikel und gibt das page-Objekt zurück. (v1.6.5 Logik)
|
||||
"""
|
||||
if not company_name:
|
||||
logger.warning("Wikipedia search skipped: No company name provided.")
|
||||
self.logger.warning("Wikipedia search skipped: No company name provided.")
|
||||
return None
|
||||
|
||||
search_terms = self._generate_search_terms(company_name, website)
|
||||
if not search_terms:
|
||||
logger.warning(f"Keine Suchbegriffe für '{company_name}' generiert.")
|
||||
self.logger.warning(f"Keine Suchbegriffe für '{company_name}' generiert.")
|
||||
return None
|
||||
|
||||
logger.info(f"Starte Wikipedia-Suche für '{company_name}' (Website: {website}) mit Begriffen: {search_terms}")
|
||||
self.logger.info(f"Starte Wikipedia-Suche für '{company_name}' (Website: {website}) mit Begriffen: {search_terms}")
|
||||
|
||||
# Versuche direkten Match zuerst (oft der beste Treffer)
|
||||
# Versuche direkten Match zuerst
|
||||
try:
|
||||
logger.debug(f" -> Versuche direkten Match für '{company_name}'...")
|
||||
self.logger.debug(f" -> Versuche direkten Match für '{company_name}'...")
|
||||
page = wikipedia.page(company_name, auto_suggest=False, preload=True)
|
||||
logger.debug(f" -> Direkten Match gefunden: '{page.title}'. Validiere...")
|
||||
self.logger.debug(f" -> Direkten Match gefunden: '{page.title}'. Validiere...")
|
||||
if self._validate_article(page, company_name, website):
|
||||
logger.info(f" -> Direkter Match '{page.title}' ({page.url}) erfolgreich validiert.")
|
||||
# Erfolg bereits hier geloggt durch _validate_article
|
||||
return page
|
||||
else:
|
||||
logger.debug(f" -> Direkter Match '{page.title}' nicht validiert. Fahre mit Suche fort.")
|
||||
self.logger.debug(f" -> Direkter Match '{page.title}' nicht validiert. Fahre mit Suche fort.")
|
||||
except wikipedia.exceptions.PageError:
|
||||
logger.debug(f" -> Kein direkter Artikel für '{company_name}' gefunden.")
|
||||
self.logger.debug(f" -> Kein direkter Artikel für '{company_name}' gefunden.")
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
logger.debug(f" -> '{company_name}' ist eine Begriffsklärungsseite. Optionen: {e.options[:3]}...")
|
||||
# Optional: Prüfe die erste Option der Begriffsklärung?
|
||||
self.logger.debug(f" -> '{company_name}' ist eine Begriffsklärungsseite. Optionen: {e.options[:3]}...")
|
||||
# Optional: Prüfe die erste Option
|
||||
if e.options:
|
||||
try:
|
||||
first_option_title = e.options[0]
|
||||
logger.debug(f" -> Prüfe erste Option der Begriffsklärung: '{first_option_title}'")
|
||||
page = wikipedia.page(first_option_title, auto_suggest=False, preload=True)
|
||||
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):
|
||||
logger.info(f" -> Erste Option '{page.title}' ({page.url}) erfolgreich validiert.")
|
||||
return page
|
||||
return page # Erfolg wird von _validate_article geloggt
|
||||
else:
|
||||
logger.debug(f" -> Erste Option '{page.title}' nicht validiert.")
|
||||
self.logger.debug(f" -> Erste Option '{page.title}' nicht validiert.")
|
||||
except Exception as e_disamb:
|
||||
logger.warning(f" -> Fehler beim Laden/Validieren der ersten Disambiguation-Option '{e.options[0]}': {e_disamb}")
|
||||
self.logger.warning(f" -> Fehler beim Laden/Validieren der Disambiguation-Option '{e.options[0]}': {e_disamb}")
|
||||
except Exception as e_direct:
|
||||
logger.error(f" -> Unerwarteter Fehler beim direkten Zugriff auf '{company_name}': {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
|
||||
|
||||
# Wenn direkter Match fehlschlägt oder nicht validiert, nutze die generierten Suchbegriffe
|
||||
logger.debug(f" -> Starte Suche mit generierten Begriffen: {search_terms}")
|
||||
# Wenn direkter Match fehlschlägt, nutze die generierten Suchbegriffe
|
||||
self.logger.debug(f" -> Starte Suche mit generierten Begriffen: {search_terms}")
|
||||
for term in search_terms:
|
||||
try:
|
||||
logger.debug(f" -> Suche mit Begriff: '{term}'...")
|
||||
results = wikipedia.search(term, results=Config.WIKIPEDIA_SEARCH_RESULTS)
|
||||
logger.debug(f" -> Suchergebnisse für '{term}': {results}")
|
||||
if not results: continue # Nächster Begriff, wenn keine Ergebnisse
|
||||
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 results:
|
||||
# Überspringe Titel, die wahrscheinlich keine Firmen sind (heuristisch)
|
||||
if any(ignore_word in title.lower() for ignore_word in ['liste von', 'disambiguation', 'begriffsklärung']):
|
||||
logger.debug(f" -> Überspringe wahrscheinlichen Nicht-Artikel: '{title}'")
|
||||
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:
|
||||
logger.debug(f" -> Prüfe potenziellen Artikel: '{title}'")
|
||||
# Lade Page-Objekt (preload=True für Effizienz bei Validierung)
|
||||
self.logger.debug(f" -> Prüfe potenziellen Artikel: '{title}'")
|
||||
page = wikipedia.page(title, auto_suggest=False, preload=True)
|
||||
# Validierung (nutzt jetzt intern _get_page_soup)
|
||||
if self._validate_article(page, company_name, website):
|
||||
logger.info(f" -> Valider Artikel gefunden: '{page.title}' ({page.url})")
|
||||
return page # Gib das validierte Page-Objekt zurück
|
||||
# else: Artikel gefunden, aber nicht validiert -> nächsten Titel prüfen
|
||||
# Minimales Delay, um nicht zu schnell zu sein
|
||||
time.sleep(0.1)
|
||||
# Erfolg wird von _validate_article geloggt
|
||||
return page
|
||||
time.sleep(0.1) # Kleines Delay
|
||||
|
||||
except wikipedia.exceptions.PageError:
|
||||
logger.debug(f" -> Seite '{title}' nicht gefunden (PageError).")
|
||||
self.logger.debug(f" -> Seite '{title}' nicht gefunden (PageError).")
|
||||
continue
|
||||
except wikipedia.exceptions.DisambiguationError as e:
|
||||
logger.debug(f" -> Seite '{title}' ist Begriffsklärung: {e.options[:3]}...")
|
||||
# Optional: Versuche ersten Link der Begriffsklärung?
|
||||
self.logger.debug(f" -> Seite '{title}' ist Begriffsklärung: {e.options[:3]}...")
|
||||
continue
|
||||
except requests.exceptions.RequestException as e_req:
|
||||
logger.warning(f" -> Netzwerkfehler beim Laden/Validieren von '{title}': {e_req}. Überspringe Titel.")
|
||||
# Kurze Pause bei Netzwerkfehler
|
||||
self.logger.warning(f" -> Netzwerkfehler beim Laden/Validieren von '{title}': {e_req}. Überspringe Titel.")
|
||||
time.sleep(1)
|
||||
continue
|
||||
except Exception as e_page:
|
||||
# Andere Fehler beim Laden/Validieren einer einzelnen Seite
|
||||
logger.error(f" -> Fehler bei Verarbeitung von Titel '{title}': {type(e_page).__name__} - {e_page}")
|
||||
self.logger.error(f" -> Fehler bei Verarbeitung von Titel '{title}': {type(e_page).__name__} - {e_page}")
|
||||
continue # Zum nächsten Titel
|
||||
|
||||
except requests.exceptions.RequestException as e_search_req:
|
||||
logger.error(f"Netzwerkfehler während Wikipedia-Suche für '{term}': {e_search_req}")
|
||||
# Bei Netzwerkfehler während der Suche ggf. kurz warten und nächsten Begriff versuchen
|
||||
time.sleep(2)
|
||||
continue
|
||||
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
|
||||
raise e_search_req
|
||||
except Exception as e_search:
|
||||
# Fehler bei der Suche selbst
|
||||
logger.error(f"Allgemeiner Fehler während Wikipedia-Suche für '{term}': {e_search}")
|
||||
# Hier nicht abbrechen, sondern nächsten Suchbegriff versuchen
|
||||
continue # Zum nächsten Suchbegriff
|
||||
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
|
||||
|
||||
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.")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user