dealfront_enrichment.py aktualisiert

This commit is contained in:
2025-07-08 11:13:25 +00:00
parent c2c56e6f72
commit 8d35722c0d

View File

@@ -11,37 +11,33 @@ from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException from selenium.common.exceptions import TimeoutException, NoSuchElementException
# Importiere Konfigurationen # ==============================================================================
from config import Config, DEALFRONT_LOGIN_URL, DEALFRONT_CREDENTIALS_FILE, DEALFRONT_TARGET_URL, TARGET_SEARCH_NAME # TEMPORÄRE, AUTARKE KONFIGURATION
# ==============================================================================
class TempConfig:
# --- Direkt hier definierte Werte, um config.py zu umgehen ---
DEALFRONT_LOGIN_URL = "https://app.dealfront.com/login"
TARGET_SEARCH_NAME = "Facility Management" # <-- BITTE AN IHRE SUCHE ANPASSEN
DEALFRONT_CREDENTIALS_FILE = "/app/dealfront_credentials.json"
# ==============================================================================
# Definiere einen festen Ausgabeordner, der im Dockerfile erstellt wird
OUTPUT_DIR = "/app/output" OUTPUT_DIR = "/app/output"
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s', force=True)
# Logging-Konfiguration logging.getLogger("selenium").setLevel(logging.WARNING)
LOG_LEVEL = logging.INFO
LOG_FORMAT = '%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s'
logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT, force=True, handlers=[logging.StreamHandler()])
logging.getLogger("selenium").setLevel(logging.WARNING) # Selenium-Spam reduzieren
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# FileHandler hinzufügen, um in eine .txt-Datei zu loggen
log_filename = f"dealfront_run_{time.strftime('%Y%m%d-%H%M%S')}.txt" log_filename = f"dealfront_run_{time.strftime('%Y%m%d-%H%M%S')}.txt"
log_filepath = os.path.join(OUTPUT_DIR, log_filename) log_filepath = os.path.join(OUTPUT_DIR, log_filename)
try: try:
file_handler = logging.FileHandler(log_filepath, mode='w', encoding='utf-8') file_handler = logging.FileHandler(log_filepath, mode='w', encoding='utf-8')
file_handler.setLevel(logging.DEBUG) # Alles in die Datei schreiben file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(LOG_FORMAT)) file_handler.setFormatter(logging.Formatter(LOG_FORMAT))
logging.getLogger().addHandler(file_handler) logging.getLogger().addHandler(file_handler)
logger.info(f"Logging konfiguriert. Konsole auf Level {logging.getLevelName(LOG_LEVEL)}. Log-Datei: {log_filepath}") logger.info(f"Logging konfiguriert. Log-Datei: {log_filepath}")
except FileNotFoundError: except Exception as e:
logger.error(f"Konnte Log-Datei nicht erstellen. Das Verzeichnis '{OUTPUT_DIR}' existiert möglicherweise nicht im Container.") logger.error(f"Konnte Log-Datei nicht erstellen: {e}")
logger.error("Stellen Sie sicher, dass das Volume-Mapping korrekt ist: -v \"$(pwd)/output:/app/output\"")
class DealfrontScraper: class DealfrontScraper:
"""
Kapselt alle Interaktionen mit der Dealfront-Plattform mittels Selenium.
"""
def __init__(self): def __init__(self):
logger.info("Initialisiere den DealfrontScraper...") logger.info("Initialisiere den DealfrontScraper...")
chrome_options = ChromeOptions() chrome_options = ChromeOptions()
@@ -51,37 +47,27 @@ class DealfrontScraper:
chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--window-size=1920,1080") chrome_options.add_argument("--window-size=1920,1080")
# Service-Objekt, das explizit den system-installierten Treiber verwendet
service = Service(executable_path='/usr/bin/chromedriver') service = Service(executable_path='/usr/bin/chromedriver')
try: try:
self.driver = webdriver.Chrome(service=service, options=chrome_options) self.driver = webdriver.Chrome(service=service, options=chrome_options)
except Exception as e: except Exception as e:
logger.critical(f"WebDriver konnte nicht initialisiert werden. Fehler: {e}", exc_info=True) logger.critical(f"WebDriver konnte nicht initialisiert werden.", exc_info=True)
self.driver = None
raise raise
self.wait = WebDriverWait(self.driver, 30) self.wait = WebDriverWait(self.driver, 30)
self.username, self.password = self._load_credentials() self.username, self.password = self._load_credentials()
logger.info("WebDriver erfolgreich initialisiert.") logger.info("WebDriver erfolgreich initialisiert.")
def _load_credentials(self): def _load_credentials(self):
try: try:
with open(DEALFRONT_CREDENTIALS_FILE, 'r') as f: with open(TempConfig.DEALFRONT_CREDENTIALS_FILE, 'r') as f:
creds = json.load(f) creds = json.load(f)
username = creds.get("username") return creds.get("username"), creds.get("password")
password = creds.get("password") except Exception as e:
if not username or "DEIN_DEALFRONT_BENUTZERNAME" in username or not password: logger.error(f"Credentials-Datei konnte nicht geladen werden: {e}")
logger.error(f"Zugangsdaten in '{DEALFRONT_CREDENTIALS_FILE}' sind ungültig.")
return None, None
return username, password
except FileNotFoundError:
logger.error(f"Credentials-Datei nicht gefunden: '{DEALFRONT_CREDENTIALS_FILE}'")
return None, None return None, None
return None, None
def _save_debug_artifacts(self): def _save_debug_artifacts(self):
# ... (Diese Methode bleibt unverändert) ...
try: try:
os.makedirs(OUTPUT_DIR, exist_ok=True) os.makedirs(OUTPUT_DIR, exist_ok=True)
timestamp = time.strftime("%Y%m%d-%H%M%S") timestamp = time.strftime("%Y%m%d-%H%M%S")
@@ -96,59 +82,36 @@ class DealfrontScraper:
logger.error(f"Konnte Debug-Artefakte nicht speichern: {e}") logger.error(f"Konnte Debug-Artefakte nicht speichern: {e}")
def login_and_find_list(self, search_name): def login_and_find_list(self, search_name):
"""Führt den gesamten Prozess vom Login bis zum Laden der Zielliste robust aus.""" # ... (Diese Methode bleibt unverändert, verwendet aber jetzt TempConfig) ...
try: try:
# === LOGIN === logger.info(f"Navigiere zur Login-Seite: {TempConfig.DEALFRONT_LOGIN_URL}")
logger.info(f"Navigiere zur Login-Seite: {DEALFRONT_LOGIN_URL}") self.driver.get(TempConfig.DEALFRONT_LOGIN_URL)
self.driver.get(DEALFRONT_LOGIN_URL)
self.wait.until(EC.visibility_of_element_located((By.NAME, "email"))).send_keys(self.username) 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.CSS_SELECTOR, "input[type='password']").send_keys(self.password)
self.driver.find_element(By.XPATH, "//button[normalize-space()='Log in']").click() self.driver.find_element(By.XPATH, "//button[normalize-space()='Log in']").click()
logger.info("Login-Befehl gesendet.") logger.info("Login-Befehl gesendet.")
# === NAVIGATION ZUM TARGET BEREICH ===
logger.info("Warte auf Dashboard und den 'Prospects finden' Quick-Link...") logger.info("Warte auf Dashboard und den 'Prospects finden' Quick-Link...")
prospects_link_selector = (By.XPATH, "//a[@data-test-target-product-tile]") 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 = self.wait.until(EC.element_to_be_clickable(prospects_link_selector))
prospects_link.click() prospects_link.click()
logger.info("'Prospects finden' geklickt.") logger.info("'Prospects finden' geklickt.")
# === LADEN DER SPEZIFISCHEN SUCHE ===
logger.info(f"Warte auf die Liste der Suchen und klicke auf '{search_name}'...") 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_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 = self.wait.until(EC.element_to_be_clickable(search_item_selector))
search_item.click() search_item.click()
# === VERIFIZIERUNG UND WARTEN AUF TABELLENDATEN ===
logger.info(f"Suche '{search_name}' geladen. Warte auf das Rendern der Ergebnistabelle.") logger.info(f"Suche '{search_name}' geladen. Warte auf das Rendern der Ergebnistabelle.")
table_header_selector = (By.XPATH, "//th[normalize-space()='Firma']") table_header_selector = (By.XPATH, "//th[normalize-space()='Firma']")
self.wait.until(EC.visibility_of_element_located(table_header_selector)) self.wait.until(EC.visibility_of_element_located(table_header_selector))
time.sleep(5) time.sleep(5)
logger.info("Zielseite mit Ergebnissen erfolgreich erreicht.") logger.info("Zielseite mit Ergebnissen erfolgreich erreicht.")
return True return True
except Exception as e: except Exception as e:
logger.critical(f"Der Prozess vom Login bis zum Finden der Liste ist fehlgeschlagen: {type(e).__name__}", exc_info=True) logger.critical(f"Der Prozess ist fehlgeschlagen: {type(e).__name__}", exc_info=True)
self._save_debug_artifacts() self._save_debug_artifacts()
return False return False
def handle_overlays(self):
"""Sucht nach bekannten Popups/Overlays und schließt sie."""
try:
short_wait = WebDriverWait(self.driver, 5)
close_button_xpath = "//button[@aria-label='Schließen' or @aria-label='Close']"
logger.info("Suche nach bekannten Overlays/Popups...")
close_button = short_wait.until(EC.element_to_be_clickable((By.XPATH, close_button_xpath)))
logger.info("Schließen-Button für Overlay gefunden. Klicke darauf.")
close_button.click()
time.sleep(1)
except TimeoutException:
logger.info("Kein Overlay/Popup gefunden. Fahre fort.")
except Exception as e:
logger.warning(f"Fehler beim Schließen des Overlays, ignoriere und fahre fort: {e}")
def extract_current_page_results(self): def extract_current_page_results(self):
"""Extrahiert die Firmennamen und Webseiten.""" # ... (Diese Methode bleibt unverändert) ...
try: try:
logger.info("Extrahiere Ergebnisse von der aktuellen Seite...") logger.info("Extrahiere Ergebnisse von der aktuellen Seite...")
results = [] results = []
@@ -182,17 +145,18 @@ class DealfrontScraper:
if __name__ == "__main__": if __name__ == "__main__":
logger.info("Starte Dealfront Automatisierung - Finaler Durchbruchsversuch") logger.info("Starte Dealfront Automatisierung - DEBUG-MODUS")
scraper = None scraper = None
try: try:
scraper = DealfrontScraper() scraper = DealfrontScraper()
if not scraper.driver: if not scraper.driver:
raise Exception("WebDriver konnte nicht initialisiert werden.") raise Exception("WebDriver konnte nicht initialisiert werden.")
if not scraper.login_and_find_list(Config.TARGET_SEARCH_NAME): if not scraper.login_and_find_list(TempConfig.TARGET_SEARCH_NAME):
raise Exception("Der Prozess vom Login bis zum Laden der Liste ist fehlgeschlagen. Details siehe Log.") raise Exception("Der Prozess vom Login bis zum Laden der Liste ist fehlgeschlagen.")
scraper.handle_overlays() # In dieser Version gibt es keine handle_overlays Methode mehr
# scraper.handle_overlays()
companies = scraper.extract_current_page_results() companies = scraper.extract_current_page_results()
if companies: if companies:
@@ -207,13 +171,15 @@ if __name__ == "__main__":
print(df.to_string(index=False)) print(df.to_string(index=False))
print("="*80 + "\n") print("="*80 + "\n")
else: else:
logger.warning("Obwohl die Seite geladen wurde, konnten keine Firmen extrahiert werden. Bitte HTML-Dump prüfen.") logger.warning("Obwohl die Seite geladen wurde, konnten keine Firmen extrahiert werden.")
logger.info("Phase 2a Test erfolgreich abgeschlossen. Warte vor dem Schließen...") logger.info("Test erfolgreich abgeschlossen. Warte vor dem Schließen...")
time.sleep(10) time.sleep(10)
except Exception as e: except Exception as e:
logger.critical(f"Ein kritischer Fehler ist im Hauptprozess aufgetreten: {e}", exc_info=False) logger.critical(f"Ein kritischer Fehler ist im Hauptprozess aufgetreten: {e}", exc_info=False)
finally: finally:
if scraper: if scraper:
scraper.close() scraper.close()
logger.info("Dealfront Automatisierung beendet.") logger.info("Dealfront Automatisierung beendet.")