dealfront_enrichment.py aktualisiert
This commit is contained in:
@@ -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.")
|
||||||
Reference in New Issue
Block a user