Integrations

使用 FastAPI 和 CaptchaAI 构建验证码解决微服务

团队同时维护几个爬虫或自动化项目时,往往每个项目都各写了一套验证码识别代码。更省事的做法是收敛成一个独立服务,其他项目调用一个 HTTP 接口就能拿到 token。

FastAPI 的异步模型很适配这个场景:识别耗时大多花在等 CaptchaAI 返回结果,async/await 不会让线程空等。本文用 FastAPI 搭建这样一个微服务,对外提供 REST 接口,内部通过 CaptchaAI API 识别 reCAPTCHA v2/v3、Turnstile 和图片验证码。出海产品的海外页面常见 reCAPTCHA、Turnstile,国内站点则以 GeeTest(极验)为主,统一到一个微服务更省心。


环境准备

开工前确认好这三样:

  • CaptchaAI API Key:在 CaptchaAI 官网 注册获取
  • Python 3.9 及以上
  • FastAPI + httpx:用于异步 HTTP 调用

安装依赖(国内环境可加清华镜像参数加速):

pip install fastapi uvicorn httpx

项目结构

solver.py 管识别逻辑,main.py 管路由和校验:

captcha-service/
├── main.py          # FastAPI app with endpoints
├── solver.py        # CaptchaAI solving logic
└── requirements.txt

CaptchaAI 识别模块:solver.py

四个 solve_* 函数各自拼好参数,统一走 submit_task 提交、poll_result 轮询:

# solver.py
import httpx
import asyncio

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://ocr.captchaai.com"


async def submit_task(params: dict) -> str:
    """Submit a CAPTCHA task and return the task ID."""
    params["key"] = API_KEY
    params["json"] = 1

    async with httpx.AsyncClient() as client:
        response = await client.post(f"{BASE_URL}/in.php", data=params)
        data = response.json()

    if data.get("status") != 1:
        raise ValueError(f"Submit error: {data.get('request')}")
    return data["request"]


async def poll_result(task_id: str, initial_wait: int = 15, max_attempts: int = 30) -> dict:
    """Poll for the CAPTCHA result."""
    await asyncio.sleep(initial_wait)

    async with httpx.AsyncClient() as client:
        for _ in range(max_attempts):
            response = await client.get(f"{BASE_URL}/res.php", params={
                "key": API_KEY, "action": "get", "id": task_id, "json": 1
            })
            data = response.json()

            if data.get("status") == 1:
                return {
                    "token": data["request"],
                    "user_agent": data.get("user_agent", "")
                }
            if data.get("request") != "CAPCHA_NOT_READY":
                raise ValueError(f"Solve error: {data['request']}")

            await asyncio.sleep(5)

    raise TimeoutError("Solve timed out")


async def solve_recaptcha_v2(sitekey: str, pageurl: str, enterprise: bool = False) -> dict:
    params = {"method": "userrecaptcha", "googlekey": sitekey, "pageurl": pageurl}
    if enterprise:
        params["enterprise"] = 1
    task_id = await submit_task(params)
    return await poll_result(task_id, initial_wait=20)


async def solve_recaptcha_v3(sitekey: str, pageurl: str, action: str, enterprise: bool = False) -> dict:
    params = {
        "method": "userrecaptcha", "version": "v3",
        "googlekey": sitekey, "pageurl": pageurl, "action": action
    }
    if enterprise:
        params["enterprise"] = 1
    task_id = await submit_task(params)
    return await poll_result(task_id, initial_wait=20)


async def solve_turnstile(sitekey: str, pageurl: str) -> dict:
    task_id = await submit_task({"method": "turnstile", "sitekey": sitekey, "pageurl": pageurl})
    return await poll_result(task_id, initial_wait=10)


async def solve_image(image_base64: str) -> dict:
    task_id = await submit_task({"method": "base64", "body": image_base64})
    return await poll_result(task_id, initial_wait=5, max_attempts=15)

FastAPI 服务:main.py

四个 POST 接口对应四种验证码类型,外加 /health 接口供探活:

# main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
import solver

app = FastAPI(title="CaptchaAI Solver Service")


class RecaptchaV2Request(BaseModel):
    sitekey: str
    pageurl: str
    enterprise: bool = False


class RecaptchaV3Request(BaseModel):
    sitekey: str
    pageurl: str
    action: str
    enterprise: bool = False


