- 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 同步收尾
151 lines
6.0 KiB
Python
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())
|