diff --git a/brancheneinstufung.py b/brancheneinstufung.py index 768fe814..dda1951d 100644 --- a/brancheneinstufung.py +++ b/brancheneinstufung.py @@ -3638,107 +3638,139 @@ class WikipediaScraper: return result @retry_on_failure - def search_company_article(self, company_name, website=None): + def search_company_article(self, company_name, website=None, max_recursion_depth=2): # max_recursion_depth hinzugefügt """ Sucht einen passenden Wikipedia-Artikel fuer das Unternehmen und gibt das wikipedia.WikipediaPage Objekt zurueck, wenn ein relevanter und validierter Artikel gefunden wird. Behandelt explizit Begriffsklaerungsseiten. """ if not company_name or str(company_name).strip() == "": - self.logger.warning("Wikipedia search skipped: No company name provided.") # <<< GEÄNDERT + self.logger.warning("Wikipedia search skipped: No company name provided.") raise ValueError("Kein Firmenname fuer Wikipedia Suche angegeben.") search_terms = self._generate_search_terms(company_name, website) if not search_terms: - self.logger.warning(f"Keine Suchbegriffe fuer '{company_name[:100]}...' generiert.") # <<< GEÄNDERT + self.logger.warning(f"Keine Suchbegriffe fuer '{company_name[:100]}...' generiert.") return None - self.logger.info( # <<< GEÄNDERT + self.logger.info( f"Starte Wikipedia-Suche fuer '{company_name[:100]}...' " f"(Website: {website[:100]}...) mit Begriffen: {search_terms}" ) - processed_titles = set() + processed_titles = set() # Verhindert Endlosschleifen bei zirkulären Redirects/Disambiguations - def check_page(title_to_check): - if title_to_check in processed_titles: + # Innere Hilfsfunktion mit Rekursionstiefe + def check_page_recursive(title_to_check, current_depth): + if title_to_check in processed_titles or current_depth > max_recursion_depth: + if title_to_check in processed_titles: + self.logger.debug(f" -> Titel '{title_to_check[:100]}...' bereits verarbeitet oder Rekursionstiefe überschritten.") return None processed_titles.add(title_to_check) - self.logger.debug(f" -> Pruefe potenziellen Artikel: '{title_to_check[:100]}...'") # <<< GEÄNDERT + + self.logger.debug(f" -> Pruefe potenziellen Artikel: '{title_to_check[:100]}...' (Tiefe: {current_depth})") try: page = wikipedia.page(title_to_check, auto_suggest=False, preload=True) - if self._validate_article(page, company_name, website): - self.logger.info(f" -> Titel '{page.title[:100]}...' erfolgreich validiert!") # <<< GEÄNDERT + if self._validate_article(page, company_name, website): # company_name ist der ursprüngliche Suchname + self.logger.info(f" -> Titel '{page.title[:100]}...' erfolgreich validiert!") return page else: - self.logger.debug(f" -> Titel '{title_to_check[:100]}...' nicht validiert.") # <<< GEÄNDERT + self.logger.debug(f" -> Titel '{title_to_check[:100]}...' nicht validiert.") return None except wikipedia.exceptions.PageError: - self.logger.debug(f" -> Seite '{title_to_check[:100]}...' nicht gefunden (PageError).") # <<< GEÄNDERT + self.logger.debug(f" -> Seite '{title_to_check[:100]}...' nicht gefunden (PageError).") return None except wikipedia.exceptions.DisambiguationError as e_inner: - self.logger.info( # <<< GEÄNDERT - f" -> Begriffsklaerung '{title_to_check[:100]}...' gefunden. " + self.logger.info( + f" -> Begriffsklaerung '{title_to_check[:100]}...' gefunden (Tiefe {current_depth}). " f"Pruefe Optionen: {str(e_inner.options)[:100]}..." ) + if current_depth >= max_recursion_depth: + self.logger.warning(f" -> Maximale Rekursionstiefe ({max_recursion_depth}) für Begriffsklärung '{title_to_check[:100]}...' erreicht. Stoppe weitere Auflösung.") + return None + + # Filtere und priorisiere Optionen + # Man könnte hier noch intelligenter filtern, z.B. Optionen bevorzugen, die "(Unternehmen)" enthalten + valid_options = [] for option in e_inner.options: option_lower = option.lower() - if any( + # Erweiterte Negativ-Filterung + if not any( ex in option_lower - for ex in ["(person)", "(ort)", "(geographie)", "liste ", "liste)"] - ): - continue - validated_option_page = check_page(option) + for ex in [ + "(person)", "(familienname)", "(vorname)", "(künstlername)", + "(ort)", "(geographie)", "(stadt)", "(gemeinde)", "(landkreis)", + "liste ", "liste)", "(album)", "(film)", "(lied)", "(manga)", + "(einheit)", "(maßeinheit)", "(begriffsklärung)", "Begriffsklärung)" # Begriffsklärungen explizit ausschließen für direkte Rekursion + ] + ) and len(option) < 100 : # Vermeide extrem lange Options-Strings + valid_options.append(option) + + self.logger.debug(f" -> {len(valid_options)} potenziell relevante Optionen für '{title_to_check[:100]}...' nach Filterung: {str(valid_options)[:200]}...") + + for option in valid_options: + # Rekursiver Aufruf mit erhöhter Tiefe + validated_option_page = check_page_recursive(option, current_depth + 1) if validated_option_page: - self.logger.info( # <<< GEÄNDERT + self.logger.info( f" -> Option '{option[:100]}...' aus Begriffsklaerung erfolgreich validiert!" ) return validated_option_page - self.logger.debug( # <<< GEÄNDERT + + self.logger.debug( f" -> Keine passende/validierte Unternehmens-Option in Begriffsklaerung '{title_to_check[:100]}...' gefunden." ) - return None + return None # Wichtig: Gibt None zurück, wenn keine der Optionen erfolgreich war except (requests.exceptions.RequestException, wikipedia.exceptions.WikipediaException) as e_req: - self.logger.warning( # <<< GEÄNDERT + self.logger.warning( f" -> Netzwerk/API-Fehler beim Laden/Validieren von '{title_to_check[:100]}...': " f"{type(e_req).__name__} - {e_req}. Ueberspringe diesen Titel." ) return None except Exception as e_page: - self.logger.error( # <<< GEÄNDERT + self.logger.error( f" -> Unerwarteter Fehler bei Verarbeitung von Titel '{title_to_check[:100]}...': " f"{type(e_page).__name__} - {e_page}" ) - self.logger.debug(traceback.format_exc()) # <<< GEÄNDERT + self.logger.debug(traceback.format_exc()) return None - self.logger.debug(f" -> Versuche direkten Match fuer '{company_name[:100]}...'...") # <<< GEÄNDERT - validated_page = check_page(company_name) + # Direkter Match mit dem ursprünglichen Firmennamen + self.logger.debug(f" -> Versuche direkten Match fuer '{company_name[:100]}...'...") + validated_page = check_page_recursive(company_name, current_depth=0) # Starte mit Tiefe 0 if validated_page: return validated_page - self.logger.debug( # <<< GEÄNDERT + # Suche mit generierten Begriffen + self.logger.debug( f" -> Kein direkter Treffer/validiert. Starte Suche mit generierten Begriffen: {search_terms}" ) for term in search_terms: + if term == company_name: continue # Bereits oben geprüft + try: - self.logger.debug(f" -> Suche mit Begriff: '{term[:100]}...'...") # <<< GEÄNDERT - search_results = wikipedia.search(term, results=getattr(Config, 'WIKIPEDIA_SEARCH_RESULTS', 5)) - self.logger.debug(f" -> Suchergebnisse fuer '{term[:100]}...': {search_results}") # <<< GEÄNDERT + self.logger.debug(f" -> Suche mit Begriff: '{term[:100]}...'...") + search_results = wikipedia.search(term, results=getattr(Config, 'WIKIPEDIA_SEARCH_RESULTS', 3)) # Weniger Ergebnisse pro Term + self.logger.debug(f" -> Suchergebnisse fuer '{term[:100]}...': {search_results}") if not search_results: - self.logger.debug(f" -> Keine Suchergebnisse fuer '{term[:100]}...'.") # <<< GEÄNDERT + self.logger.debug(f" -> Keine Suchergebnisse fuer '{term[:100]}...'.") continue for title in search_results: - validated_page = check_page(title) + validated_page = check_page_recursive(title, current_depth=0) # Starte mit Tiefe 0 für jeden neuen Suchtreffer if validated_page: return validated_page except Exception as e_search: - self.logger.error( # <<< GEÄNDERT - f"Fehler waehrend Wikipedia-Suche fuer '{term[:100]}...': " + # Fehler von wikipedia.search() selbst, nicht von check_page_recursive + self.logger.error( + f"Fehler waehrend Wikipedia-Suche (wikipedia.search) fuer '{term[:100]}...': " f"{type(e_search).__name__} - {e_search}" ) - raise e_search + # Hier nicht direkt 'raise e_search', da der Retry-Decorator auf search_company_article liegt. + # Wenn dies ein wiederholbarer Fehler der wikipedia-Bibliothek ist, sollte der Decorator greifen. + # Für den Moment loggen wir und machen mit dem nächsten Suchbegriff weiter. + # Bei einem persistenten API-Problem wird der äußere Decorator nach mehreren Versuchen abbrechen. - self.logger.warning( # <<< GEÄNDERT + + self.logger.warning( f"Kein passender & validierter Wikipedia-Artikel fuer '{company_name[:100]}...' gefunden nach Pruefung aller Begriffe und Optionen." ) return None