DevOps & Scaling

使用 Datadog 进行 CaptchaAI 监控:指标和警报

凌晨两点,验证码识别队列悄悄堆到 300+。直到第二天早上运营问”采集任务怎么全卡住了”,你才后知后觉。

把解决量、成功率、延迟和余额这几个信号接入 Datadog,管道真正断掉的那一刻就收到告警,而不是靠用户投诉倒推故障。

该监控哪些 CaptchaAI 指标

下面 7 个指标基本能覆盖一条验证码识别管道的健康状况:

指标 类型 作用
captcha.solve.count 计数器 提交任务总数
captcha.solve.success 计数器 成功解决的次数
captcha.solve.error 计数器 失败次数(按错误类型细分)
captcha.solve.latency 直方图 从提交到出结果的耗时
captcha.queue.depth 仪表值 队列中排队的任务数
captcha.balance 仪表值 剩余 API 余额
captcha.worker.active 仪表值 在线工作进程数

指标异常排查

常见坑:

  • 指标不显示:DogStatsD agent 未运行。检查 DD_AGENT_HOST;用 docker ps 确认 agent 容器状态。
  • 延迟直方图为空:没有解决成功的请求被记录。确认 statsd.histogram() 在成功分支里被调用。
  • 标签丢失:标签格式写错。使用 key:value 格式,标签内不要有空格。
  • 指标重复上报:多个上报进程同时在跑。每次部署只保留一个余额上报进程。

Python 集成:用 DogStatsD 自动埋点

下面用一个装饰器包住 solve 函数,自动上报提交次数、成功率和延迟:

import os
import time
import functools
import requests
from datadog import initialize, statsd

# Initialize Datadog
initialize(
    statsd_host=os.environ.get("DD_AGENT_HOST", "localhost"),
    statsd_port=int(os.environ.get("DD_DOGSTATSD_PORT", "8125"))
)

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
session = requests.Session()


def track_captcha_metrics(captcha_type="recaptcha_v2"):
    """Decorator to track solve metrics."""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            tags = [f"captcha_type:{captcha_type}"]
            statsd.increment("captcha.solve.count", tags=tags)

            start = time.time()
            try:
                result = func(*args, **kwargs)
                elapsed = time.time() - start

                if "solution" in result:
                    statsd.increment("captcha.solve.success", tags=tags)
                    statsd.histogram("captcha.solve.latency", elapsed, tags=tags)
                else:
                    error = result.get("error", "unknown")
                    statsd.increment(
                        "captcha.solve.error",
                        tags=tags + [f"error:{error}"]
                    )
                return result
            except Exception as e:
                statsd.increment(
                    "captcha.solve.error",
                    tags=tags + [f"error:{type(e).__name__}"]
                )
                raise
        return wrapper
    return decorator


@track_captcha_metrics(captcha_type="recaptcha_v2")
def solve_recaptcha(sitekey, pageurl):
    resp = session.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    })
    data = resp.json()
    if data.get("status") != 1:
        return {"error": data.get("request")}

    captcha_id = data["request"]
    for _ in range(60):
        time.sleep(5)
        result = session.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": captcha_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return {"solution": result["request"]}
        if result.get("request") != "CAPCHA_NOT_READY":
            return {"error": result.get("request")}
    return {"error": "TIMEOUT"}


def report_balance():
    """Send balance as a gauge metric."""
    resp = session.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "getbalance", "json": 1
    })
    data = resp.json()
    if data.get("status") == 1:
        balance = float(data["request"])
        statsd.gauge("captcha.balance", balance)
        return balance
    return None


def report_queue_depth(depth):
    """Report current queue depth."""
    statsd.gauge("captcha.queue.depth", depth)


def report_worker_count(active, total):
    """Report worker health."""
    statsd.gauge("captcha.worker.active", active)
    statsd.gauge("captcha.worker.total", total)

国内安装慢就换清华镜像:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple datadog

JavaScript 集成:Node.js 上报指标

Node.js 用 hot-shots 接入 DogStatsD,思路和 Python 版一致:

const { StatsD } = require("hot-shots");
const axios = require("axios");

const API_KEY = process.env.CAPTCHAAI_API_KEY;

const dogstatsd = new StatsD({
  host: process.env.DD_AGENT_HOST || "localhost",
  port: parseInt(process.env.DD_DOGSTATSD_PORT || "8125", 10),
  prefix: "captcha.",
  globalTags: [`env:${process.env.NODE_ENV || "development"}`],
});

