From 01edbf87e2b1bbca9438646a1ea378a8f82ea8e5 Mon Sep 17 00:00:00 2001 From: Floke Date: Tue, 8 Jul 2025 14:19:18 +0000 Subject: [PATCH] revoce --- dealfront_enrichment.py | 167 ++++++++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 57 deletions(-) diff --git a/dealfront_enrichment.py b/dealfront_enrichment.py index eef6179e..6b975737 100644 --- a/dealfront_enrichment.py +++ b/dealfront_enrichment.py @@ -11,20 +11,21 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException -# Temporäre Konfiguration direkt im Skript +# ============================================================================== +# TEMPORÄRE, AUTARKE KONFIGURATION +# ============================================================================== class TempConfig: + # --- Direkt hier definierte Werte, um config.py zu umgehen --- DEALFRONT_LOGIN_URL = "https://app.dealfront.com/login" - DEALFRONT_TARGET_URL = "https://app.dealfront.com/t/prospector/companies" - TARGET_SEARCH_NAME = "Facility Management" # Anzupassender Name der Suche + TARGET_SEARCH_NAME = "Facility Management" # <-- BITTE AN IHRE SUCHE ANPASSEN DEALFRONT_CREDENTIALS_FILE = "/app/dealfront_credentials.json" +# ============================================================================== OUTPUT_DIR = "/app/output" -LOG_FORMAT = '%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s' -logging.basicConfig(level=logging.INFO, format=LOG_FORMAT, force=True, handlers=[logging.StreamHandler()]) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s', force=True) logging.getLogger("selenium").setLevel(logging.WARNING) logger = logging.getLogger(__name__) -# FileHandler log_filename = f"dealfront_run_{time.strftime('%Y%m%d-%H%M%S')}.txt" log_filepath = os.path.join(OUTPUT_DIR, log_filename) try: @@ -38,9 +39,10 @@ except Exception as e: class DealfrontScraper: def __init__(self): - logger.info("Initialisiere DealfrontScraper...") + logger.info("Initialisiere den DealfrontScraper...") chrome_options = ChromeOptions() - chrome_options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2}) + prefs = {"profile.managed_default_content_settings.images": 2} + chrome_options.add_experimental_option("prefs", prefs) chrome_options.add_argument("--headless") chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage") @@ -49,7 +51,7 @@ class DealfrontScraper: try: self.driver = webdriver.Chrome(service=service, options=chrome_options) except Exception as e: - logger.critical("WebDriver konnte nicht initialisiert werden.", exc_info=True) + logger.critical(f"WebDriver konnte nicht initialisiert werden.", exc_info=True) raise self.wait = WebDriverWait(self.driver, 30) self.username, self.password = self._load_credentials() @@ -58,75 +60,126 @@ class DealfrontScraper: def _load_credentials(self): try: with open(TempConfig.DEALFRONT_CREDENTIALS_FILE, 'r') as f: - return json.load(f).get("username"), json.load(f).get("password") + creds = json.load(f) + return creds.get("username"), creds.get("password") except Exception as e: logger.error(f"Credentials-Datei konnte nicht geladen werden: {e}") return None, None def _save_debug_artifacts(self): - # Implementierung bleibt gleich - pass + # ... (Diese Methode bleibt unverändert) ... + try: + os.makedirs(OUTPUT_DIR, exist_ok=True) + timestamp = time.strftime("%Y%m%d-%H%M%S") + screenshot_filepath = os.path.join(OUTPUT_DIR, f"error_{timestamp}.png") + html_filepath = os.path.join(OUTPUT_DIR, f"error_{timestamp}.html") + self.driver.save_screenshot(screenshot_filepath) + logger.error(f"Screenshot '{screenshot_filepath}' wurde für die Analyse gespeichert.") + with open(html_filepath, "w", encoding="utf-8") as f: + f.write(self.driver.page_source) + logger.error(f"HTML-Quellcode '{html_filepath}' wurde für die Analyse gespeichert.") + except Exception as e: + logger.error(f"Konnte Debug-Artefakte nicht speichern: {e}") - def login(self): - logger.info(f"Navigiere zur Login-Seite: {TempConfig.DEALFRONT_LOGIN_URL}") - self.driver.get(TempConfig.DEALFRONT_LOGIN_URL) - self.wait.until(EC.visibility_of_element_located((By.NAME, "email"))).send_keys(self.username) - self.driver.find_element(By.CSS_SELECTOR, "input[type='password']").send_keys(self.password) - self.driver.find_element(By.XPATH, "//button[normalize-space()='Log in']").click() - logger.info("Login-Befehl gesendet. Kurze Pause...") - time.sleep(5) - return True - - def navigate_to_target(self): - logger.info(f"Navigiere direkt zur Target-URL.") - self.driver.get(TempConfig.DEALFRONT_TARGET_URL) - self.wait.until(EC.url_contains("/t/prospector/")) - logger.info(f"URL-Wechsel bestätigt.") - self.wait.until(EC.visibility_of_element_located((By.XPATH, "//button[normalize-space()='+ Neue Suche']"))) - logger.info("'Target'-Seite erfolgreich geladen.") - return True - - def load_search(self, search_name): - logger.info(f"Lade Suche: '{search_name}'...") - search_item_selector = (By.XPATH, f"//div[contains(@class, 'truncate') and normalize-space()='{search_name}']") - self.wait.until(EC.element_to_be_clickable(search_item_selector)).click() - logger.info(f"Suche '{search_name}' geladen.") - return True + def login_and_find_list(self, search_name): + # ... (Diese Methode bleibt unverändert, verwendet aber jetzt TempConfig) ... + try: + logger.info(f"Navigiere zur Login-Seite: {TempConfig.DEALFRONT_LOGIN_URL}") + self.driver.get(TempConfig.DEALFRONT_LOGIN_URL) + self.wait.until(EC.visibility_of_element_located((By.NAME, "email"))).send_keys(self.username) + self.driver.find_element(By.CSS_SELECTOR, "input[type='password']").send_keys(self.password) + self.driver.find_element(By.XPATH, "//button[normalize-space()='Log in']").click() + logger.info("Login-Befehl gesendet.") + logger.info("Warte auf Dashboard und den 'Prospects finden' Quick-Link...") + prospects_link_selector = (By.XPATH, "//a[@data-test-target-product-tile]") + prospects_link = self.wait.until(EC.element_to_be_clickable(prospects_link_selector)) + prospects_link.click() + logger.info("'Prospects finden' geklickt.") + logger.info(f"Warte auf die Liste der Suchen und klicke auf '{search_name}'...") + search_item_selector = (By.XPATH, f"//div[contains(@class, 'truncate') and normalize-space()='{search_name}']") + search_item = self.wait.until(EC.element_to_be_clickable(search_item_selector)) + search_item.click() + logger.info(f"Suche '{search_name}' geladen. Warte auf das Rendern der Ergebnistabelle.") + table_header_selector = (By.XPATH, "//th[normalize-space()='Firma']") + self.wait.until(EC.visibility_of_element_located(table_header_selector)) + time.sleep(5) + logger.info("Zielseite mit Ergebnissen erfolgreich erreicht.") + return True + except Exception as e: + logger.critical(f"Der Prozess ist fehlgeschlagen: {type(e).__name__}", exc_info=True) + self._save_debug_artifacts() + return False def extract_current_page_results(self): - logger.warning("Extraktionsmethode ist noch nicht implementiert und wird übersprungen.") - # Hier muss die robuste Warte- und Extraktionslogik implementiert werden. - return [] - + # ... (Diese Methode bleibt unverändert) ... + try: + logger.info("Extrahiere Ergebnisse von der aktuellen Seite...") + results = [] + rows_selector = (By.CSS_SELECTOR, "table#t-result-table tbody tr[id]") + self.wait.until(EC.presence_of_element_located(rows_selector)) + time.sleep(3) + rows = self.driver.find_elements(*rows_selector) + if not rows: + logger.warning("Keine Ergebniszeilen (tr[id]) gefunden.") + return [] + logger.info(f"{len(rows)} Firmen-Datenzeilen zur Verarbeitung gefunden.") + for i, row in enumerate(rows, 1): + try: + company_name = row.find_element(By.CSS_SELECTOR, ".sticky-column a.t-highlight-text").get_attribute("title").strip() + website = row.find_element(By.CSS_SELECTOR, "a.text-gray-400.t-highlight-text").text.strip() + results.append({'name': company_name, 'website': website}) + except NoSuchElementException: + logger.warning(f"Zeile {i}: Name oder Webseite nicht extrahierbar. Überspringe.") + continue + logger.info(f"Extraktion abgeschlossen. {len(results)} Firmen gefunden.") + return results + except Exception as e: + logger.error(f"Schwerwiegender Fehler bei der Extraktion: {type(e).__name__}", exc_info=True) + self._save_debug_artifacts() + return [] + def close(self): if self.driver: logger.info("Schließe den WebDriver.") self.driver.quit() + if __name__ == "__main__": - logger.info("Starte Dealfront Automatisierung - Stabiler Stand") + logger.info("Starte Dealfront Automatisierung - DEBUG-MODUS") scraper = None try: scraper = DealfrontScraper() - scraper.login() - scraper.navigate_to_target() - scraper.load_search(TempConfig.TARGET_SEARCH_NAME) - - logger.info("Alle Schritte bis zur Anzeige der Ergebnisseite waren erfolgreich.") - logger.info("Nächster Schritt: Implementierung der Extraktionslogik.") + if not scraper.driver: + raise Exception("WebDriver konnte nicht initialisiert werden.") - # Hier wird die (noch leere) Extraktionsmethode aufgerufen + if not scraper.login_and_find_list(TempConfig.TARGET_SEARCH_NAME): + raise Exception("Der Prozess vom Login bis zum Laden der Liste ist fehlgeschlagen.") + + # In dieser Version gibt es keine handle_overlays Methode mehr + # scraper.handle_overlays() + companies = scraper.extract_current_page_results() if companies: - print("Erfolgreich extrahiert (dies sollte noch nicht passieren).") + df = pd.DataFrame(companies) + pd.set_option('display.max_rows', None) + pd.set_option('display.max_columns', None) + pd.set_option('display.width', 1000) + pd.set_option('display.max_colwidth', None) + print("\n" + "="*80) + print(" EXTRAHIERTE FIRMEN (ERSTE SEITE) ".center(80, "=")) + print("="*80) + print(df.to_string(index=False)) + print("="*80 + "\n") else: - logger.info("Keine Daten extrahiert, wie erwartet.") - + logger.warning("Obwohl die Seite geladen wurde, konnten keine Firmen extrahiert werden.") + + logger.info("Test erfolgreich abgeschlossen. Warte vor dem Schließen...") + time.sleep(10) + except Exception as e: - logger.critical(f"Ein kritischer Fehler ist aufgetreten: {e}", exc_info=True) - scraper._save_debug_artifacts() + logger.critical(f"Ein kritischer Fehler ist im Hauptprozess aufgetreten: {e}", exc_info=False) finally: if scraper: - logger.info("Prozess beendet. Browser bleibt 10s offen.") - time.sleep(10) - scraper.close() \ No newline at end of file + scraper.close() + + logger.info("Dealfront Automatisierung beendet.") \ No newline at end of file