Hướng Dẫn Thực Hành

Theo dõi xu hướng hiệu suất giải CAPTCHA bằng dữ liệu chuỗi thời gian

Pipeline vẫn chạy, log không có exception, nhưng số job trễ hạn tuần này gấp đôi tuần trước. Dashboard "ngay lúc này" không trả lời được, vì vấn đề nằm ở xu hướng.

Cách xử lý: ghi tỷ lệ giải thành công, thời gian giải và chi phí vào một database chuỗi thời gian, rồi cảnh báo theo trung bình trượt.

Chọn database chuỗi thời gian trước

Quyết định lưu ở đâu chi phối cách instrument solver.

Tiêu chí Prometheus InfluxDB TimescaleDB
Hợp với Giám sát vận hành Cardinality cao Phân tích bằng SQL
Truy vấn PromQL Flux SQL
Độ khó Thấp Trung bình Thấp nếu quen SQL

Đã có Prometheus cho hạ tầng thì thêm metric CAPTCHA vào registry sẵn có; InfluxDB hợp khi cần kho riêng.

Bảy chỉ số hiệu suất giải CAPTCHA cần ghi

Chỉ số Kiểu Vì sao cần
Tỷ lệ giải thành công (%) Gauge Chất lượng đổi theo ngày
Thời gian giải (ms) Histogram Bắt hiện tượng chậm dần
Tỷ lệ lỗi theo mã lỗi Counter Mã lỗi mới xuất hiện
Chi phí quy đổi mỗi lần giải ($) Gauge Đối chiếu ngân sách tháng
Độ sâu hàng đợi Gauge Khi nào cần thêm thread
Token hết hạn trước khi dùng Counter Tín hiệu chỉnh TTL
Số dư tài khoản API Gauge Kích hoạt nạp tiền

Lưu ý dòng chi phí: CaptchaAI tính theo thread (luồng giải đồng thời), không theo từng lần giải, nên "$/lần giải" là số bạn tự quy đổi từ giá gói — BASIC ($15/tháng, 5 thread).

Đo trong solver Python, đẩy qua Pushgateway

Job giải CAPTCHA sống ngắn, Prometheus không kịp scrape — để solver tự đẩy số liệu.

Instrument solver bằng prometheus_client

import os
import time
import requests
from prometheus_client import CollectorRegistry, Counter, Histogram, Gauge, push_to_gateway

registry = CollectorRegistry()

SOLVE_TOTAL = Counter(
    "captcha_solve_total", "Total CAPTCHA solve attempts",
    ["type", "status"], registry=registry
)
SOLVE_LATENCY = Histogram(
    "captcha_solve_latency_seconds", "CAPTCHA solve latency",
    ["type"], buckets=[5, 10, 15, 20, 30, 45, 60, 90, 120],
    registry=registry
)
SOLVE_COST = Counter(
    "captcha_solve_cost_dollars", "Total cost of CAPTCHA solves",
    ["type"], registry=registry
)
API_BALANCE = Gauge(
    "captcha_api_balance_dollars", "CaptchaAI account balance",
    registry=registry
)

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
PUSHGATEWAY = os.environ.get("PUSHGATEWAY_URL", "localhost:9091")

def solve_with_metrics(sitekey, pageurl, captcha_type="recaptcha_v2"):
    start = time.time()

    resp = requests.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:
        SOLVE_TOTAL.labels(type=captcha_type, status="submit_error").inc()
        push_metrics()
        return {"error": data.get("request")}

    captcha_id = data["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get",
            "id": captcha_id, "json": 1
        }).json()

        if result.get("status") == 1:
            elapsed = time.time() - start
            SOLVE_TOTAL.labels(type=captcha_type, status="solved").inc()
            SOLVE_LATENCY.labels(type=captcha_type).observe(elapsed)
            SOLVE_COST.labels(type=captcha_type).inc(0.00299)
            push_metrics()
            return {"solution": result["request"]}

        if result.get("request") != "CAPCHA_NOT_READY":
            SOLVE_TOTAL.labels(type=captcha_type, status="error").inc()
            push_metrics()
            return {"error": result.get("request")}

    SOLVE_TOTAL.labels(type=captcha_type, status="timeout").inc()
    push_metrics()
    return {"error": "TIMEOUT"}