async function solveCaptchaWithMetrics(sitekey, pageurl, captchaType = "recaptcha_v2") {
  const tags = [`captcha_type:${captchaType}`];
  dogstatsd.increment("solve.count", 1, tags);
  const startTime = Date.now();

  try {
    const result = await solveCaptcha(sitekey, pageurl);
    const elapsed = (Date.now() - startTime) / 1000;

    if (result.solution) {
      dogstatsd.increment("solve.success", 1, tags);
      dogstatsd.histogram("solve.latency", elapsed, tags);
    } else {
      dogstatsd.increment("solve.error", 1, [...tags, `error:${result.error}`]);
    }

    return result;
  } catch (err) {
    dogstatsd.increment("solve.error", 1, [...tags, `error:${err.message}`]);
    throw err;
  }
}

async function solveCaptcha(sitekey, pageurl) {
  const submitResp = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: {
      key: API_KEY,
      method: "userrecaptcha",
      googlekey: sitekey,
      pageurl: pageurl,
      json: 1,
    },
  });

  if (submitResp.data.status !== 1) {
    return { error: submitResp.data.request };
  }

  const captchaId = submitResp.data.request;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const pollResp = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
    });
    if (pollResp.data.status === 1) return { solution: pollResp.data.request };
    if (pollResp.data.request !== "CAPCHA_NOT_READY") {
      return { error: pollResp.data.request };
    }
  }
  return { error: "TIMEOUT" };
}

async function reportBalance() {
  try {
    const resp = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "getbalance", json: 1 },
    });
    if (resp.data.status === 1) {
      const balance = parseFloat(resp.data.request);
      dogstatsd.gauge("balance", balance);
      return balance;
    }
  } catch (err) {
    console.error("Balance check failed:", err.message);
  }
  return null;
}

// Report balance every minute
setInterval(reportBalance, 60000);

module.exports = { solveCaptchaWithMetrics, reportBalance };

导入这份 JSON,快速搭出监控仪表板

把下面的 JSON 粘贴到 Datadog“导入 JSON”面板,直接拿到一个能用的监控看板:

{
  "title": "CaptchaAI Pipeline",
  "widgets": [
    {
      "definition": {
        "type": "timeseries",
        "title": "Solve Rate (Success vs Error)",
        "requests": [
          {"q": "sum:captcha.solve.success{*}.as_count()"},
          {"q": "sum:captcha.solve.error{*}.as_count()"}
        ]
      }
    },
    {
      "definition": {
        "type": "timeseries",
        "title": "Solve Latency (p50, p95, p99)",
        "requests": [
          {"q": "avg:captcha.solve.latency{*}"},
          {"q": "percentile:captcha.solve.latency{*},0.95"},
          {"q": "percentile:captcha.solve.latency{*},0.99"}
        ]
      }
    },
    {
      "definition": {
        "type": "query_value",
        "title": "API Balance",
        "requests": [{"q": "avg:captcha.balance{*}"}]
      }
    },
    {
      "definition": {
        "type": "timeseries",
        "title": "Queue Depth",
        "requests": [{"q": "avg:captcha.queue.depth{*}"}]
      }
    }
  ]
}

告警规则怎么配

光看仪表板不够,得有人在管道断掉前被叫醒:

  • 余额不足(警告):captcha.balance < 10
  • 余额告急(严重):captcha.balance < 2
  • 错误率过高(警告):5 分钟内错误率超过 10%
  • 延迟突增(警告):p95 延迟连续 10 分钟超过 120 秒
  • 队列积压(警告):队列深度连续 5 分钟增长且超过 100
  • 工作进程离线(严重):captcha.worker.active == 0
# Datadog monitor definition (API create)
- type: metric alert
  name: "CaptchaAI Low Balance"
  query: "avg(last_5m):avg:captcha.balance{*} < 10"
  message: "CaptchaAI balance is low: {{value}}. Top up to avoid solve failures."
  tags:

    - team:scraping
    - service:captcha

常见问题

一台主机上跑多个 worker,要装几个 Datadog agent?

一个就够。同一台主机上所有 worker 把指标发到本地这一个 agent,再统一转发给 Datadog。

从国内网络上报指标到 Datadog,会不会经常超时?

会有一定延迟,接收端点在海外,国内网络偶尔抖动很正常。agent 部署在服务器本地先聚合再批量转发,能明显减少超时影响。

要不要按 CaptchaAI 套餐的线程数设置容量告警?

值得加一条。比如用 STANDARD($30/月,15 线程),captcha.queue.depth 持续超过线程数数倍时,该升级 ADVANCE($90/月,50 线程),而不是先查错误率。

下一步

把可观测性接入识别管道——申请 CaptchaAI API Key,按上面的步骤接入 Datadog。

相关文章

  1. 如何搭建审核监控系统
  2. 如何搭建内容变更监控机器人
  3. 搭建 CaptchaAI 用量监控仪表板

相关指南

该文章已禁用评论。