"""探针: 打开活验证码页, dump 滑块 iframe 的 DOM 结构 + 元素几何, 供写自动求解器参考。 取新鲜 error_url -> 系统浏览器 CDP -> 截屏 + dump frames + 候选滑块元素 bbox。 落盘 out/captcha_dom/ """ from __future__ import annotations import json, re, subprocess, sys, time, shutil, tempfile from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) OUT = ROOT / "out" / "captcha_dom" def fresh_error_url(mobile, profile, base): cmd = [sys.executable, "-m", "tools.sms_login_cli", "--mobile", mobile, "--code", "000000", "--base-url", base, "--transport", "okhttp4-android10"] print("[probe] 取新鲜 error_url ...") p = subprocess.run(cmd, capture_output=True, text=True, cwd=str(ROOT)) out = p.stdout + p.stderr m = re.search(r'https://app\.m\.kuaishou\.com/verify/captcha\.html\?[^"\s\\]+', out) if not m: print(out[-1500:]); raise SystemExit("[probe] 未提取到 error_url") return m.group(0) def main(): from core.captcha_assist import _find_system_chromium, _free_local_port, _wait_for_cdp_endpoint from playwright.sync_api import sync_playwright import subprocess as sp OUT.mkdir(parents=True, exist_ok=True) url = fresh_error_url("17666663175", "out/sms_device_latest.json", "https://az1-api.ksapisrv.com") print("[probe] error_url =", url[:90]) browser = _find_system_chromium(); port = _free_local_port() prof = tempfile.mkdtemp(prefix="ksjsb-dom-") proc = sp.Popen([browser, f"--remote-debugging-port={port}", "--remote-debugging-address=127.0.0.1", f"--user-data-dir={prof}", "--no-first-run", "--no-default-browser-check", "--window-size=430,920", "about:blank"], stdout=sp.DEVNULL, stderr=sp.DEVNULL) try: ep = _wait_for_cdp_endpoint(port, timeout=20) with sync_playwright() as pw: b = pw.chromium.connect_over_cdp(ep, timeout=20000) page = b.contexts[0].pages[0] if b.contexts[0].pages else b.contexts[0].new_page() page.goto(url, wait_until="domcontentloaded", timeout=30000) # 等 captcha iframe 出现并加载 print("[probe] 等 iframe 加载...") time.sleep(6) dump = {"main_url": page.url, "frames": []} for i, fr in enumerate(page.frames): f = {"i": i, "url": fr.url[:160], "elements": []} if "captcha" in fr.url or "sliding" in fr.url or "verify" in fr.url: try: f["body_html_head"] = (fr.content() or "")[:2500] except Exception as e: f["body_html_head_err"] = str(e) # 找候选滑块元素 try: cand = fr.evaluate("""() => { const out = []; const sel = 'div, img, span, canvas'; document.querySelectorAll(sel).forEach(el => { const r = el.getBoundingClientRect(); const cls = el.getAttribute('class')||''; const id = el.getAttribute('id')||''; const role = el.getAttribute('data-role')||''; const sty = el.getAttribute('style')||''; const key = (cls+' '+id+' '+role).toLowerCase(); if (r.width>0 && r.height>0 && r.bottom>0 && r.right>0 && /(slide|slider|btn|track|cut|piece|drag|knob|selec)/.test(key)) { out.push({tag:el.tagName, cls, id, role, x:Math.round(r.x), y:Math.round(r.y), w:Math.round(r.width), h:Math.round(r.height), bg:(sty.match(/background[^;]*/)||[''])[0].slice(0,60)}); } }); // 也 dump 所有 img 元素(背景图/缺口图) document.querySelectorAll('img,canvas').forEach(el => { const r = el.getBoundingClientRect(); if (r.width>0 && r.height>0) out.push({tag:el.tagName, src:(el.getAttribute('src')||'').slice(0,80), x:Math.round(r.x), y:Math.round(r.y), w:Math.round(r.width), h:Math.round(r.height)}); }); return out; }""") f["elements"] = cand except Exception as e: f["elements_err"] = str(e) dump["frames"].append(f) (OUT / "dom.json").write_text(json.dumps(dump, ensure_ascii=False, indent=2), encoding="utf-8") try: page.screenshot(path=str(OUT / "page.png"), full_page=True) except Exception: pass print("[probe] done ->", OUT) # 打印候选滑块元素 for f in dump["frames"]: if f["elements"]: print(f"\n=== frame {f['i']} {f['url'][:60]} ===") if f.get("body_html_head"): print(" HTML head:\n", f["body_html_head"][:1500]) print(" 候选元素:") for e in f["elements"][:25]: print(" ", json.dumps(e, ensure_ascii=False)) finally: if proc.poll() is None: proc.terminate() try: proc.wait(timeout=3) except sp.TimeoutExpired: proc.kill() shutil.rmtree(prof, ignore_errors=True) return 0 if __name__ == "__main__": raise SystemExit(main())