diff --git a/scrape_fotograf.py b/scrape_fotograf.py index 73cab2c0..1bdc0332 100644 --- a/scrape_fotograf.py +++ b/scrape_fotograf.py @@ -17,17 +17,19 @@ OUTPUT_FILE = os.path.join(OUTPUT_DIR, 'nutzer_ohne_logins.csv') LOGIN_URL = 'https://app.fotograf.de/login/login' # --- Selektoren (zentral verwaltet für einfache Anpassung) --- -# FINALE, KORREKTE VERSION mit Ihren IDs +# GEÄNDERT: Robuste Selektoren für die Alben-Übersicht SELECTORS = { "cookie_accept_button": "#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll", "login_user": "#login-email", "login_pass": "#login-password", "login_button": "#login-submit", "job_name": "h1", - "album_rows": "div.table-row-group > div.table-row", - "album_link": "a.text-legacy-azure-200", - "child_name": "div:nth-of-type(4) strong", - "login_count": "div:nth-of-type(6) strong", + # NEU: Dieser Selector findet jede div-Zeile, die einen Link zu einer Galerie enthält. Das ist sehr robust. + "album_rows": "div:has(a[href*='/config_jobs_photos/gallery/'])", + # NEU: Der Link selbst, um ihn innerhalb der Zeile zu finden. + "album_link": "a[href*='/config_jobs_photos/gallery/']", + # NEU: Die Spalte "Zugänge" (Logins). Es ist die 7. Spalte in der Grid-Ansicht. + "login_count": "div:nth-child(7)", "buyer_link": "a.block:has(span:contains('Käufer'))", "buyer_email": "div.flex:nth-of-type(4) span" } @@ -64,10 +66,8 @@ def load_all_credentials(): with open(CREDENTIALS_FILE, 'r') as f: return json.load(f) except FileNotFoundError: - print(f"Fehler: {CREDENTIALS_FILE} nicht gefunden.") return None except json.JSONDecodeError: - print(f"Fehler: {CREDENTIALS_FILE} ist keine gültige JSON-Datei.") return None def login(driver, username, password): @@ -75,7 +75,6 @@ def login(driver, username, password): try: driver.get(LOGIN_URL) wait = WebDriverWait(driver, 10) - try: print("Suche nach Cookie-Banner...") cookie_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, SELECTORS["cookie_accept_button"]))) @@ -84,14 +83,11 @@ def login(driver, username, password): time.sleep(1) except TimeoutException: print("Kein Cookie-Banner gefunden, fahre fort.") - print("Fülle Anmeldeformular aus...") wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, SELECTORS["login_user"]))).send_keys(username) driver.find_element(By.CSS_SELECTOR, SELECTORS["login_pass"]).send_keys(password) - print("Klicke auf Login...") driver.find_element(By.CSS_SELECTOR, SELECTORS["login_button"]).click() - print("Warte auf die nächste Seite...") wait.until(EC.url_contains('/config_dashboard/index')) print("Login erfolgreich!") @@ -105,6 +101,7 @@ def login(driver, username, password): take_error_screenshot(driver, "login_unexpected") return False +# GEÄNDERT: Logik an die neue Seitenstruktur angepasst def process_job(driver, job_url): print(f"\nVerarbeite Job-URL: {job_url}") job_id = job_url.split('/')[-1] @@ -127,21 +124,32 @@ def process_job(driver, job_url): albums_to_process = [] try: + # Warten, bis die Album-Zeilen geladen sind (mit dem neuen, robusten Selector) album_rows = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, SELECTORS["album_rows"]))) print(f"{len(album_rows)} Alben gefunden. Prüfe auf Logins...") for row in album_rows: try: - if int(row.find_element(By.CSS_SELECTOR, SELECTORS["login_count"]).text) == 0: - child_name = row.find_element(By.CSS_SELECTOR, SELECTORS["child_name"]).text - album_link = row.find_element(By.CSS_SELECTOR, SELECTORS["album_link"]).get_attribute('href') - albums_to_process.append({"child_name": child_name, "album_detail_url": album_link}) - print(f" -> Gefunden: Kind '{child_name}' mit 0 Logins.") - except (NoSuchElementException, ValueError): + # Finde die Login-Anzahl in der 7. Spalte der Zeile + login_count_text = row.find_element(By.CSS_SELECTOR, SELECTORS["login_count"]).text + if int(login_count_text) == 0: + # Finde den Link innerhalb der Zeile + album_link_element = row.find_element(By.CSS_SELECTOR, SELECTORS["album_link"]) + child_name = album_link_element.text + album_link = album_link_element.get_attribute('href') + + albums_to_process.append({ + "child_name": child_name, + "album_detail_url": album_link + }) + print(f" -> Gefunden: Album '{child_name}' mit 0 Logins.") + + except (NoSuchElementException, ValueError) as e: + # Ignorieren, falls eine Zeile nicht dem Format entspricht pass except TimeoutException: - print("Keine Alben auf der Seite gefunden.") + print("Keine Alben auf der Seite gefunden oder Timeout beim Warten.") take_error_screenshot(driver, "album_list_timeout") return [] @@ -151,17 +159,13 @@ def process_job(driver, job_url): try: print(f" Rufe Detailseite für '{album['child_name']}' auf...") driver.get(album["album_detail_url"]) - buyer_link_element = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, SELECTORS["buyer_link"]))) buyer_name = buyer_link_element.text.replace('Käufer ', '').strip() buyer_page_url = buyer_link_element.get_attribute('href') - print(f" Käufer gefunden: '{buyer_name}'. Rufe Käuferseite auf...") driver.get(buyer_page_url) - buyer_email = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, SELECTORS["buyer_email"]))).text print(f" E-Mail gefunden: {buyer_email}") - results.append({ "Auftragsname": job_name, "Kind Vorname": album["child_name"], @@ -169,7 +173,6 @@ def process_job(driver, job_url): "Käufer E-Mail": buyer_email, }) time.sleep(1) - except TimeoutException: print(f" Fehler: Timeout bei '{album['child_name']}'.") take_error_screenshot(driver, f"detail_page_timeout_{album['child_name']}")