From 795235ac38896b71ecbb3a76db21eabe8db63f36 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 11 Oct 2026 21:36:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=BA=AF=E7=AE=97):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=20captcha=20=E5=9F=9F=20cookie=20=E9=80=8F=E4=BC=A0=20+=20H3?= =?UTF-8?q?=20=E5=8D=95=E5=8F=98=E9=87=8F=E5=AE=9E=E9=AA=8C=E8=84=9A?= =?UTF-8?q?=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 做了什么: - TencentCaptchaClient 新增可选 cookies 参数, 逐条写入 captcha 域 Cookie 头 (默认 None, 行为不变) - OneMsAdapter 新增 captcha_cookies 透传 - 新增 scripts/experiment_h3_cookie_ab.py: 只改 cookie 一个变量做 A/B 对照并自动判读 为什么: - 账本 §7.5 的 H3 (腾讯云登录 cookie uin/skey/qcmainCSRFToken/intl) 是唯一 尚未单变量验证过的差异; 其余 (环境 / 后端 / 出口 IP / 静默时长) 均已排除 怎么验证: - 离线: 带 cookie 时请求头为 Cookie=uin=..;intl=1, 不带时为 None (默认行为不变) - .venv/bin/python -m tcaptcha.selftest 全绿 (crypto_core + glyph 8/8 + xtea_key) --- scripts/experiment_h3_cookie_ab.py | 113 +++++++++++++++++++++++++++++ tcaptcha/client.py | 33 ++++++--- tcaptcha/one_ms_adapter.py | 6 +- 3 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 scripts/experiment_h3_cookie_ab.py diff --git a/scripts/experiment_h3_cookie_ab.py b/scripts/experiment_h3_cookie_ab.py new file mode 100644 index 0000000..17bff6d --- /dev/null +++ b/scripts/experiment_h3_cookie_ab.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +H3 单变量实验: captcha 域「带 / 不带」腾讯云登录 cookie 对 errorCode=12 的影响 + +依据: AGENTS.md §2.1 (用服务端响应画生死线) 与 har/1ms_dianxuan/ANALYSIS.md §7.5。 + +背景 (已确证): 12 与遥测后端 / 出口 IP / 静默时长均无关。成功样本 +(har/签到成功-1.har) 的 captcha 域 prehandle 带着腾讯云登录 cookie +(uin / skey / qcmainCSRFToken / intl), 而本客户端此前完全不带 —— 这是唯一 +未被单变量验证过的差异 (H3)。 + +本脚本**只改一个变量 (cookie)**, 其余参数完全相同, 依次跑两次: + A) 不带 cookie B) 带 cookie +判读: + A=12 且 B=0 -> H3 成立 (纯算流程只需补上该 cookie) + A=12 且 B=12 -> H3 排除, 转 H1/H2 (真实验证码页面上下文 / 自动化检测) + +用法: + .venv/bin/python scripts/experiment_h3_cookie_ab.py --aid 195672344 \ + --entry-url https://1ms.run/console/personal/benefits/check-in \ + --uin 100012108023 --skey xxxxxxxx --csrf xxxxxxxx --intl 1 \ + [--referer https://1ms.run/] [--proxy http://user:pass@host:port] + +cookie 获取: 浏览器 F12 -> Application -> Cookies -> https://turing.captcha.qcloud.com/ +""" +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 + + +def run_once(tag: str, 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, + ) + try: + result = client.solve(max_retries=args.retries, max_refreshes=8) + finally: + client.close() + raw = result.get("raw_response") or {} + print(f"\n[{tag}] success={result['success']} " + f"errorCode={raw.get('errorCode')} errMessage={raw.get('errMessage', '')!r}") + print(f" error={result.get('error', '')}") + return result + + +def main() -> int: + ap = argparse.ArgumentParser(description="H3: captcha 域 cookie 单变量实验") + 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("--telemetry", default="python_keyed", choices=["python_keyed", "python"]) + ap.add_argument("--proxy", default=None) + # 腾讯云登录 cookie (从浏览器取) + 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(不带) 再跑 B(带); 默认只跑 A") + ap.add_argument("--only", choices=["a", "b"], default="a", + help="只跑 A(不带 cookie) 或 B(带 cookie)") + 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} + if args.only == "b" or args.both: + if not cookies: + print("[-] 跑 B 必须提供 uin/skey/csrf 等 cookie", file=sys.stderr) + return 2 + + plan = ["a", "b"] if args.both else [args.only] + summary = {} + for which in plan: + if which == "a": + r = run_once("A 不带 cookie", None, args) + else: + r = run_once("B 带 cookie ", cookies, args) + summary[which] = (r.get("raw_response") or {}).get("errorCode") + + print("\n=== 判读 ===") + print(json.dumps({"A_no_cookie": summary.get("a"), "B_with_cookie": summary.get("b")}, + ensure_ascii=False)) + if args.both: + a, b = summary.get("a"), summary.get("b") + if str(a) == "12" and str(b) == "0": + print("=> H3 成立: 补上 captcha 域 cookie 即可通过") + elif str(a) == "12" and str(b) == "12": + print("=> H3 排除: 转 H1/H2 (真实验证码页面上下文 / 自动化检测)") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tcaptcha/client.py b/tcaptcha/client.py index 198679a..f50209c 100644 --- a/tcaptcha/client.py +++ b/tcaptcha/client.py @@ -108,6 +108,7 @@ class TencentCaptchaClient: inter_attempt_delay: float = 0.25, telemetry_mode: str = "python_keyed", proxy: Optional[str] = None, + cookies: Optional[dict] = None, ): """ telemetry_mode 决定 collect/eks 怎么来 (见 tcaptcha.crypto.generate_telemetry), 只走纯算: @@ -123,6 +124,11 @@ class TencentCaptchaClient: 9 分钟静默后单发, **全部都是 12**。所以 12 不是遥测/环境/IP 问题, 不要再走"补环境"或"换 IP"的路; 真正的变量 (登录态 / 真实验证码页面上下文) 尚未被单变量实验区分。 + + cookies: 可选, 逐条写入 captcha 域 (turing.captcha.qcloud.com) 的 Cookie 头。 + 成功样本 (har/签到成功-1.har) 的 captcha 域请求带着腾讯云登录 cookie + (uin / skey / qcmainCSRFToken / intl), 而本客户端此前完全不带 —— + 这是账本 §7.5 的 H3 单变量实验入口 (默认 None, 不改变原有行为)。 """ self.aid = str(aid) self.timeout = timeout @@ -146,21 +152,26 @@ class TencentCaptchaClient: self._text_solver = text_solver self.subsid = 1 + headers = { + "User-Agent": self.user_agent, + "Referer": self.referer, + "Accept-Language": "zh-CN,zh;q=0.9,fr;q=0.8,de;q=0.7,en;q=0.6", + "sec-ch-ua": '"Google Chrome";v="155", "Chromium";v="155", "Not(A:Brand";v="24"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"macOS"', + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "cross-site", + } + # 可选: 透传 captcha 域的腾讯云登录 cookie (账本 §7.5 的 H3 单变量实验入口) + if cookies: + headers["Cookie"] = "; ".join(f"{k}={v}" for k, v in cookies.items()) + self.http_client = httpx.Client( base_url=self.BASE_URL, trust_env=False, # 屏蔽环境代理干扰 proxy=self.proxy, - headers={ - "User-Agent": self.user_agent, - "Referer": self.referer, - "Accept-Language": "zh-CN,zh;q=0.9,fr;q=0.8,de;q=0.7,en;q=0.6", - "sec-ch-ua": '"Google Chrome";v="155", "Chromium";v="155", "Not(A:Brand";v="24"', - "sec-ch-ua-mobile": "?0", - "sec-ch-ua-platform": '"macOS"', - "Sec-Fetch-Dest": "empty", - "Sec-Fetch-Mode": "cors", - "Sec-Fetch-Site": "cross-site", - }, + headers=headers, timeout=self.timeout, follow_redirects=True, ) diff --git a/tcaptcha/one_ms_adapter.py b/tcaptcha/one_ms_adapter.py index 1d95c47..234173f 100644 --- a/tcaptcha/one_ms_adapter.py +++ b/tcaptcha/one_ms_adapter.py @@ -55,13 +55,16 @@ class OneMsAdapter: def __init__(self, cookie: Optional[str] = None, user_agent: str = DEFAULT_USER_AGENT, timeout: float = 15.0, proxy: Optional[str] = None, - proxy_api: Optional[str] = None, telemetry_mode: str = "python_keyed"): + proxy_api: Optional[str] = None, telemetry_mode: str = "python_keyed", + captcha_cookies: Optional[Dict[str, str]] = None): self.user_agent = user_agent self.timeout = timeout # 出口代理 (可选)。注意: 实测换出口 IP 并不能解决 errorCode=12 (见 ANALYSIS.md §7) self.proxy = proxy self.proxy_api = proxy_api self.telemetry_mode = telemetry_mode + # 可选: 透传到 captcha 域的腾讯云登录 cookie (账本 §7.5 的 H3 实验入口) + self.captcha_cookies = captcha_cookies headers = { "User-Agent": user_agent, "Origin": ONE_MS_BASE, @@ -130,6 +133,7 @@ class OneMsAdapter: accept_kinds=(KIND_TEXT_CLICK,), proxy=proxy, telemetry_mode=self.telemetry_mode, + cookies=self.captcha_cookies, ) connectivity_error = False try: