dealfront_enrichment.py aktualisiert

This commit is contained in:
2025-07-03 04:55:33 +00:00
parent 26fa27b0fb
commit a36e943b07

View File

@@ -4,71 +4,54 @@ import time
import logging import logging
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.by import By from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.chrome.options import Options as ChromeOptions from selenium.webdriver.chrome.options import Options as ChromeOptions
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import WebDriverWait 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
# Wichtig: Die zentralen Imports zuerst
from config import Config, DEALFRONT_LOGIN_URL, DEALFRONT_CREDENTIALS_FILE from config import Config, DEALFRONT_LOGIN_URL, DEALFRONT_CREDENTIALS_FILE
from helpers import setup_logging
# Logging aus dem helpers-Modul initialisieren # Logging-Konfiguration, eigenständig für dieses Skript
setup_logging(log_level=logging.DEBUG if Config.DEBUG else logging.INFO) LOG_LEVEL = logging.DEBUG if Config.DEBUG else logging.INFO
# Spezifischen Logger für dieses Modul erstellen LOG_FORMAT = '%(asctime)s - %(levelname)-8s - %(name)-25s - %(message)s'
logging.basicConfig(level=LOG_LEVEL, format=LOG_FORMAT, force=True)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Definiere einen festen Ausgabeordner, der im Dockerfile erstellt wird
OUTPUT_DIR = "/app/output"
# ... (Kontext: Zeile davor)
class DealfrontScraper: class DealfrontScraper:
"""
Kapselt alle Interaktionen mit der Dealfront-Plattform mittels Selenium.
"""
def __init__(self): def __init__(self):
"""
Initialisiert den WebDriver und den WebDriverWait.
Verwendet den system-installierten chromedriver im Docker-Container.
"""
logger.info("Initialisiere den DealfrontScraper und den Chrome WebDriver.") logger.info("Initialisiere den DealfrontScraper und den Chrome WebDriver.")
chrome_options = ChromeOptions() chrome_options = ChromeOptions()
# Docker-optimierte und Headless-Argumente
chrome_options.add_argument("--headless") chrome_options.add_argument("--headless")
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")
# Anti-Detection-Maßnahmen
chrome_options.add_argument("--disable-blink-features=AutomationControlled") chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option('useAutomationExtension', False) chrome_options.add_experimental_option('useAutomationExtension', False)
# Da der chromedriver im Dockerfile systemweit installiert wird,
# ist der webdriver-manager nicht mehr nötig. Selenium findet den Treiber von selbst.
try: try:
self.driver = webdriver.Chrome(options=chrome_options) self.driver = webdriver.Chrome(options=chrome_options)
# Wichtig, um als "echter" Browser zu erscheinen
self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})") self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
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. Fehler: {e}", exc_info=True)
self.driver = None self.driver = None
raise raise
# Zentraler WebDriverWait mit einem Timeout von 20 Sekunden
self.wait = WebDriverWait(self.driver, 20) self.wait = WebDriverWait(self.driver, 20)
logger.info("WebDriver erfolgreich initialisiert.") logger.info("WebDriver erfolgreich initialisiert.")
def _load_credentials(self): def _load_credentials(self):
"""Lädt Dealfront-Zugangsdaten sicher aus der JSON-Datei."""
try: try:
with open(DEALFRONT_CREDENTIALS_FILE, 'r') as f: with open(DEALFRONT_CREDENTIALS_FILE, 'r') as f:
creds = json.load(f) creds = json.load(f)
username = creds.get("username") username = creds.get("username")
password = creds.get("password") password = creds.get("password")
if not username or "DEIN_DEALFRONT_BENUTZERNAME" in username or not password or "DEIN_DEALFRONT_PASSWORT" in 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 oder Platzhalter.") logger.error(f"Zugangsdaten in '{DEALFRONT_CREDENTIALS_FILE}' sind ungültig.")
return None, None return None, None
return username, password return username, password
except FileNotFoundError: except FileNotFoundError:
@@ -78,12 +61,17 @@ class DealfrontScraper:
logger.error(f"Fehler beim Parsen der Credentials-Datei: '{DEALFRONT_CREDENTIALS_FILE}'") logger.error(f"Fehler beim Parsen der Credentials-Datei: '{DEALFRONT_CREDENTIALS_FILE}'")
return None, None return None, None
def _save_error_screenshot(self):
try:
os.makedirs(OUTPUT_DIR, exist_ok=True)
filepath = os.path.join(OUTPUT_DIR, "login_error_screenshot.png")
self.driver.save_screenshot(filepath)
logger.error(f"Screenshot '{filepath}' wurde für die Analyse gespeichert.")
except Exception as e_ss:
logger.error(f"Konnte Screenshot nicht speichern: {e_ss}")
def login(self): def login(self):
"""
Führt den Login-Prozess auf der Dealfront-Plattform durch.
"""
if not self.driver: if not self.driver:
logger.error("Login nicht möglich, da der WebDriver nicht initialisiert wurde.")
return False return False
username, password = self._load_credentials() username, password = self._load_credentials()
@@ -94,84 +82,64 @@ class DealfrontScraper:
logger.info(f"Navigiere zur Login-Seite: {DEALFRONT_LOGIN_URL}") logger.info(f"Navigiere zur Login-Seite: {DEALFRONT_LOGIN_URL}")
self.driver.get(DEALFRONT_LOGIN_URL) self.driver.get(DEALFRONT_LOGIN_URL)
# 1. Cookie-Banner behandeln (falls vorhanden) # Warten, bis die Seite grundsätzlich geladen ist (z.B. auf das Username-Feld warten)
try: self.wait.until(EC.visibility_of_element_located((By.ID, "username")))
logger.debug("Suche nach Cookie-Banner...")
# NEU: Robusterer XPath-Selektor, der nach einem Button mit spezifischem Text sucht.
# Dies ist weniger anfällig für ID-Änderungen.
cookie_button_xpath = "//button[contains(text(), 'Alle zulassen') or contains(text(), 'Alle akzeptieren')]"
cookie_button = self.wait.until(EC.element_to_be_clickable((By.XPATH, cookie_button_xpath)))
cookie_button.click()
logger.info("Cookie-Banner erfolgreich via XPath geklickt.")
# Kurze Pause nach dem Klick, damit sich die Seite anpassen kann
time.sleep(1)
except TimeoutException:
logger.warning("Cookie-Banner konnte nicht via XPath gefunden werden. Das kann OK sein, wenn kein Banner da war.")
# 2. Anmeldedaten ausfüllen # Cookie-Banner behandeln, wenn es existiert
logger.info("Fülle Anmeldeformular aus...") try:
username_field = self.wait.until(EC.visibility_of_element_located((By.ID, "username"))) # Wir suchen nach einem allgemeineren Cookie-Banner-Container
cookie_banner = self.driver.find_element(By.ID, "CybotCookiebotDialog")
if cookie_banner.is_displayed():
logger.debug("Cookie-Banner-Container gefunden. Suche nach 'Alle zulassen'-Button.")
# Suchen des Buttons innerhalb des Banners
allow_button = cookie_banner.find_element(By.ID, "CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll")
allow_button.click()
logger.info("Cookie-Banner akzeptiert.")
time.sleep(1) # Kurze Pause
except NoSuchElementException:
logger.warning("Kein Cookie-Banner gefunden. Fahre mit Login fort.")
username_field = self.driver.find_element(By.ID, "username")
password_field = self.driver.find_element(By.ID, "password") password_field = self.driver.find_element(By.ID, "password")
username_field.send_keys(username) username_field.send_keys(username)
password_field.send_keys(password) password_field.send_keys(password)
logger.info("Benutzername und Passwort eingetragen.") logger.info("Benutzername und Passwort eingetragen.")
# 3. Login-Button klicken
login_button = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']") login_button = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
login_button.click() login_button.click()
logger.info("Login-Button geklickt. Warte auf die Verifizierung...") logger.info("Login-Button geklickt. Warte auf die Verifizierung...")
# 4. Login-Erfolg verifizieren
# Wir warten auf ein Element, das nur nach erfolgreichem Login existiert,
# z.B. das Haupt-Suchfeld auf dem Dashboard.
verification_element_xpath = "//input[@data-cy='header-search-input']" verification_element_xpath = "//input[@data-cy='header-search-input']"
self.wait.until(EC.visibility_of_element_located((By.XPATH, verification_element_xpath))) self.wait.until(EC.visibility_of_element_located((By.XPATH, verification_element_xpath)))
logger.info("Login erfolgreich! Dashboard-Element gefunden.") logger.info("Login erfolgreich! Dashboard-Element gefunden.")
return True return True
except TimeoutException as e:
logger.error(f"Login fehlgeschlagen. Timeout beim Warten auf ein Element.")
self.driver.save_screenshot("login_error_screenshot.png")
logger.error("Screenshot 'login_error_screenshot.png' wurde für die Analyse gespeichert.")
return False
except NoSuchElementException as e:
logger.error(f"Login fehlgeschlagen. Ein Element konnte nicht gefunden werden.")
self.driver.save_screenshot("login_error_screenshot.png")
return False
except Exception as e: except Exception as e:
logger.critical(f"Ein unerwarteter Fehler ist während des Logins aufgetreten: {e}", exc_info=True) logger.critical(f"Ein Fehler ist während des Logins aufgetreten: {type(e).__name__}", exc_info=True)
self.driver.save_screenshot("login_error_screenshot.png") self._save_error_screenshot()
return False return False
def close(self): def close(self):
"""
Schließt den WebDriver und beendet die Browser-Session.
"""
if self.driver: if self.driver:
logger.info("Schließe den WebDriver.") logger.info("Schließe den WebDriver.")
self.driver.quit() self.driver.quit()
if __name__ == "__main__": if __name__ == "__main__":
logger.info("Starte Dealfront Automatisierung - Phase 1: Login-Test") logger.info("Starte Dealfront Automatisierung - Phase 1: Login-Test")
scraper = None scraper = None
try: try:
scraper = DealfrontScraper() scraper = DealfrontScraper()
if scraper.driver: # Nur wenn der Driver erfolgreich gestartet wurde if scraper.driver:
if scraper.login(): if scraper.login():
logger.info("Login-Prozess erfolgreich abgeschlossen. Der Bot ist nun eingeloggt.") logger.info("Login-Prozess erfolgreich abgeschlossen.")
# Hier würde in Zukunft die weitere Logik (Suche, Extraktion) folgen.
# Für den Test warten wir 5 Sekunden, damit man das Ergebnis sehen kann.
time.sleep(5) time.sleep(5)
else: else:
logger.error("Login-Prozess ist fehlgeschlagen. Bitte Log-Meldungen und Screenshot prüfen.") logger.error("Login-Prozess ist fehlgeschlagen.")
except Exception as e: except Exception as e:
logger.critical(f"Ein kritischer Fehler ist im Hauptprozess aufgetreten: {e}", exc_info=True) logger.critical(f"Ein kritischer Fehler ist im Hauptprozess aufgetreten.", exc_info=True)
finally: finally:
# Stellt sicher, dass der Browser immer geschlossen wird, auch bei einem Fehler.
if scraper: if scraper:
scraper.close() scraper.close()
logger.info("Login-Test beendet.") logger.info("Login-Test beendet.")