DevOps & Scaling

Docker + CaptchaAI:容器化验证码解决

把 CaptchaAI 验证码识别服务部署到 Docker 里,核心就三件事:API Key 不写死进镜像、单个 worker 顶不住就用 Compose 横向扩容、生产环境用多阶段构建把镜像做小。本文从最简单的 Dockerfile 讲到 Redis 队列多 worker,代码可直接抄进项目里用。


基础镜像该怎么写

验证码识别对运行环境很敏感——本地能跑、CI 里报错,多半是 Python 或依赖版本没锁死。打包成镜像,就能让每台机器跑出一样的结果。下面是一个够用的基础镜像:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY solver.py .

# API key passed at runtime, not baked into image
ENV CAPTCHAAI_KEY=""

CMD ["python", "solver.py"]

requirements.txt:

requests>=2.31.0

国内网络访问 PyPI 有时不太稳定,构建镜像时可以在 pip install 后面加一个国内镜像源,比如清华 TUNA:pip install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt,能明显缩短构建耗时。


求解器脚本:调用 CaptchaAI 接口

脚本逻辑很直接:把任务提交给 in.php,拿到 task id 后每 5 秒轮询 res.php,直到拿到 token 或超时。CaptchaAI 支持 reCAPTCHA v2/v3、Turnstile、GeeTest v3、图片/九宫格验证码等类型,下面以 reCAPTCHA v2 为例:

# solver.py
import os
import sys
import requests
import time


def solve_recaptcha(api_key, site_key, page_url):
    """Solve reCAPTCHA v2 using CaptchaAI."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(f"Submit error: {result.get('request')}")

    task_id = result["request"]

    # Poll for result
    for _ in range(24):  # 120s max
        time.sleep(5)
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()
        if data["request"] != "CAPCHA_NOT_READY":
            if data.get("status") == 1:
                return data["request"]
            raise RuntimeError(f"Solve error: {data['request']}")

    raise TimeoutError("Solve timeout")


if __name__ == "__main__":
    api_key = os.environ.get("CAPTCHAAI_KEY")
    if not api_key:
        print("Error: CAPTCHAAI_KEY environment variable required")
        sys.exit(1)

    site_key = os.environ.get("SITE_KEY", "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")
    page_url = os.environ.get("PAGE_URL", "https://example.com")

    token = solve_recaptcha(api_key, site_key, page_url)
    print(f"Token: {token[:50]}...")

拿到 token 之后,直接把它塞进目标页面对应的表单字段,正常提交表单即可,不需要额外处理。


构建镜像并启动容器

镜像写好后,本地先构建再跑一次,确认能拿到 token:

# Build
docker build -t captchaai-solver .

# Run with API key from environment
docker run --rm \
  -e CAPTCHAAI_KEY="YOUR_API_KEY" \
  -e SITE_KEY="TARGET_SITE_KEY" \
  -e PAGE_URL="https://example.com" \
  captchaai-solver

CAPTCHAAI_KEY 通过 -e 在运行时注入,不出现在镜像的任何一层——下一节的多阶段构建同样要保持这一点。


多阶段构建:给生产镜像瘦身

本地调试的镜像够用就行,但生产环境要更小的体积、非 root 用户运行。多阶段构建把安装和运行拆成两个阶段,最终镜像不会留下构建工具链:

# Build stage
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/app/deps -r requirements.txt

# Runtime stage
FROM python:3.11-slim

# Run as non-root
RUN useradd --create-home solver
USER solver

WORKDIR /home/solver/app

COPY --from=builder /app/deps /home/solver/app/deps
COPY solver.py .

ENV PYTHONPATH=/home/solver/app/deps
ENV PYTHONUNBUFFERED=1

CMD ["python", "solver.py"]

useradd --create-home solverUSER solver,让容器进程以非 root 身份运行——多数安全扫描工具会把"以 root 运行"标成高风险项。


用 Docker Compose 起多个 worker

单个容器吞吐有上限,量上去了就该用 Compose 横向扩容。下面这份配置同时起了 4 个求解 worker、一个 Redis,和 4 个消费队列的 worker:

# docker-compose.yml
version: "3.8"

services:
  solver-worker:
    build: .
    environment:

      - CAPTCHAAI_KEY=${CAPTCHAAI_KEY}
    restart: unless-stopped
    deploy:
      replicas: 4
      resources:
        limits:
          memory: 256M
          cpus: "0.25"

  redis:
    image: redis:7-alpine
    ports:

      - "6379:6379"

  queue-worker:
    build:
      context: .
      dockerfile: Dockerfile.worker
    environment:

      - CAPTCHAAI_KEY=${CAPTCHAAI_KEY}
      - REDIS_URL=redis://redis:6379
    depends_on:

      - redis
    deploy:
      replicas: 4

副本数最好参考 CaptchaAI 套餐的线程数:BASIC($15/月,5 线程)配 4 个 worker 差不多够用;量上来了换 ADVANCE($90/月,50 线程),副本数同步往上调。


基于 Redis 队列的 worker 脚本

如果任务是持续从队列消费(比如上游爬虫、批量任务往 Redis 塞任务),用队列 worker 模式更合适——只管取任务、求解、写回结果,扩容加副本数就行:

# queue_worker.py
import os
import json
import time
import redis
import requests


def process_task(api_key, task_data):
    """Process a single CAPTCHA task from the queue."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": task_data["method"],
        "json": 1,
        **task_data["params"],
    }, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        return {"error": result.get("request")}

    task_id = result["request"]

    for _ in range(24):
        time.sleep(5)
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()
        if data["request"] != "CAPCHA_NOT_READY":
            if data.get("status") == 1:
                return {"token": data["request"]}
            return {"error": data["request"]}

    return {"error": "timeout"}


