Files
Brancheneinstufung2/dealfront_enrichment.py

205 lines
9.7 KiB
Python

import os
import json
import time
import logging
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options as ChromeOptions
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
# Wichtig: Die zentralen Imports zuerst
from config import Config, DEALFRONT_LOGIN_URL, DEALFRONT_CREDENTIALS_FILE
# Logging-Konfiguration, eigenständig für dieses Skript
LOG_LEVEL = logging.DEBUG if Config.DEBUG else logging.INFO
LOG_FORMAT = '%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s'
logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT, force=True)
logger = logging.getLogger(__name__)
# Definiere einen festen Ausgabeordner, der im Dockerfile erstellt wird
OUTPUT_DIR = "/app/output"
class DealfrontScraper:
def __init__(self):
logger.info("Initialisiere den DealfrontScraper und den Chrome WebDriver.")
chrome_options = ChromeOptions()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
chrome_options.add_argument("--disable-dev-shm-usage")
chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False)
try:
self.driver = webdriver.Chrome(options=chrome_options)
self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
except Exception as e:
logger.critical(f"WebDriver konnte nicht initialisiert werden. Fehler: {e}", exc_info=True)
self.driver = None
raise
self.wait = WebDriverWait(self.driver, 20)
logger.info("WebDriver erfolgreich initialisiert.")
def _load_credentials(self):
try:
with open(DEALFRONT_CREDENTIALS_FILE, 'r') as f:
creds = json.load(f)
username = creds.get("username")
password = creds.get("password")
if not username or "DEIN_DEALFRONT_BENUTZERNAME" in username or not password or "DEIN_DEALFRONT_PASSWORT" in password:
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
except json.JSONDecodeError:
logger.error(f"Fehler beim Parsen der Credentials-Datei: '{DEALFRONT_CREDENTIALS_FILE}'")
return None, None
def _save_debug_artifacts(self):
"""Speichert einen Screenshot UND den HTML-Quellcode im Fehlerfall."""
try:
os.makedirs(OUTPUT_DIR, exist_ok=True)
timestamp = time.strftime("%Y%m%d-%H%M%S")
# 1. Screenshot speichern
screenshot_filepath = os.path.join(OUTPUT_DIR, f"error_{timestamp}.png")
self.driver.save_screenshot(screenshot_filepath)
logger.error(f"Screenshot '{screenshot_filepath}' wurde für die Analyse gespeichert.")
# 2. HTML-Quellcode speichern
html_filepath = os.path.join(OUTPUT_DIR, f"error_{timestamp}.html")
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):
"""
Führt den Login-Prozess auf der Dealfront-Plattform durch.
Diese Version wartet explizit auf jedes Element und verwendet
die verifizierten Selektoren aus dem HTML-Dump.
"""
if not self.driver:
return False
username, password = self._load_credentials()
if not username or not password:
return False
try:
logger.info(f"Navigiere zur Login-Seite: {DEALFRONT_LOGIN_URL}")
self.driver.get(DEALFRONT_LOGIN_URL)
# --- SCHRITT 1: Warten auf das E-Mail-Feld und ausfüllen ---
email_selector = (By.CSS_SELECTOR, "input[name='email']")
logger.debug(f"Warte auf die Sichtbarkeit des E-Mail-Feldes: {email_selector}")
email_field = self.wait.until(EC.visibility_of_element_located(email_selector))
email_field.send_keys(username)
logger.info("E-Mail-Feld gefunden und ausgefüllt.")
# --- SCHRITT 2: Warten auf das Passwort-Feld und ausfüllen ---
# KORRIGIERT: Wir verwenden den CSS-Selektor für das 'type'-Attribut,
# da kein 'name'-Attribut vorhanden ist.
password_selector = (By.CSS_SELECTOR, "input[type='password']")
logger.debug(f"Warte auf die Sichtbarkeit des Passwort-Feldes: {password_selector}")
password_field = self.wait.until(EC.visibility_of_element_located(password_selector))
password_field.send_keys(password)
logger.info("Passwort-Feld gefunden und ausgefüllt.")
# --- SCHRITT 3: Warten auf den Anmelde-Button und klicken ---
login_button_selector = (By.XPATH, "//button[normalize-space()='Log in']") # Englische Version, falls Sprache umschaltet
logger.debug(f"Warte darauf, dass der Anmelde-Button klickbar ist: {login_button_selector}")
login_button = self.wait.until(EC.element_to_be_clickable(login_button_selector))
login_button.click()
logger.info("Anmelde-Button geklickt. Warte auf die Verifizierung...")
# --- SCHRITT 4: Login-Erfolg verifizieren ---
verification_element_selector = (By.XPATH, "//input[@data-cy='header-search-input']")
logger.debug(f"Warte auf das Verifizierungs-Element: {verification_element_selector}")
self.wait.until(EC.visibility_of_element_located(verification_element_selector))
logger.info("LOGIN ERFOLGREICH! Dashboard-Element gefunden.")
return True
except Exception as e:
logger.critical(f"Ein Fehler ist während des Logins aufgetreten: {type(e).__name__}", exc_info=True)
self._save_debug_artifacts()
return False
def navigate_to_target_list(self, list_name: str):
"""
Navigiert nach dem Login zu einer spezifischen Zielliste im Target-Modul.
:param list_name: Der exakte Name der Zielliste, die angeklickt werden soll.
"""
try:
# --- SCHRITT 1: Zum Target-Modul navigieren ---
# Wir verwenden die Basis-URL, da die spezifische URL Session-IDs enthalten könnte.
target_url = "https://app.dealfront.com/t"
logger.info(f"Navigiere zum Target-Modul: {target_url}")
self.driver.get(target_url)
# --- SCHRITT 2: Auf die Liste in der Seitenleiste warten und klicken ---
# Wir suchen nach einem Link, dessen Text genau mit dem Listennamen übereinstimmt.
# Dieser XPath ist sehr spezifisch und robust.
list_selector = (By.XPATH, f"//div[contains(@class, 'list-group-item')]//a[normalize-space()='{list_name}']")
logger.debug(f"Warte darauf, dass die Zielliste '{list_name}' klickbar ist.")
target_list_element = self.wait.until(EC.element_to_be_clickable(list_selector))
target_list_element.click()
logger.info(f"Zielliste '{list_name}' erfolgreich angeklickt.")
# --- SCHRITT 3: Warten, bis die Ergebnisseite geladen ist ---
# Wir verifizieren, dass der Header der Tabelle sichtbar ist.
# Ein guter Indikator ist die Spaltenüberschrift "Firma".
results_header_selector = (By.XPATH, "//th[normalize-space()='Firma']")
self.wait.until(EC.visibility_of_element_located(results_header_selector))
logger.info("Ergebnistabelle erfolgreich geladen.")
return True
except Exception as e:
logger.critical(f"Fehler beim Navigieren zur Zielliste '{list_name}': {type(e).__name__}", exc_info=True)
self._save_debug_artifacts()
return False
def close(self):
if self.driver:
logger.info("Schließe den WebDriver.")
self.driver.quit()
if __name__ == "__main__":
logger.info("Starte Dealfront Automatisierung - Phase 2: Test der Ziellisten-Navigation")
# Der Name der Zielliste, die wir testen wollen
TARGET_LIST_NAME = "vending_slot"
scraper = None
try:
scraper = DealfrontScraper()
if scraper.driver:
if scraper.login():
logger.info("Login erfolgreich. Starte Navigation zur Zielliste...")
if scraper.navigate_to_target_list(TARGET_LIST_NAME):
logger.info(f"Erfolgreich zur Liste '{TARGET_LIST_NAME}' navigiert. Wir sind bereit für die Extraktion.")
# Kurze Pause, damit wir das Ergebnis sehen können
time.sleep(5)
else:
logger.error("Navigation zur Zielliste ist fehlgeschlagen.")
else:
logger.error("Login-Prozess ist fehlgeschlagen.")
except Exception as e:
logger.critical(f"Ein kritischer Fehler ist im Hauptprozess aufgetreten.", exc_info=True)
finally:
if scraper:
scraper.close()
logger.info("Test der Ziellisten-Navigation beendet.")