优化验证码展示

This commit is contained in:
yml2213
2026-09-06 13:02:37 +08:00
parent 8309072120
commit 4f9a6864eb
7 changed files with 972 additions and 30 deletions
+189
View File
@@ -0,0 +1,189 @@
"""虎牙验证码数据采集与分析脚本。
功能:
1. 【URL 采集模式】直接传入 safe_auth URL(dx_auth 点选 / pt_auth 滑块),
拉取验证码图片、提示语与元数据并落盘保存;
2. 【登录探测模式】输入账号密码触发风控,捕获验证码全套图片与提示语。
用法示例:
# 模式一:直接解析已有风控 URL
python scripts/capture_huya_captcha.py url "https://aq.huya.com/p/safe_auth/dx_auth.html?param=..."
# 模式二:仅拉取图片和提示语,不向服务端提交答案
python scripts/capture_huya_captcha.py url "https://aq.huya.com/p/safe_auth/dx_auth.html?param=..." --dump-only
# 模式三:登录触发捕获
python scripts/capture_huya_captcha.py login <账号> <密码> --channel app
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from urllib.parse import parse_qs, urlparse
from loguru import logger
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from core.huya.verification.capture import DEFAULT_CAPTURE_DIR
from core.huya.verification.solver import (
HuyaQrAuthRequiredError,
HuyaSmsAuthRequiredError,
HuyaVerificationSolver,
)
def capture_from_url(
url: str,
*,
dump_only: bool = False,
account: str = "",
proxies: dict | None = None,
) -> None:
"""从 safe_auth URL 抓取并保存验证码数据。"""
logger.info(f"开始从 URL 采集验证码: {url[:100]}...")
q = parse_qs(urlparse(url).query)
app_id = q.get("appId", ["5002"])[0]
solver = HuyaVerificationSolver(
app_id=app_id,
page_url=url,
account=account,
proxies=proxies,
enable_capture=True,
)
solver._parse_strategy(url)
if dump_only:
# 只拉取 config3 和 gurl,不提交 vurl
config_payload = solver._build_common_payload({"cTuIndex": 0})
config = solver._request_json(
"post",
"https://udbrtt.huya.com/auth/client/config3",
json=config_payload,
)
data = config.get("data") or {}
gurl = str(data.get("gurl") or "")
dx_js = str(data.get("js") or "")
if not gurl:
logger.error(f"config3 未下发 gurl: {config}")
return
if dx_js:
solver._compile_js(dx_js)
point_payload = solver._build_common_payload({"ver": 5})
point = solver._get_track_point(gurl, point_payload)
logger.info(f"【仅采集模式】验证码解析完成,识别结果: {point}")
if solver.current_capture and solver.current_capture.target_dir:
logger.info(f"验证码数据已成功保存至: {solver.current_capture.target_dir}")
return
# 完整求解并记录提交结果
try:
result = solver.solve(url)
logger.info(f"验证码提交完成: authId={result.get('authId')}")
except HuyaQrAuthRequiredError as exc:
logger.warning(f"该 URL 属于扫码验证: {exc}")
except HuyaSmsAuthRequiredError as exc:
logger.warning(f"该 URL 触发短信二次验证: {exc}")
except Exception as exc: # noqa: BLE001
logger.error(f"验证码处理异常: {exc}")
def capture_from_login(
account: str,
password: str,
channel: str = "app",
proxies: dict | None = None,
) -> None:
"""通过尝试登录触发风控并自动落盘验证码。"""
logger.info(f"尝试登录账号 {account} (渠道: {channel}) 探测验证码...")
if channel == "app":
from core.huya.app_login import login_huya_app_password
res = login_huya_app_password(account, password, proxies=proxies)
logger.info(
f"登录结果: success={res.success}, code={res.code}, msg={res.message}"
)
else:
from core.huya.login import login_huya_password
res = login_huya_password(account, password, proxies=proxies)
logger.info(
f"登录结果: success={res.success}, code={res.code}, msg={res.message}"
)
def list_recent_captures() -> None:
"""列出最近采集到的验证码。"""
if not DEFAULT_CAPTURE_DIR.exists():
print("暂无捕获数据目录: ", DEFAULT_CAPTURE_DIR)
return
folders = sorted(
DEFAULT_CAPTURE_DIR.glob("*"), key=lambda p: p.stat().st_mtime, reverse=True
)
if not folders:
print("当前尚未捕获任何验证码数据。")
return
print(f"\n找到 {len(folders)} 条捕获记录 (位于 {DEFAULT_CAPTURE_DIR}):\n")
for f in folders[:10]:
meta_file = f / "meta.json"
if meta_file.exists():
try:
meta = json.loads(meta_file.read_text(encoding="utf-8"))
auth_name = meta.get("auth_name", "未知")
prompts = meta.get("prompts", {})
question = (
prompts.get("question") or prompts.get("default_instruction") or ""
)
files = [p.name for p in f.glob("*") if p.name != "meta.json"]
print(f"📁 {f.name}")
print(f" 类型: {auth_name} | 提示语: {question}")
print(f" 文件: {', '.join(files)}")
except Exception: # noqa: BLE001
print(f"📁 {f.name}")
else:
print(f"📁 {f.name}")
def main() -> int:
parser = argparse.ArgumentParser(description="虎牙验证码采集与测试工具")
subparsers = parser.add_subparsers(dest="cmd")
url_parser = subparsers.add_parser("url", help="从 safe_auth URL 抓取并落盘验证码")
url_parser.add_argument("url", help="safe_auth 完整 URL")
url_parser.add_argument("--account", default="", help="关联账号名 (可选)")
url_parser.add_argument(
"--dump-only",
action="store_true",
help="仅下载图片与提示语,不向服务器提交答案",
)
login_parser = subparsers.add_parser("login", help="通过密码登录触发验证码")
login_parser.add_argument("account", help="虎牙账号或手机号")
login_parser.add_argument("password", help="密码")
login_parser.add_argument(
"--channel", choices=["app", "web"], default="app", help="登录渠道 (默认 app)"
)
subparsers.add_parser("list", help="查看最近采集到的验证码记录")
args = parser.parse_args()
if args.cmd == "url":
capture_from_url(args.url, dump_only=args.dump_only, account=args.account)
elif args.cmd == "login":
capture_from_login(args.account, args.password, channel=args.channel)
elif args.cmd == "list":
list_recent_captures()
else:
parser.print_help()
return 0
if __name__ == "__main__":
sys.exit(main())