def push_metrics():
    try:
        push_to_gateway(PUSHGATEWAY, job="captcha_solver", registry=registry)
    except Exception:
        pass  # Don't fail solving because metrics push failed

def update_balance():
    resp = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": API_KEY, "action": "getbalance"
    })
    try:
        balance = float(resp.text)
        API_BALANCE.set(balance)
        push_metrics()
    except ValueError:
        pass

push_metrics() nằm trong try/except: Pushgateway chết thì việc giải vẫn chạy tiếp.

Truy vấn PromQL cho tỷ lệ giải và P95

# Success rate over last hour
rate(captcha_solve_total{status="solved"}[1h])
/ rate(captcha_solve_total[1h]) * 100

# P95 solve latency
histogram_quantile(0.95, rate(captcha_solve_latency_seconds_bucket[1h]))

# Error rate by type
rate(captcha_solve_total{status="error"}[1h])

# Hourly cost
increase(captcha_solve_cost_dollars_total[1h])

Bốn truy vấn này đủ cho một dashboard Grafana và cho cảnh báo đầu tiên.

Đặt ngưỡng cảnh báo theo xu hướng, không theo một điểm dữ liệu

Cảnh báo theo từng lần giải lỗi sẽ kêu suốt đêm. So cửa sổ hiện tại với cùng cửa sổ hôm trước: tỷ lệ giải 1 giờ thấp hơn 10 điểm, P95 sáu giờ cao hơn 1,5 lần đường nền tuần, hoặc hàng đợi tăng liên tục ba mẫu đo. Chốt ngưỡng bằng dữ liệu của chính bạn sau một tuần chạy thật.

Ghi từng lần giải vào InfluxDB

Muốn tách số liệu CAPTCHA khỏi Prometheus hạ tầng, InfluxDB gọn hơn.

Viết point cho mỗi lần giải

from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS

INFLUX_URL = os.environ.get("INFLUX_URL", "http://localhost:8086")
INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN", "")
INFLUX_ORG = os.environ.get("INFLUX_ORG", "captcha")
INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "captcha_metrics")

influx_client = InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG)
write_api = influx_client.write_api(write_options=SYNCHRONOUS)

def record_solve_metric(captcha_type, status, elapsed_ms, cost=0.0, error=None):
    point = (
        Point("captcha_solve")
        .tag("type", captcha_type)
        .tag("status", status)
        .field("elapsed_ms", elapsed_ms)
        .field("cost", cost)
        .field("success", 1 if status == "solved" else 0)
    )
    if error:
        point = point.tag("error_code", error)
    write_api.write(bucket=INFLUX_BUCKET, record=point)

def record_balance(balance):
    point = Point("captcha_balance").field("balance", balance)
    write_api.write(bucket=INFLUX_BUCKET, record=point)

Trường success dạng 0/1 cho phép tính tỷ lệ bằng đúng hàm mean.

Truy vấn Flux theo cửa sổ 1 giờ

// Success rate over last 24 hours (1-hour windows)
from(bucket: "captcha_metrics")
  |> range(start: -24h)
  |> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "success")
  |> aggregateWindow(every: 1h, fn: mean)
  |> map(fn: (r) => ({r with _value: r._value * 100.0}))
  |> yield(name: "success_rate")

// Average solve time by type
from(bucket: "captcha_metrics")
  |> range(start: -24h)
  |> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "elapsed_ms" and r.status == "solved")
  |> group(columns: ["type"])
  |> aggregateWindow(every: 1h, fn: mean)
  |> yield(name: "avg_latency")

// Cumulative cost
from(bucket: "captcha_metrics")
  |> range(start: -24h)
  |> filter(fn: (r) => r._measurement == "captcha_solve" and r._field == "cost")
  |> cumulativeSum()
  |> yield(name: "cumulative_cost")

Ba đường: tỷ lệ thành công, thời gian giải và chi phí cộng dồn.

Bản Node.js với endpoint /metrics

Worker Node.js chạy dài không cần Pushgateway: mở sẵn /metrics cho Prometheus scrape.

const client = require("prom-client");
const axios = require("axios");

