Captcha.solve()— 单次识别(默认 oracle 多候选)Captcha.feedback()— 业务侧回传成败,贡献训练数据Captcha.solve_with_refresh()— 完整业务回路, 自动 oracle 试每个候选 + 失败换图重试 + auto-feedbackTCaptcha.solve_click()/GeetestV3.solve()— 厂商协议封装(腾讯防水墙 / 极验 V3)- NEW ·
playgroundCLI +BrowserAdapter— Playwright 浏览器自动化集成 - 同步 + 异步双套 API (
asolve/asolve_with_refresh)
📦 安装
方式 1 · 从 GitLab(推荐)
仓库地址 git.7bm.co:1024/CG/Bot-SDK:
# SSH (优先 — 复用本地密钥, 无需输密码)
uv add git+ssh://git@git.7bm.co:1024/CG/Bot-SDK.git
# 或 HTTPS (CI 场景常用, 私有仓需配 token)
uv add git+http://git.7bm.co:1024/CG/Bot-SDK.git
# 钉版本/分支(可选)
uv add "git+ssh://git@git.7bm.co:1024/CG/Bot-SDK.git@v0.1.0"
uv add "git+ssh://git@git.7bm.co:1024/CG/Bot-SDK.git@main"方式 2 · 本地开发模式
需要直接改 SDK 源码时:
# 本地路径 (editable, 改 SDK 立即生效)
uv add /Users/zwy/CG/CGBot-SDK
# 或在 SDK 项目里独立开发
cd /Users/zwy/CG/CGBot-SDK && uv sync💡 普通业务项目用方式 1(锁定版本,可重现);只有改 SDK 本身时才用方式 2。
⚙ 配置
设环境变量即可,业务代码无需任何 init 调用。
Captcha 是 static class,导入时自动从 env 读完配置,直接 Captcha.solve(...) 用。
export CGBOT_CAPTCHA_BASE_URL=http://127.0.0.1:8000
export CGBOT_CAPTCHA_API_KEY=sk-cap-xxx # 服务端开了鉴权才需要,本地一般不用
# 通用回退(可选, 5 个模块共用)
export CGBOT_TIMEOUT=30
export CGBOT_RETRIES=2业务代码:
from cgbot_sdk import Captcha, CaptchaType
# 直接用 — 无需 configure()、无需 from_env()
result = Captcha.solve(img, CaptchaType.ALNUM)⚙ 高级:运行时改配置(几乎用不上)
仅在需要进程中切 key/base_url 时:
import cgbot_sdk
# 单模块覆盖
cgbot_sdk.configure(
captcha={"api_key": "sk-new", "base_url": "http://staging:8000"},
)
# 或环境变量改了之后热重载
cgbot_sdk.configure_from_env()🎯 单次识别 classmethod
最简单的用法 — 拉一张图,识别一次。alpha / alnum 类型默认走 oracle 模式,
返回的 candidates 可能有 1 或 2 个,业务方需挨个试。
from pathlib import Path
from cgbot_sdk import Captcha, CaptchaType
img = Path("captcha.png").read_bytes()
result = Captcha.solve(img, CaptchaType.ALNUM)
print(result.text) # 主候选 — "8vka"
print(result.candidates) # oracle 全候选 — ["8vka"] 或 ["8vka", "8vku"]
print(result.elapsed_ms) # 耗时
print(result.solver) # 用了哪个引擎: local-ddddocr-oracle带 business_id — 触发服务端落库
传 business_id 后,服务端自动 record_solve 入 SolveLog,返回的
result.solve_id 业务方拿来回传 feedback。没传 business_id = 不入库。
result = Captcha.solve(
img, CaptchaType.ALPHA,
business_id="acme.com", # ← 业务归类 key
expect_length=4, # 透传选项: 期望 4 字符
)
assert result.solve_id is not None
print(f"已入库 solve_id={result.solve_id}, 候选={result.candidates}")↩ 反馈回传 classmethod
业务侧验证完成后,把实际成败 + 真值(失败时)回传 — 闭环训练数据的关键步骤。
result = Captcha.solve(img, CaptchaType.ALPHA, business_id="acme.com")
# 业务侧用 result.text (或 result.candidates 里某一个)去试登录...
login_ok = my_login_form.submit(captcha=result.text)
if login_ok:
Captcha.feedback(result.solve_id, success=True, true_text=result.text)
else:
# 失败时如果能从页面错误提示里抠到正确答案,一并传 → 直接当训练标签
Captcha.feedback(result.solve_id, success=False, true_text="actual_truth_if_known")🔁 完整业务回路 — solve_with_refresh classmethod ⭐ 主推
一行代码搞定"solve + 多候选挨个试 + 失败换图重试 + auto-feedback"整套回路。 业务方只需提供两个回调:
fetch_image— 拉一张新验证码 → 返回 bytestry_submit— 用候选试登录/提交 → 返回 True/False
import requests
from cgbot_sdk import Captcha, CaptchaType
SESSION = requests.Session()
def fetch_captcha() -> bytes:
"""业务方实现: 拉一张新验证码。"""
r = SESSION.get("https://target-site.com/captcha.jpg")
r.raise_for_status()
return r.content
def try_login(captcha_text: str) -> bool:
"""业务方实现: 用候选试登录,返回 True/False。"""
r = SESSION.post("https://target-site.com/login", data={
"username": "u123", "password": "p456",
"captcha": captcha_text,
})
return r.status_code == 200 and "token" in r.text
# ── 一行启动 ──
outcome = Captcha.solve_with_refresh(
fetch_image=fetch_captcha,
try_submit=try_login,
type=CaptchaType.ALPHA,
business_id="target-site.com", # 强烈建议传 — 触发自动 feedback
max_retries=5, # 最多刷 5 张验证码
sleep_between=0.5, # 每次刷新间隔 0.5s,防风控限流
on_attempt=lambda idx, cands, ok: # 可选回调,打日志/进度 UI
print(f" 尝试 #{idx} 候选={cands} 成功={ok}"),
)
if outcome.success:
print(f"✓ 登录成功! 共试 {outcome.attempts} 张验证码,命中: {outcome.matched_text!r}")
else:
print(f"✗ {outcome.attempts} 次都没成功 — 该换号或检查站点改版")feedback(success=True, true_text=cand);
所有候选都被拒 → 自动 feedback(success=False) 后换图重试。
业务方无需手动调 feedback,数据自动入库到 admin。
🏢 厂商协议封装 classmethod
部分站点用第三方风控厂商的完整协议(不只是给一张图给你识别),
SDK 把这些协议层都封装在服务端 — 业务方只传几个前端能拿到的入口参数,服务端跑加密 / 多步握手 / OCR / POW,
最终把可以直接 POST 给业务站点的凭证返回给业务方。
所有 vendor 协议共享 captcha 模块配置(同一组 base_url + api_key)。
🐧 TCaptcha — 腾讯防水墙文字点选
服务端处理: prehandle JSONP → tdc.js (Node.js sdenv-jsdom) → OCR 文字点选 → md5 POW → cap_union_new_verify。
业务方传 aid + website_url,拿 ticket + randstr。
from cgbot_sdk import TCaptcha
ticket = TCaptcha.solve_click(
aid="198735528", # 腾讯防水墙应用 ID
website_url="https://chengkao.gdcxxy.net/", # 业务方站点 (作 Referer)
business_id="chengkao", # 可选: OCR 阶段样本会落库
)
if ticket.success:
session.post(login_url, data={
"ticket": ticket.ticket,
"randstr": ticket.randstr,
"username": "...", "password": "...",
})
else:
print(f"协议失败: {ticket.error}")🧩 GeetestV3 — 极验 V3 滑块
服务端处理: api.geetest.com 探活 → step1 probe → step2 指纹无感 → step3 拉滑块图 → step4 提交。
业务方传 gt + challenge(前端 init 接口拿)+ website_url,
拿 validate + seccode。
from cgbot_sdk import GeetestV3
r = GeetestV3.solve(
gt="abc123...", # 前端 init 拿
challenge="xyz789...", # 前端 init 拿
website_url="https://jxjyseig.o-learn.cn/",
)
if r.success:
session.post(login_url, data={
"geetest_challenge": r.challenge,
"geetest_validate": r.validate,
"geetest_seccode": r.seccode,
"username": "...", "password": "...",
})异步
两个类都有 a* 异步版: TCaptcha.asolve_click(...) /
GeetestV3.asolve(...),签名与同步版一致。
🎮 Playground · 浏览器自动化 NEW
captcha-solver 内置一组 Playwright 工具,把 SDK 跟真实浏览器接到一起 — 业务方一行命令 就能跑"打开页面 → 拉验证码 → SDK solve → 填表 → 提交"完整闭环。 装在 captcha-solver 里,不影响 SDK 本身的纯 HTTP 调用。
uv sync --extra playground
📦 安装
# 装 playground extra
uv sync --extra playground
uv run playwright install chromium
# 装 cgbot-sdk(还没装的话)
uv pip install -e /Users/zwy/CG/CGBot-SDK
# 自检
uv run playground doctor🚀 CLI 用法
# 跑本地 playground 命中率测试
uv run playground test ocr_alnum --rounds 20
# → [1/20] ✓ success=True attempts=1 matched='8vka'
# ...
# 成功率: 18/20 = 90%
# 跑滑块
uv run playground test slider_gap --rounds 10 --headed # --headed 看浏览器
# 跑外部 URL(显式 selectors 模式)
uv run playground login \
--url https://target.com/login \
--user u --pass p \
--captcha-type ocr_alpha \
--sel-username '#user' --sel-password '#pass' \
--sel-captcha 'img.cap' --sel-input '#code' \
--sel-submit 'button[type=submit]' \
--success-marker 'text=欢迎'
# 列出内置 profile
uv run playground profiles list🐍 Python API
核心抽象 BrowserAdapter:把 Playwright page + selectors 包成 SDK 期望的
fetch_image + try_submit 回调,无缝套上
Captcha.solve_with_refresh。
from playground_runner import open_browser, BrowserAdapter, Selectors
from cgbot_sdk import Captcha, CaptchaType
selectors = Selectors(
username="#user", password="#pass",
captcha_widget="img.cap", captcha_input="#code",
submit="button[type=submit]",
success_marker="text=欢迎",
)
with open_browser(headless=False) as page:
page.goto("https://target.com/login")
adapter = BrowserAdapter(page, selectors, user="u", pwd="p")
outcome = Captcha.solve_with_refresh(
fetch_image=adapter.fetch_image_bytes,
try_submit=adapter.try_submit,
type=CaptchaType.ALPHA,
business_id="target.com",
max_retries=5,
)
print(outcome)🧩 滑块支持
SliderAdapter 用 human_drag 模拟人手轨迹(加速 → 过冲 → 回拖 + 抖动),
风控站点拖动检测过得去。坐标映射也帮你处理(thumb 拖距 ≠ piece 偏移时按比例换算)。
examples/ 目录playground_alnum.py OCR 完整回路playground_slider.py 滑块 + human_dragplayground_external_url.py 自定义 selectors 跑外站⚡ 异步 API async
所有同步方法都有 a* 异步版: asolve / afeedback /
asolve_with_refresh。回调可同步可异步,SDK 自动判断。
import asyncio, httpx
import cgbot_sdk
from cgbot_sdk import Captcha, CaptchaType
async def main():
async with httpx.AsyncClient() as session:
async def afetch():
r = await session.get("https://target.com/captcha.jpg")
return r.content
async def asubmit(text: str) -> bool:
r = await session.post("https://target.com/login",
data={"captcha": text, "user": "..."})
return r.status_code == 200
outcome = await Captcha.asolve_with_refresh(
fetch_image=afetch, try_submit=asubmit,
type=CaptchaType.ALPHA, business_id="target.com",
max_retries=3,
)
print(outcome)
await cgbot_sdk.aclose() # 关闭 httpx 连接池
asyncio.run(main())📐 数据类型
CaptchaType
| 枚举值 | 字符集 | 默认 oracle | 备注 |
|---|---|---|---|
| ALNUM | a-z 0-9 | ✓ | 默认类型,4-6 字符 |
| DIGIT | 0-9 | — | 单 default |
| ALPHA | a-z | ✓ | 真实风控站点 oracle 提升 +10pp |
| CHINESE | 汉字 | — | 单 default |
| CALC | 数学算式 | — | 服务端 OCR + 安全 eval |
| SLIDER_GAP | 滑块 | — | 需传 bg_image |
| DETECTION | 点选 | — | 返回 bboxes |
| CLICK_TEXT | 文字点选 | — | 需传 target_chars,返回 points 坐标 |
CaptchaResult
@dataclass
class CaptchaResult:
success: bool # 服务端 solve 是否本地成功
solver: str # 引擎名: "local-ddddocr-oracle" 等
elapsed_ms: int # 耗时
text: Optional[str] = None # 主候选文本
candidates: List[str] = [] # oracle 全候选: 1 个一致 / 2 个分歧
solve_id: Optional[str] = None # 服务端落库后的 ID, 业务方拿去 feedback
x: Optional[int] = None # slider 的 X 偏移
bboxes: Optional[List[Tuple]] = None # detection 的检测框
confidence: Optional[float] = None # 置信度(ddddocr 不太准,仅参考)
error: Optional[str] = None # 失败原因SolveOutcome (solve_with_refresh 返回)
@dataclass
class SolveOutcome:
success: bool # max_retries 内有任一 (image, candidate) 被业务接受
attempts: int # 实际刷新了几次验证码 (1..max_retries)
matched_text: Optional[str] = None # 成功时业务侧接受的那个候选文本
last_solve_id: Optional[str] = None # 最后一次 solve 的 solve_id📊 实测命中率
基于真实风控站点 alpha 验证码 + captcha 库生成的 alnum 测试集。 oracle 多候选 + max_retries 重试组合极大提升业务可用率。
| 类型 | 单次命中 | +oracle | +oracle +retry=3 | +oracle +retry=5 |
|---|---|---|---|---|
| ALPHA (真实站点) | 35% | 47.5% | 83% | 94.5% |
| ALNUM (captcha 库) | 40% | 52% | 90% | 96% |
💡 业务可用率 = max_retries 次内任一 (image, candidate) 被业务接受。在批量验证页可复现。
⚠ 异常处理
SDK 会抛 CGBotError 子类,业务方按需 catch:
from cgbot_sdk.errors import (
CGBotError, # 基类
AuthError, # 401 — API key 错或没传
NotFoundError, # 404 — solve_id 不存在
NetworkError, # 连接超时/拒绝
ServerError, # 5xx
)
try:
result = Captcha.solve(img, CaptchaType.ALNUM)
except AuthError:
print("API key 无效,检查 CGBOT_CAPTCHA_API_KEY")
except NetworkError as e:
print(f"服务端连不上: {e}")
except CGBotError as e:
print(f"其他 SDK 错误: {e}")