From 3f30c63adc5c84e3c748e21a780e58a040a6234c Mon Sep 17 00:00:00 2001 From: Floke Date: Mon, 31 Mar 2025 19:18:07 +0000 Subject: [PATCH] domain priorisierung MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vollständige Domain-Extraktion: Implementiert über die neue Methode _get_full_domain, die nun den kompletten Domainnamen (inklusive TLD) liefert (z. B. "heimbach.com"). Normalisierung der Firmennamen: Einführung der Funktion normalize_company_name, welche gängige Firmierungsformen (z. B. GmbH, AG, Aktiengesellschaft, Co. KG, mbH, & Co. KG, e.V., Limited, Ltd, Inc, Corp, Corporation, Gruppe) entfernt. Dies führt zu einem konsistenten Vergleich zwischen den Unternehmensdaten und Wikipedia-Titeln. Verbesserte Artikelvalidierung: In _validate_article werden nun: Infobox-Links sowie externe Links geprüft, ob sie den vollständigen Domainnamen enthalten (ohne Dateilinks). Der Vergleich der Wikipedia-Titel und des Firmennamens erfolgt auf Basis der normalisierten Namen. Ein dynamischer Schwellenwert wird verwendet (0.60 statt 0.65), wenn ein definitiver Link-Match gefunden wurde. --- brancheneinstufung.py | 95 +++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 35 deletions(-) diff --git a/brancheneinstufung.py b/brancheneinstufung.py index e539b013..3a0a10de 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -12,7 +12,7 @@ import csv # ==================== KONFIGURATION ==================== class Config: - VERSION = "1.0.14" + VERSION = "1.1.3" LANG = "de" CREDENTIALS_FILE = "service_account.json" SHEET_URL = "https://docs.google.com/spreadsheets/d/1u_gHr9JUfmV1-iviRzbSe3575QEp7KLhK5jFV_gJcgo" @@ -51,6 +51,17 @@ def clean_text(text): text = re.sub(r'\s+', ' ', text).strip() return text if text else "k.A." +def normalize_company_name(name): + """Entfernt gängige Firmierungsformen und normalisiert den Namen.""" + if not name: + return "" + # Liste gängiger Firmierungsformen, inklusive "Gruppe" + pattern = r'\b(gmbh|ag|aktiengesellschaft|co\.?\s*kg|mbh|&\s*co\.?\s*kg|e\.v\.|limited|ltd|inc|corp|corporation|gruppe)\b' + normalized = re.sub(pattern, '', name, flags=re.IGNORECASE) + normalized = re.sub(r'[\-–]', ' ', normalized) # Ersetze Bindestriche durch Leerzeichen + normalized = re.sub(r'\s+', ' ', normalized).strip() + return normalized.lower() + # ==================== GOOGLE SHEET HANDLER ==================== class GoogleSheetHandler: """Handhabung der Google Sheets Interaktion""" @@ -83,72 +94,86 @@ class WikipediaScraper: def __init__(self): wikipedia.set_lang(Config.LANG) - def _get_domain_key(self, website): - """Extrahiert den Domain-Key aus der URL (ohne Protokoll und www)""" + def _get_full_domain(self, website): + """Extrahiert den vollständigen Domainnamen (inklusive Topleveldomain) aus der URL.""" if not website: return "" website = website.lower().strip() website = re.sub(r'^https?:\/\/', '', website) website = re.sub(r'^www\.', '', website) - parts = website.split(".") - if len(parts) > 1: - return parts[0] + website = website.split('/')[0] return website def _generate_search_terms(self, company_name, website): """ Generiert Suchbegriffe in folgender Reihenfolge: - 1. Domain-Key (falls vorhanden) - 2. Die ersten zwei Wörter des Firmennamens - 3. Der vollständige Firmenname + 1. Vollständiger Domainname (z. B. "heimbach.com") + 2. Die ersten zwei Wörter des normalisierten Firmennamens + 3. Der vollständige, normalisierte Firmenname """ terms = [] - domain_key = self._get_domain_key(website) - if domain_key: - terms.append(domain_key) - candidate = " ".join(company_name.split()[:2]).strip() + full_domain = self._get_full_domain(website) + if full_domain: + terms.append(full_domain) + normalized_name = normalize_company_name(company_name) + candidate = " ".join(normalized_name.split()[:2]).strip() if candidate and candidate not in terms: terms.append(candidate) - original = company_name.strip() - if original and original not in terms: - terms.append(original) + if normalized_name and normalized_name not in terms: + terms.append(normalized_name) debug_print(f"Generierte Suchbegriffe: {terms}") return terms - def _validate_article(self, page, company_name, domain_key): + def _validate_article(self, page, company_name, website): """ Validiert den Artikel: - - Sucht in der Infobox nach externen Links, die den Domain-Key enthalten. - Wird der Domain-Key gefunden, wird der Artikel als definitiv korrekt akzeptiert. - - Andernfalls wird der Wikipedia-Titel mit dem Firmennamen mittels Ähnlichkeitsvergleich geprüft. + - Sucht in der Infobox und in den externen Links nach Links, die den vollständigen Domainnamen enthalten. + Wird ein solcher Link gefunden, wird ein niedrigerer Schwellenwert (0.60) angewendet. + - Andernfalls werden der normalisierte Wikipedia-Titel und der normalisierte Firmenname verglichen. """ - # Zuerst: Prüfe, ob der Domain-Key in den Links der Infobox vorkommt. - if domain_key: + full_domain = self._get_full_domain(website) + domain_found = False + if full_domain: try: html_raw = requests.get(page.url).text soup = BeautifulSoup(html_raw, Config.HTML_PARSER) + # Suche in der Infobox infobox = soup.find('table', class_=lambda c: c and 'infobox' in c.lower()) if infobox: links = infobox.find_all('a', href=True) for link in links: - href = link['href'].lower() - if domain_key in href: + href = link.get('href').lower() + # Überspringe Dateilinks + if href.startswith('/wiki/datei:'): + continue + if full_domain in href: debug_print(f"Definitiver Link-Match in Infobox gefunden: {href}") - return True + domain_found = True + break + # Suche in den externen Links, falls vorhanden + if not domain_found and hasattr(page, 'externallinks'): + for ext_link in page.externallinks: + ext_link = ext_link.lower() + if full_domain in ext_link: + debug_print(f"Definitiver Link-Match in externen Links gefunden: {ext_link}") + domain_found = True + break except Exception as e: - debug_print(f"Fehler beim Extrahieren des Infobox-Links: {str(e)}") - # Falls kein definitiver Link-Match, mache den Ähnlichkeitsvergleich: - clean_title = re.sub(r'\(.*?\)', '', page.title).lower() - clean_company = company_name.lower().strip() - similarity = SequenceMatcher(None, clean_title, clean_company).ratio() - debug_print(f"Ähnlichkeit: {similarity:.2f} ({clean_title} vs {clean_company})") - return similarity >= Config.SIMILARITY_THRESHOLD + debug_print(f"Fehler beim Extrahieren von Links: {str(e)}") + + # Normalisierte Namen für Vergleich + normalized_title = normalize_company_name(page.title) + normalized_company = normalize_company_name(company_name) + similarity = SequenceMatcher(None, normalized_title, normalized_company).ratio() + debug_print(f"Ähnlichkeit (normalisiert): {similarity:.2f} ({normalized_title} vs {normalized_company})") + + threshold = 0.60 if domain_found else Config.SIMILARITY_THRESHOLD + return similarity >= threshold @retry_on_failure def search_company_article(self, company_name, website): - """Sucht mit optimierten Suchbegriffen (Domain-Key, Candidate, Name) nach dem Wikipedia-Artikel.""" + """Sucht mit optimierten Suchbegriffen (vollständiger Domainname, Candidate, normalisierter Name) nach dem Wikipedia-Artikel.""" search_terms = self._generate_search_terms(company_name, website) - domain_key = self._get_domain_key(website) for term in search_terms: try: results = wikipedia.search(term, results=Config.WIKIPEDIA_SEARCH_RESULTS) @@ -156,7 +181,7 @@ class WikipediaScraper: for title in results: try: page = wikipedia.page(title, auto_suggest=False) - if self._validate_article(page, company_name, domain_key): + if self._validate_article(page, company_name, website): return page except (wikipedia.exceptions.DisambiguationError, wikipedia.exceptions.PageError) as e: debug_print(f"Seitenfehler: {str(e)}")