Files
douyin-captcha/try/live_solve.py
T
杨豪 6902be04d4 feat(src): 交付物收敛为单文件 captcha_solver.py 并新增第三方对接文档
- 合并 matcher_core + solve 为自包含单文件 captcha_solver.py,
  对外仅暴露 solve_slide()(base64 进、JSON 出),内部实现细节不外泄
- 新增 README_3rd.md:接入步骤、返回字段、像素换算、鼠标操作规范、
  轨迹形态要求、踩坑清单与验收基线
- 删除内部 CLI 批量工具与调试字段(candidates/method),
  返回清洗后的 quality/alternatives
- try/live_solve.py 改用新模块路径;docs 同步更新引用
2026-09-16 11:44:35 +08:00

70 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""线上验证码求解(带服务端 tip_y 约束)。
服务端 captcha/get 响应中的 tip_y × 2 = 缺口 y 坐标(mark 画布顶)。
经 10 组线上样本校准:solver 无约束 y 与 2×tip_y 差 ≤4px(定位准的样本),
偏差大的样本(AMBIGUOUS)正是被服务端判 VerifyErr 的。
用 tip_y 约束 y 后在 score map 上重选 x,可显著提高准确率。
用法:
.venv/bin/python try/live_solve.py <big.jpeg> <mark.png> <tip_y>
输出 JSON: x(拖动目标, 原图px)、score、scale
"""
import json
import sys
from pathlib import Path
import cv2
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
import captcha_solver as L # noqa: E402
def live_solve(big_path, mark_path, tip_y, y_tol=8):
"""tip_y 约束版求解。返回 {x, y, scale, score} 或 None。"""
try:
target_y = int(tip_y) * 2
except (TypeError, ValueError):
return None
big = cv2.imread(big_path)
mark = cv2.imread(mark_path, cv2.IMREAD_UNCHANGED)
if big is None or mark is None:
return None
try:
d = L.setup(big, mark)
if d is None:
return None
best = []
for s in L.SCALES:
s = round(float(s), 3)
score, w, h, ptset = L.score_at_scale(d, s)
if score is None:
continue
for v, x, y in L.nms_take(score, 30, int(0.45 * max(w, h))):
if abs(y - target_y) <= y_tol:
best.append((v, s, x, y, w, h))
except (ValueError, IndexError, TypeError) as e:
print(f"匹配失败: {e}", file=sys.stderr)
return None
if not best:
# 放宽容差重试
return live_solve(big_path, mark_path, tip_y, y_tol=y_tol * 2)
best.sort(key=lambda c: c[0])
try:
v, s, x, y, w, h = best[0]
return {"x": x, "y": y, "scale": s, "score": round(float(v), 2), "w": w, "h": h}
except (IndexError, TypeError, ValueError) as e:
print(f"候选解析失败: {e}", file=sys.stderr)
return None
if __name__ == "__main__":
if len(sys.argv) < 4:
print(__doc__)
sys.exit(1)
try:
result = live_solve(sys.argv[1], sys.argv[2], int(sys.argv[3]))
except (OSError, ValueError) as e:
result = None
print(f"求解失败: {e}", file=sys.stderr)
print(json.dumps(result) if result else '{"ok": false}')