const register = new client.Registry();
const API_KEY = process.env.CAPTCHAAI_API_KEY;

const solveTotal = new client.Counter({
  name: "captcha_solve_total",
  help: "Total CAPTCHA solve attempts",
  labelNames: ["type", "status"],
  registers: [register],
});

const solveLatency = new client.Histogram({
  name: "captcha_solve_latency_seconds",
  help: "CAPTCHA solve latency",
  labelNames: ["type"],
  buckets: [5, 10, 15, 20, 30, 45, 60, 90, 120],
  registers: [register],
});

async function solveWithMetrics(sitekey, pageurl, type = "recaptcha_v2") {
  const start = Date.now();

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

  if (submit.data.status !== 1) {
    solveTotal.inc({ type, status: "submit_error" });
    return { error: submit.data.request };
  }

  const captchaId = submit.data.request;

  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
    });

    if (poll.data.status === 1) {
      const elapsed = (Date.now() - start) / 1000;
      solveTotal.inc({ type, status: "solved" });
      solveLatency.observe({ type }, elapsed);
      return { solution: poll.data.request };
    }

    if (poll.data.request !== "CAPCHA_NOT_READY") {
      solveTotal.inc({ type, status: "error" });
      return { error: poll.data.request };
    }
  }

  solveTotal.inc({ type, status: "timeout" });
  return { error: "TIMEOUT" };
}

// Expose metrics endpoint
const express = require("express");
const app = express();
app.get("/metrics", async (req, res) => {
  res.set("Content-Type", register.contentType);
  res.end(await register.metrics());
});
app.listen(9090);

Ví dụ: đội scraping TP.HCM đọc biểu đồ 24 giờ

Một agency ở TP.HCM giám sát giá trên dữ liệu công khai của Shopee và Tiki mỗi đêm. Sau khi bật metric: P95 thời gian giải tăng rõ trong khung 21–23h, tỷ lệ giải thành công không đổi, độ sâu hàng đợi tăng vọt. Solver không chậm đi — toàn bộ thread đã bận. Cách xử lý: giãn lịch batch, hoặc nâng từ STANDARD ($30/tháng, 15 thread) lên ADVANCE ($90/tháng, 50 thread).

Nhãn metric chỉ nên gồm loại, trạng thái và mã lỗi — đừng đưa URL hay dữ liệu người dùng vào: cardinality thấp hơn và hợp nguyên tắc tối thiểu hóa dữ liệu của Nghị định 13/2023/NĐ-CP.

Sự cố thường gặp khi đo hiệu suất giải CAPTCHA

Triệu chứng Nguyên nhân Cách xử lý
Biểu đồ đứt quãng Pushgateway không nhận dữ liệu Push trước khi job thoát
P95 lệch xa thực tế Bucket không khớp workload Chỉnh bucket theo thời gian giải thật
Chi phí lệch hóa đơn Một hằng số giá cho mọi loại Quy đổi từ giá gói tháng
Prometheus ngốn RAM Cardinality nhãn quá cao Giới hạn nhãn ở type, status, error_code

Câu hỏi thường gặp

Chi phí mỗi lần giải tính thế nào khi CaptchaAI bán theo thread?

Bạn tự quy đổi: lấy giá gói tháng chia cho tổng số lần giải thực chạy. CaptchaAI bán theo thread, không tính tiền theo từng lần giải.

Nên giữ số liệu CAPTCHA trong bao lâu?

Độ phân giải cao 7 ngày, tổng hợp theo giờ 90 ngày, tóm tắt theo ngày giữ lâu dài.

Bao nhiêu thread là đủ để hàng đợi không dồn?

Để biểu đồ trả lời: hàng đợi thường xuyên lớn hơn 0 trong khi tỷ lệ giải vẫn ổn định là dấu hiệu chạm trần thread.

Ghi metric có làm chậm solver không?

Không đáng kể. Phần chờ nằm ở polling res.php, không ở vài mili giây ghi metric.

Bước tiếp theo

Lấy API key CaptchaAI và đẩy điểm dữ liệu đầu tiên vào Prometheus hôm nay.

Hướng dẫn liên quan:

Os comentários estão desativados para este artigo.