Files
杨豪 cae37465d2 feat: 尺度精修上线,首次机器 PASS(live_160548 s=1.0375)
- method_l_shape: OK 候选尺度 ±0.0375 精修(步长 0.0125 + ±6px 窗口),
  GT live_165204 Δ 179→176(真人 177),离线 9/10 保持
- 插桩 JSON.stringify 捕获 SDK 日志明文;指针 probe 排除 coalesced/pressure
- 拖动不跟手指标作废:post_pos 晚于 SDK 重置滑块,鼠标事件流始终完整
- 剩余 VerifyErr = 候选整体选错(视觉确证),方向:多候选质量评估
- docs: experiments/solution/retrospective 同步收尾
2026-09-15 16:20:05 +08:00

151 lines
6.0 KiB
Python

"""单次干净尝试:刷新→采题→solve(score<=阈值才出手)→force=0.5 拖动→读 verify。
用法: python try/solo_attempt.py [wait_sec] [max_score]
频控敏感:每次尝试间隔 wait_sec(默认 600s)。
"""
import asyncio, json, sys, time, base64, random, urllib.request as ur
from pathlib import Path
import websockets
sys.path.insert(0, str(Path(__file__).resolve().parent))
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
from solve import solve
from human_drag import gen_human_path
CDP = "http://127.0.0.1:9222"
IFRAME_X, IFRAME_Y = 770, 305
SCALE = 340 / 552
HOOK = """
(() => {
window.__lastGet=null; window.__lastVerify=null;
const oOpen=XMLHttpRequest.prototype.open, oSend=XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open=function(m,u){this.__u=u;return oOpen.apply(this,arguments);};
XMLHttpRequest.prototype.send=function(){const x=this;this.addEventListener('load',()=>{
try{ if(/captcha\\/get/.test(x.__u||'')) window.__lastGet=JSON.parse(x.responseText);
if(/captcha\\/verify/.test(x.__u||'')) window.__lastVerify=x.responseText.slice(0,200);}catch(e){}});return oSend.apply(this,arguments);};
return 'ok';
})()
"""
def _json(path):
try:
with ur.urlopen(f"{CDP}{path}", timeout=5) as r:
return json.load(r)
except (OSError, ValueError):
return None
async def one_attempt(ws, mid_ref, max_score):
async def cdp(method, params=None, sid=None):
mid_ref[0] += 1
m = {"id": mid_ref[0], "method": method, "params": params or {}}
if sid:
m["sessionId"] = sid
await ws.send(json.dumps(m))
while True:
try:
r = json.loads(await ws.recv())
except (json.JSONDecodeError, asyncio.TimeoutError):
continue
if r.get("id") == mid_ref[0]:
if "error" in r:
raise RuntimeError(f"{method}: {r['error']}")
return r.get("result", {})
targets = _json("/json/list") or []
page = next((t for t in targets if t["type"] == "page" and "douyin" in t.get("url", "")), None)
ifr = next((t for t in targets if "rmc.bytedance.com" in t.get("url", "")), None)
if not page or not ifr:
print("页面或验证码 iframe 不在", flush=True)
return None
psid = (await cdp("Target.attachToTarget", {"targetId": page["id"], "flatten": True}))["sessionId"]
isid = (await cdp("Target.attachToTarget", {"targetId": ifr["id"], "flatten": True}))["sessionId"]
await cdp("Runtime.enable", sid=isid)
async def jse(expr, sid=isid):
r = await cdp("Runtime.evaluate",
{"expression": expr, "returnByValue": True, "awaitPromise": True}, sid)
return r.get("result", {}).get("value")
await jse(HOOK)
for typ in ("mousePressed", "mouseReleased"):
await cdp("Input.dispatchMouseEvent",
{"type": typ, "x": IFRAME_X + 30, "y": IFRAME_Y + 355,
"button": "left", "buttons": 0, "clickCount": 1}, psid)
g = None
t0 = time.time()
while time.time() - t0 < 8 and not g:
await asyncio.sleep(0.8)
gg = await jse("JSON.stringify(window.__lastGet)")
if gg and gg != "null":
try:
g = json.loads(gg)
except ValueError:
g = None
if not g:
print("no get", flush=True)
return None
q = g["data"]["question"]
with ur.urlopen(q["url1"]) as r:
big = r.read()
with ur.urlopen(q["url2"]) as r:
mark = r.read()
res = solve(base64.b64encode(big).decode(), base64.b64encode(mark).decode(), tip_y=q["tip_y"])
print(f"tip_y={q['tip_y']} solve={ {k: res.get(k) for k in ('x','y','scale','score','confidence')} }", flush=True)
if not res.get("ok") or res.get("confidence") != "OK" or res.get("score", 99) > max_score:
print("低质量题,跳过拖动", flush=True)
return None
dist_ui = res["x"] * SCALE
sx, sy = IFRAME_X + 54, IFRAME_Y + 307
pts = gen_human_path(dist_ui, seed=random.randint(1, 9999))
await cdp("Input.dispatchMouseEvent",
{"type": "mousePressed", "x": sx, "y": sy, "button": "left",
"buttons": 1, "clickCount": 1, "force": 0.5}, psid)
await asyncio.sleep(0.25)
cx, cy = sx, sy
for dx, dy, dt in pts:
cx += dx
cy += dy
await asyncio.sleep(dt / 1000)
await cdp("Input.dispatchMouseEvent",
{"type": "mouseMoved", "x": round(cx, 1), "y": round(cy, 1),
"button": "left", "buttons": 1, "force": 0.5}, psid)
await asyncio.sleep(0.35)
await cdp("Input.dispatchMouseEvent",
{"type": "mouseReleased", "x": round(cx, 1), "y": round(cy, 1),
"button": "left", "buttons": 1, "clickCount": 1, "force": 0.5}, psid)
await asyncio.sleep(3)
v = await jse("JSON.stringify(window.__lastVerify)")
print("verify:", (v or "null")[-90:], flush=True)
return v
async def main():
try:
wait_sec = int(sys.argv[1]) if len(sys.argv) > 1 else 600
max_score = float(sys.argv[2]) if len(sys.argv) > 2 else 4.2
except ValueError:
print("用法: python try/solo_attempt.py [wait_sec] [max_score]", file=sys.stderr)
return
ver = _json("/json/version")
if not ver or not ver.get("webSocketDebuggerUrl"):
print("无法连接 CDP", file=sys.stderr)
return
async with websockets.connect(ver["webSocketDebuggerUrl"], max_size=64 * 1024 * 1024) as ws:
mid = [0]
while True:
try:
v = await one_attempt(ws, mid, max_score)
if v and '"code":200' in v:
print("*** PASS ***", flush=True)
return
except Exception as e:
print("attempt error:", e, flush=True)
print(f"等待 {wait_sec}s(频控冷却)...", flush=True)
await asyncio.sleep(wait_sec)
if __name__ == "__main__":
asyncio.run(main())