def main():
    api_key = os.environ["CAPTCHAAI_KEY"]
    redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379")
    r = redis.from_url(redis_url)

    print("Worker started, waiting for tasks...")
    while True:
        _, raw = r.blpop("captcha:tasks")
        task = json.loads(raw)
        task_id = task.get("id", "unknown")

        print(f"Processing task {task_id}...")
        result = process_task(api_key, task)

        r.hset("captcha:results", task_id, json.dumps(result))
        print(f"Task {task_id} done: {'ok' if 'token' in result else 'error'}")


if __name__ == "__main__":
    main()

脚本用的是最基础的 blpop 阻塞读取,需要优先级或失败重试可换成 Celery、RQ,逻辑不用大改。


API Key 怎么管理:环境变量与 .env

无论单容器还是 Compose 多 worker,API Key 都应在运行时通过环境变量注入,.env 文件不进 Git 仓库:

# .env file (never commit to Git)
CAPTCHAAI_KEY=your_api_key_here

# .gitignore
echo ".env" >> .gitignore

# Run with .env file
docker compose --env-file .env up -d

# Scale workers
docker compose up -d --scale queue-worker=8

常见故障排查

问题 原因 处理方式
容器启动后立刻退出 没有传入 CAPTCHAAI_KEY 加上 -e CAPTCHAAI_KEY=...
DNS 解析失败 容器没有网络访问权限 检查 Docker 网络配置
内存占用过高 并发请求数过多 限制容器内存并控制并发数
API Key 出现在镜像里 Key 写死在 Dockerfile 里 改用环境变量或 Docker secrets

常见问题

requirements.txt 只锁了 requests 的版本,够用吗?

对这个脚本足够——它只靠 requests 发请求。同一镜像里如果还跑 Selenium、Playwright,建议锁到具体小版本,避免 CI 因依赖自动升级出现不可复现的构建。

API Key 能不能直接写死在 Dockerfile 里?

不建议。写进 Dockerfile 的值会留在镜像层历史里,之后删掉也能被 docker history 翻出来。始终用 -e CAPTCHAAI_KEY=... 或 Docker secrets 在运行时传入。

docker pull 拉基础镜像很慢,有办法吗?

给 Docker daemon 配置国内 registry 加速地址,或把 python:3.11-slim 换成团队自维护的私有镜像,首次拉取快不少。

worker 副本数要不要跟着 CaptchaAI 套餐的线程数走?

建议大致对齐。比如 ADVANCE($90/月,50 线程)配 8~10 个 worker 通常就能把并发打满;副本开太多,任务反而在线程层面排队等待,没有实际收益。


相关阅读


把你的验证码识别服务容器化——立即使用 CaptchaAI

该文章已禁用评论。