class TurnstileRequest(BaseModel):
    sitekey: str
    pageurl: str


class ImageRequest(BaseModel):
    image_base64: str


class SolveResponse(BaseModel):
    token: str
    user_agent: Optional[str] = ""


@app.post("/solve/recaptcha-v2", response_model=SolveResponse)
async def solve_recaptcha_v2(req: RecaptchaV2Request):
    try:
        result = await solver.solve_recaptcha_v2(req.sitekey, req.pageurl, req.enterprise)
        return SolveResponse(**result)
    except (ValueError, TimeoutError) as e:
        raise HTTPException(status_code=502, detail=str(e))


@app.post("/solve/recaptcha-v3", response_model=SolveResponse)
async def solve_recaptcha_v3(req: RecaptchaV3Request):
    try:
        result = await solver.solve_recaptcha_v3(req.sitekey, req.pageurl, req.action, req.enterprise)
        return SolveResponse(**result)
    except (ValueError, TimeoutError) as e:
        raise HTTPException(status_code=502, detail=str(e))


@app.post("/solve/turnstile", response_model=SolveResponse)
async def solve_turnstile(req: TurnstileRequest):
    try:
        result = await solver.solve_turnstile(req.sitekey, req.pageurl)
        return SolveResponse(**result)
    except (ValueError, TimeoutError) as e:
        raise HTTPException(status_code=502, detail=str(e))


@app.post("/solve/image", response_model=SolveResponse)
async def solve_image(req: ImageRequest):
    try:
        result = await solver.solve_image(req.image_base64)
        return SolveResponse(**result)
    except (ValueError, TimeoutError) as e:
        raise HTTPException(status_code=502, detail=str(e))


@app.get("/health")
async def health():
    return {"status": "ok"}

启动服务

uvicorn main:app --host 0.0.0.0 --port 8000

本地调试直接跑,上线后可放进容器交给编排系统管理。


调用示例

识别 reCAPTCHA v2

curl -X POST http://localhost:8000/solve/recaptcha-v2 \
  -H "Content-Type: application/json" \
  -d '{"sitekey": "6Le-wvkS...", "pageurl": "https://staging.example.com/qa-login"}'

识别 Cloudflare Turnstile

curl -X POST http://localhost:8000/solve/turnstile \
  -H "Content-Type: application/json" \
  -d '{"sitekey": "0x4AAAA...", "pageurl": "https://example.com/form"}'

响应示例:

{
  "token": "03AGdBq24PBCqLmOx2V4...",
  "user_agent": "Mozilla/5.0..."
}

生产环境加固建议

  • API Key 从环境变量读取,缺失时启动阶段就快速失败。
  • 请求校验和求解器执行分开处理。
  • 返回结构化错误,区分校验失败、求解器异常和上游拒绝。

常见故障排查

问题 原因 处理方式
502 响应 CaptchaAI 返回了错误 查看 detail 字段里的具体错误信息
识别超时 验证码处理耗时超出预期 调大 max_attempts,或检查 CaptchaAI 服务状态
连接被拒绝 服务没有启动 确认 uvicorn 监听的端口和请求端口一致
响应慢 阻塞了事件循环 确保用的是 httpx.AsyncClient,而不是同步的 requests

常见问题

微服务能同时处理多少个验证码请求?

瓶颈在 CaptchaAI 的线程数,不在 FastAPI:

  • BASIC($15/月,5 线程):同时最多 5 个任务
  • STANDARD($30/月,15 线程):同时最多 15 个任务

某个实例挂了,其他项目的识别会跟着断吗?

只部署一个实例会。建议多实例前面挂反向代理(nginx、Traefik),配合 /health 做健康检查,异常实例自动摘掉。

需要给 /solve/* 接口加鉴权吗?

需要,至少加一层 API Key 请求头校验,内部调用也不例外——用 FastAPI 依赖注入几行代码就能挂上。

怎么知道每次识别花了多长时间?

submit_taskpoll_result 前后打点计时,连同 CaptchaAI 返回的状态写进结构化日志,方便定位是提交慢还是轮询慢。


现在就把验证码识别封装成微服务

CaptchaAI 官网 注册获取 API Key,把这套 FastAPI 微服务接入你现有的项目,验证码识别只写一遍。


相关指南

该文章已禁用评论。