#!/usr/bin/env python3 """Playwright-based login + scraping for STATdx (recommended for JS-heavy sites). This script launches a headless Chromium, navigates to the app, fills username/password, clicks the submit button, waits for a post-login selector, then saves the resulting HTML to disk. You may need to tweak selectors if the login form uses unusual markup or an SSO redirect. """ import os import time from loguru import logger from dotenv import load_dotenv load_dotenv() USERNAME = os.getenv("STATDX_USERNAME") PASSWORD = os.getenv("STATDX_PASSWORD") TARGET_URL = os.getenv("STATDX_TOPICS_URL", "https://app.statdx.com/") if not USERNAME or not PASSWORD: raise SystemExit("Please set STATDX_USERNAME and STATDX_PASSWORD in your environment or .env file.") # using loguru logger def run(headless=True, timeout=30000): from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch(headless=headless) context = browser.new_context() page = context.new_page() logger.info("Opening %s", TARGET_URL) page.goto(TARGET_URL, wait_until="domcontentloaded", timeout=timeout) # Heuristics to find fields — adapt per the real login form password_selector = "input[type=\"password\"]" try: page.wait_for_selector(password_selector, timeout=8000) logger.info("Password field found") except Exception: logger.warning("Password field not found quickly; continuing to try common selectors") # Try to detect username/email field by common selectors candidate_user_selectors = [ "input[type=\"email\"]", "input[name*=\"email\" i]", "input[name*=\"user\" i]", "input[type=\"text\"]", ] user_sel = None for sel in candidate_user_selectors: try: if page.query_selector(sel): user_sel = sel break except Exception: continue if not user_sel: logger.warning("Could not auto-detect username field; you may need to edit selectors in this script.") # fallback: pick first visible text input inputs = page.query_selector_all("input[type=\"text\"]") if inputs: user_sel = "input[type=\"text\"]" logger.info("Using username selector: %s", user_sel) if user_sel: page.fill(user_sel, USERNAME) else: logger.error("No username field found. Aborting.") browser.close() return # Fill password if page.query_selector(password_selector): page.fill(password_selector, PASSWORD) else: logger.error("No password field found. Aborting.") browser.close() return # Try to click a submit button submit_selectors = [ "button[type=submit]", "input[type=submit]", "button:has-text(\"Sign in\")", "button:has-text(\"Sign In\")", "button:has-text(\"Log in\")", "button:has-text(\"Log In\")", ] clicked = False for s in submit_selectors: try: btn = page.query_selector(s) if btn: btn.click() clicked = True logger.info("Clicked submit via selector %s", s) break except Exception: continue if not clicked: logger.warning("No submit button clicked; attempting to press Enter on password field") page.press(password_selector, "Enter") # Wait for navigation or the app root element that indicates successful login try: page.wait_for_selector("#ds-app", timeout=20000) logger.info("#ds-app found — likely logged in and app rendered") except Exception: logger.info("Timed out waiting for #ds-app — page content may still be loading or login failed.") # Optional: wait a bit for client-side rendering time.sleep(2) content = page.content() out_file = "topics_playwright.html" with open(out_file, "w", encoding="utf-8") as f: f.write(content) logger.info("Saved rendered HTML to %s", out_file) browser.close() if __name__ == "__main__": run(headless=True)