#!/usr/bin/env python # -*- coding: utf-8 -*- """ 传输指纹单变量实验: errorCode=12 是否由 HTTP/TLS 指纹决定? 依据: AGENTS.md §2.1 (用服务端响应画生死线) / §5.6 (先怀疑自己的工具) / ANALYSIS.md §7.6。 已确证的事实 (2026-10-11): * 同一 aid (1ms 195672344) + 同一套腾讯云登录 cookie + 同一 prehandle 参数下, **真实浏览器** (har/签到成功-1.har, 点选.har) 对 cap_union_new_verify 只得到 50 / 9 / 0 —— **从不 12**; * 本客户端 (httpx, HTTP/1.1 + OpenSSL) 在不同 cookie / IP / 时序 / 后端下**恒为 12**; * 答案正确 (OCR 后验 1.0)、XTEA 密钥为实时构建真密钥 → 12 与载荷内容无关。 → 剩下唯一被测到的差异: **"谁在发这个 HTTP"** (JA3/JA4 + HTTP/2 + 头序 + 头大小写)。 本脚本**只改传输层**, 其余 (cookie / 参数 / 题型 / 求解器) 完全相同: A) transport=httpx (基线) B) transport=curl (curl_cffi impersonate=chrome) 判读: A=12 且 B=0 -> 12 = 传输指纹问题; 纯算流程只需换传输层即可通过 A=12 且 B=12 -> 传输指纹也不够, 需回到"真实浏览器页面上下文"(H1/H2) 或 collect 内容 用法: .venv/bin/python scripts/experiment_transport_ab.py --both \ --uin 100012108023 --skey --csrf --intl 1 """ from __future__ import annotations import argparse import json import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[1] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from tcaptcha.client import KIND_TEXT_CLICK, TencentCaptchaClient # noqa: E402 TRANSPORTS = {"a": ("httpx", None), "b": ("curl", "chrome")} def run_once(tag: str, transport: str, impersonate, cookies, args) -> dict: client = TencentCaptchaClient( aid=args.aid, entry_url=args.entry_url, referer=args.referer, wb=args.wb, accept_kinds=(KIND_TEXT_CLICK,), telemetry_mode=args.telemetry, proxy=args.proxy, cookies=cookies, transport=transport, impersonate=impersonate, ) try: result = client.solve(max_retries=args.retries, max_refreshes=args.refreshes) finally: client.close() raw = result.get("raw_response") or {} print(f"\n[{tag}] transport={transport} impersonate={impersonate} " f"success={result['success']} errorCode={raw.get('errorCode')} " f"ticket={(raw.get('ticket') or '')[:24]}") if result.get("error"): print(f" error={result['error']}") return result def main() -> int: ap = argparse.ArgumentParser(description="传输指纹单变量实验 (httpx vs curl_cffi)") ap.add_argument("--aid", default="195672344") ap.add_argument("--entry-url", default="https://1ms.run/console/personal/benefits/check-in") ap.add_argument("--referer", default="https://1ms.run/") ap.add_argument("--wb", default="1") ap.add_argument("--retries", type=int, default=3) ap.add_argument("--refreshes", type=int, default=20, help="换题上限 (1ms 会随机下发滑块/图标/文字)") ap.add_argument("--telemetry", default="python_keyed", choices=["python_keyed", "python"]) ap.add_argument("--proxy", default=None) ap.add_argument("--uin", default="") ap.add_argument("--skey", default="") ap.add_argument("--csrf", dest="csrf", default="", help="qcmainCSRFToken") ap.add_argument("--intl", default="1") ap.add_argument("--both", action="store_true", help="先跑 A(httpx) 再跑 B(curl)") ap.add_argument("--only", choices=["a", "b"], default="b") args = ap.parse_args() cookies = {k: v for k, v in { "uin": args.uin, "skey": args.skey, "qcmainCSRFToken": args.csrf, "intl": args.intl, }.items() if v} print(f"[*] cookie 数量={len(cookies)} 代理={args.proxy} telemetry={args.telemetry}") plan = ["a", "b"] if args.both else [args.only] summary = {} for which in plan: transport, impersonate = TRANSPORTS[which] tag = f"{which.upper()} {transport}" r = run_once(tag, transport, impersonate, cookies or None, args) summary[which] = str((r.get("raw_response") or {}).get("errorCode")) print("\n=== 判读 ===") print(json.dumps({"A_httpx": summary.get("a"), "B_curl_chrome": summary.get("b")}, ensure_ascii=False)) if args.both: a, b = summary.get("a"), summary.get("b") if a == "12" and b == "0": print("=> 传输指纹是 12 的自变量: 换 curl_cffi(Chrome 指纹) 即可通过") elif a == "12" and b == "12": print("=> 传输指纹不足: 回到 H1/H2(真实浏览器页面上下文) 或 collect 内容") else: print("=> 结果非典型, 需人工判读") return 0 if __name__ == "__main__": raise SystemExit(main())