优化验证码展示
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user