#!/usr/bin/env python3 """ Autofill-Script fuer das Kontaktformular (React / react-aria, z.B. ImmoScout24). Fuellt Anrede, Vorname, Nachname, E-Mail, Telefon und Nachricht mit Mock-Daten. Weil es ein React-Formular ist, werden echte Tastatur-Events via Selenium gesendet (send_keys) - nur so uebernimmt React die Werte. Die Feld-IDs sind dynamisch und aendern sich bei jedem Laden. Deshalb wird ueber die stabilen name-Attribute selektiert. Felder kommen doppelt vor (zwei gerenderte Varianten) -> es werden nur die SICHTBAREN gefuellt. Setup: pip install selenium Benutzung: python autofill_kontakt.py "https://www.immobilienscout24.de/..../kontakt" # oder lokal (nur zum Ansehen, React laeuft dann nicht): python autofill_kontakt.py """ import sys import time from selenium import webdriver from selenium.webdriver.common.by import By # --------------------------------------------------------------------------- # Werte kommen aus config.json (siehe config.py) # --------------------------------------------------------------------------- import config as _config # Kopie, damit die App pro Lauf z.B. die E-Mail ueberschreiben kann, # ohne die config.json zu veraendern. MOCK = dict(_config.CFG["contact"]) def reload_mock(): """Uebernimmt geaenderte config.json-Werte in MOCK.""" MOCK.clear() MOCK.update(_config.reload()["contact"]) return MOCK # --------------------------------------------------------------------------- # Hilfsfunktionen (arbeiten nur auf sichtbaren Elementen) # --------------------------------------------------------------------------- def visible(driver, css): """Alle sichtbaren Elemente zu einem CSS-Selektor.""" els = driver.find_elements(By.CSS_SELECTOR, css) return [e for e in els if e.is_displayed() and e.is_enabled()] REACT_SET_JS = """ const el = arguments[0], val = arguments[1]; const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype; const setter = Object.getOwnPropertyDescriptor(proto, 'value').set; el.focus(); setter.call(el, ''); // erst leeren el.dispatchEvent(new Event('input', {bubbles: true})); setter.call(el, val); // dann Wert setzen el.dispatchEvent(new Event('input', {bubbles: true})); // React onChange el.dispatchEvent(new Event('change', {bubbles: true})); el.dispatchEvent(new Event('blur', {bubbles: true})); // Validierung """ def type_into(driver, el, value, label=""): """React-sicher setzen: nativer value-Setter + input-Event (kein Tippen).""" try: driver.execute_script("arguments[0].scrollIntoView({block:'center'});", el) driver.execute_script(REACT_SET_JS, el, value) print(f" [ok] {label or el.get_attribute('name')} = {value}") except Exception as e: print(f" [skip] {label}: {e.__class__.__name__}") def fill_by_name(driver, name, value): """Fuellt NUR das erste sichtbare Feld dieses Namens (verhindert Dopplung).""" els = visible(driver, f'input[name="{name}"], textarea[name="{name}"]') if els: type_into(driver, els[0], value, label=name) else: print(f" [skip] {name}: kein sichtbares Feld") def choose_salutation(driver, value): # react-aria Radios sind optisch versteckt (is_displayed==False) -> NICHT filtern. els = driver.find_elements( By.CSS_SELECTOR, f'input[name="salutation"][value="{value}"]') for el in els: try: driver.execute_script("arguments[0].scrollIntoView({block:'center'});", el) # zugehoeriges Label klicken (react-aria reagiert darauf); sonst Input selbst driver.execute_script(""" const el = arguments[0]; const lbl = el.closest('label') || (el.id && document.querySelector('label[for="'+el.id+'"]')); (lbl || el).click(); el.checked = true; el.dispatchEvent(new Event('click', {bubbles: true})); el.dispatchEvent(new Event('change', {bubbles: true})); """, el) label = {"mr": "Herr", "mrs": "Frau"}.get(value, value) print(f" [ok] Anrede = {label}") return except Exception as e: print(f" [skip] Anrede: {e.__class__.__name__}") print(" [skip] Anrede: kein Radio gefunden") def fill_phone(driver, value): """Telefon: sichtbares tel-Feld (autocomplete='tel', ohne name).""" for el in visible(driver, 'input[autocomplete="tel"]'): type_into(driver, el, value, label="phone") return # Fallback: irgendein Feld mit inputmode text, das nicht schon belegt ist print(" [info] kein dediziertes Telefon-Feld gefunden") def click_cookie_banner(driver): """Cookie-Zustimmung wegklicken - auch in iframes (best effort).""" texts = ["Alle akzeptieren", "Akzeptieren", "Alle zulassen", "Zustimmen", "Einverstanden", "Accept all", "Accept"] def try_click(ctx): for txt in texts: try: btn = ctx.find_element( By.XPATH, f"//button[contains(normalize-space(.), '{txt}')]") if btn.is_displayed(): btn.click() return True except Exception: pass return False if try_click(driver): time.sleep(0.8) return # Consent liegt oft in einem iframe for frame in driver.find_elements(By.TAG_NAME, "iframe"): try: driver.switch_to.frame(frame) ok = try_click(driver) except Exception: ok = False finally: driver.switch_to.default_content() if ok: time.sleep(0.8) return def open_contact_form(driver): """Versucht, ueber einen Button das Kontaktformular zu oeffnen.""" labels = ["Nachricht", "Kontakt", "Anfrage", "Kontaktieren", "Anbieter kontaktieren", "Nachricht senden", "Senden"] for lbl in labels: try: btn = driver.find_element( By.XPATH, f"//button[contains(normalize-space(.), '{lbl}')] " f"| //a[contains(normalize-space(.), '{lbl}')]") if btn.is_displayed(): driver.execute_script("arguments[0].scrollIntoView({block:'center'});", btn) btn.click() print(f" Kontakt-Button geklickt: '{lbl}'") time.sleep(2.0) if visible(driver, 'input[name="email"]'): return except Exception: pass # --------------------------------------------------------------------------- def main(): url = sys.argv[1] if len(sys.argv) > 1 else \ "file:///Users/timu/programming/momentrum/projekte/ibo/index2.html" options = webdriver.ChromeOptions() # options.add_argument("--headless=new") driver = webdriver.Chrome(options=options) try: print(f"Oeffne {url}") driver.get(url) time.sleep(2.0) # React-Bootstrap abwarten # Cookie-Banner wegklicken (best effort, auch in iframes) click_cookie_banner(driver) # Kontaktformular ggf. erst per Button oeffnen if not visible(driver, 'input[name="email"]'): print("E-Mail-Feld noch nicht sichtbar -> versuche Kontakt-Button ...") open_contact_form(driver) # Aktiv auf das E-Mail-Feld warten (bis 25s) print("Warte auf Kontaktformular ...") deadline = 25 while deadline > 0 and not visible(driver, 'input[name="email"]'): time.sleep(1.0) deadline -= 1 found = visible(driver, 'input[name="email"]') print(f"Sichtbare E-Mail-Felder gefunden: {len(found)}") if not found: print("!! Kein Kontaktformular sichtbar. Ist es evtl. in einem iframe") print(" oder hinter einem weiteren Klick? Fenster bleibt offen -") print(" oeffne das Formular manuell und starte das Script erneut,") print(" ODER sag mir, wie man das Formular auf der Seite oeffnet.") print("Anrede:") choose_salutation(driver, MOCK["salutation"]) print("Textfelder:") fill_by_name(driver, "firstName", MOCK["firstName"]) fill_by_name(driver, "lastName", MOCK["lastName"]) fill_by_name(driver, "email", MOCK["email"]) print("Telefon:") fill_phone(driver, MOCK["phone"]) print("Nachricht:") fill_by_name(driver, "message", MOCK["message"]) print("\nFertig. Formular ausgefuellt (nicht abgesendet).") print("Fenster bleibt offen. Mit Strg+C beenden.") while True: time.sleep(3600) except KeyboardInterrupt: print("\nBeende ...") driver.quit() if __name__ == "__main__": main()