115 lines
5.7 KiB
Python
115 lines
5.7 KiB
Python
"""探针: 打开活验证码页, 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())
|