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

MongoDB cho CAPTCHA Lịch sử giải quyết và phân tích

Lược đồ và khung tổng hợp linh hoạt của MongoDB khiến nó trở nên phù hợp mạnh mẽ cho việc theo dõi giải CAPTCHA. Lưu trữ mọi nỗ lực giải bằng siêu dữ liệu, sau đó truy vấn các mẫu theo thời gian, loại CAPTCHA và tỷ lệ lỗi.

Tại sao MongoDB cho dữ liệu CAPTCHA

Bản ghi giải CAPTCHA có các trường thay đổi tùy theo loại - reCAPTCHA cần googlekey, hCaptcha cần sitekey, CAPTCHA hình ảnh cần body. Các tài liệu không có lược đồ của MongoDB xử lý những điều này một cách tự nhiên mà không cần di chuyển lược đồ.

Lược đồ tài liệu

{
  "_id": "ObjectId",
  "captcha_id": "12345678",
  "type": "recaptcha_v2",
  "method": "userrecaptcha",
  "sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
  "pageurl": "https://example.com/form",
  "status": "solved",
  "solution": "03AGdBq26...",
  "error": null,
  "submitted_at": "2026-04-04T10:15:30.000Z",
  "solved_at": "2026-04-04T10:15:45.000Z",
  "elapsed_ms": 15000,
  "polls": 3,
  "proxy_used": true,
  "cost": 0.00299,
  "metadata": {
    "project": "price-monitor",
    "worker_id": "worker-3",
    "target_domain": "example.com"
  }
}

Triển khai Python

Thiết lập và kết nối

import os
import time
from datetime import datetime, timezone
from pymongo import MongoClient, ASCENDING, DESCENDING
import requests

MONGO_URI = os.environ.get("MONGO_URI", "mongodb://localhost:27017")
API_KEY = os.environ["CAPTCHAAI_API_KEY"]

client = MongoClient(MONGO_URI)
db = client["captcha_tracking"]
solves = db["solves"]

Tạo chỉ mục

def setup_indexes():
    solves.create_index([("submitted_at", DESCENDING)])
    solves.create_index([("type", ASCENDING), ("status", ASCENDING)])
    solves.create_index([("metadata.project", ASCENDING)])
    solves.create_index([("metadata.target_domain", ASCENDING)])
    solves.create_index(
        [("submitted_at", ASCENDING)],
        expireAfterSeconds=90 * 24 * 3600,  # Auto-delete after 90 days
        name="ttl_cleanup"
    )

setup_indexes()

Giải quyết và lưu trữ

def solve_and_store(sitekey, pageurl, captcha_type="recaptcha_v2", metadata=None):
    record = {
        "type": captcha_type,
        "method": "userrecaptcha",
        "sitekey": sitekey,
        "pageurl": pageurl,
        "status": "submitted",
        "submitted_at": datetime.now(timezone.utc),
        "metadata": metadata or {}
    }

    result = solves.insert_one(record)
    doc_id = result.inserted_id

    # Submit to CaptchaAI
    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:
        solves.update_one(
            {"_id": doc_id},
            {"$set": {"status": "error", "error": data.get("request")}}
        )
        return None

    captcha_id = data["request"]
    solves.update_one(
        {"_id": doc_id},
        {"$set": {"captcha_id": captcha_id, "status": "polling"}}
    )

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

        if poll_resp.get("status") == 1:
            solved_at = datetime.now(timezone.utc)
            elapsed_ms = int(
                (solved_at - record["submitted_at"]).total_seconds() * 1000
            )
            solves.update_one({"_id": doc_id}, {"$set": {
                "status": "solved",
                "solution": poll_resp["request"],
                "solved_at": solved_at,
                "elapsed_ms": elapsed_ms,
                "polls": polls
            }})
            return poll_resp["request"]

        if poll_resp.get("request") != "CAPCHA_NOT_READY":
            solves.update_one({"_id": doc_id}, {"$set": {
                "status": "error",
                "error": poll_resp.get("request"),
                "polls": polls
            }})
            return None

    solves.update_one({"_id": doc_id}, {"$set": {
        "status": "timeout", "polls": polls
    }})
    return None

Truy vấn phân tích

def get_success_rate(hours=24):
    """Success rate for the last N hours."""
    from datetime import timedelta
    cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)

    pipeline = [
        {"$match": {"submitted_at": {"$gte": cutoff}}},
        {"$group": {
            "_id": "$status",
            "count": {"$sum": 1}
        }}
    ]
    results = {r["_id"]: r["count"] for r in solves.aggregate(pipeline)}
    total = sum(results.values())
    solved = results.get("solved", 0)
    return (solved / total * 100) if total else 0

def get_avg_solve_time_by_type():
    """Average solve time grouped by CAPTCHA type."""
    pipeline = [
        {"$match": {"status": "solved"}},
        {"$group": {
            "_id": "$type",
            "avg_time_ms": {"$avg": "$elapsed_ms"},
            "min_time_ms": {"$min": "$elapsed_ms"},
            "max_time_ms": {"$max": "$elapsed_ms"},
            "count": {"$sum": 1}
        }},
        {"$sort": {"count": -1}}
    ]
    return list(solves.aggregate(pipeline))

def get_hourly_solve_volume(days=7):
    """Hourly solve volume for charting."""
    from datetime import timedelta
    cutoff = datetime.now(timezone.utc) - timedelta(days=days)

    pipeline = [
        {"$match": {"submitted_at": {"$gte": cutoff}}},
        {"$group": {
            "_id": {
                "date": {"$dateToString": {"format": "%Y-%m-%d", "date": "$submitted_at"}},
                "hour": {"$hour": "$submitted_at"}
            },
            "total": {"$sum": 1},
            "solved": {"$sum": {"$cond": [{"$eq": ["$status", "solved"]}, 1, 0]}}
        }},
        {"$sort": {"_id.date": 1, "_id.hour": 1}}
    ]
    return list(solves.aggregate(pipeline))

def get_error_breakdown(hours=24):
    """Error frequency by error code."""
    from datetime import timedelta
    cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)

    pipeline = [
        {"$match": {"submitted_at": {"$gte": cutoff}, "status": "error"}},
        {"$group": {"_id": "$error", "count": {"$sum": 1}}},
        {"$sort": {"count": -1}}
    ]
    return list(solves.aggregate(pipeline))

Triển khai JavaScript

const { MongoClient } = require("mongodb");
const axios = require("axios");

const MONGO_URI = process.env.MONGO_URI || "mongodb://localhost:27017";
const API_KEY = process.env.CAPTCHAAI_API_KEY;

let db, solves;

async function connect() {
  const client = await MongoClient.connect(MONGO_URI);
  db = client.db("captcha_tracking");
  solves = db.collection("solves");

  await solves.createIndex({ submitted_at: -1 });
  await solves.createIndex({ type: 1, status: 1 });
  await solves.createIndex({ "metadata.project": 1 });
  await solves.createIndex(
    { submitted_at: 1 },
    { expireAfterSeconds: 90 * 24 * 3600 }
  );
}

async function solveAndStore(sitekey, pageurl, type = "recaptcha_v2", metadata = {}) {
  const submittedAt = new Date();
  const { insertedId } = await solves.insertOne({
    type, method: "userrecaptcha", sitekey, pageurl,
    status: "submitted", submitted_at: submittedAt, metadata,
  });

  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) {
    await solves.updateOne({ _id: insertedId }, { $set: { status: "error", error: submit.data.request } });
    return null;
  }

  const captchaId = submit.data.request;
  await solves.updateOne({ _id: insertedId }, { $set: { captcha_id: captchaId, status: "polling" } });

  let polls = 0;
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    polls++;
    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 solvedAt = new Date();
      await solves.updateOne({ _id: insertedId }, { $set: {
        status: "solved", solution: poll.data.request,
        solved_at: solvedAt, elapsed_ms: solvedAt - submittedAt, polls,
      }});
      return poll.data.request;
    }
    if (poll.data.request !== "CAPCHA_NOT_READY") {
      await solves.updateOne({ _id: insertedId }, { $set: { status: "error", error: poll.data.request, polls } });
      return null;
    }
  }

  await solves.updateOne({ _id: insertedId }, { $set: { status: "timeout", polls } });
  return null;
}

async function getSuccessRate(hours = 24) {
  const cutoff = new Date(Date.now() - hours * 3600 * 1000);
  const pipeline = [
    { $match: { submitted_at: { $gte: cutoff } } },
    { $group: { _id: "$status", count: { $sum: 1 } } },
  ];
  const results = await solves.aggregate(pipeline).toArray();
  const total = results.reduce((s, r) => s + r.count, 0);
  const solved = results.find((r) => r._id === "solved")?.count || 0;
  return total ? ((solved / total) * 100).toFixed(1) : 0;
}

Lưu giữ dữ liệu

Chiến lược Chỉ số TTL Trường hợp sử dụng
Lưu giữ 30 ngày expireAfterSeconds: 2592000 Phát triển/testing
Lưu giữ 90 ngày expireAfterSeconds: 7776000 Phân tích sản xuất
Vĩnh viễn (có lưu trữ) Không có TTL; sử dụng bộ sưu tập có nắp hoặc kho lạnh Tuân thủ/audit

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Truy vấn tổng hợp chậm Thiếu chỉ mục trên submitted_attype Chạy setup_indexes() - xem phần chỉ mục ở trên
Tài liệu ngày càng lớn Lưu trữ đầy đủ các giải pháp trong mỗi bản ghi Lưu trữ giải pháp băm hoặc cắt bớt sau khi sử dụng
TTL không xóa bản ghi cũ Màn hình TTL chạy 60 giây một lần; tồn đọng lớn cần có thời gian Đợi dọn dẹp nền; kiểm tra chỉ mục với db.solves.getIndexes()
Sự cạn kiệt của nhóm kết nối Quá nhiều thao tác giải quyết đồng thời Đặt maxPoolSize trong chuỗi kết nối

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

Tôi có nên lưu trữ toàn bộ mã thông báo giải pháp CAPTCHA không?

Để gỡ lỗi, hãy lưu trữ mã thông báo trong 24–48 giờ sau đó để chỉ mục TTL dọn sạch chúng. Đối với phân tích dài hạn, chỉ lưu trữ siêu dữ liệu (loại, thời gian, trạng thái, lỗi) - mã thông báo sẽ vô dụng sau khi hết hạn.

Cái này sử dụng bao nhiêu dung lượng?

Mỗi bản ghi giải có kích thước khoảng 500 byte đến 2 KB tùy thuộc vào siêu dữ liệu. Với 10.000 giải/day với khả năng lưu giữ trong 90 ngày, dự kiến sẽ có khoảng 1–2 GB. MongoDB xử lý việc này một cách dễ dàng.

Tôi có thể sử dụng MongoDB Atlas (đám mây) không?

Vâng. Atlas hỗ trợ các chỉ mục TTL và đường dẫn tổng hợp. Sử dụng chuỗi kết nối từ bảng điều khiển Atlas của bạn trong MONGO_URI.

Các bước tiếp theo

Theo dõi mọi giải pháp CAPTCHA và phát hiện các vấn đề trước khi chúng ảnh hưởng đến hệ thống của bạn —lấy khóa API CaptchaAI của bạn.

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

  • SQLite dành cho bộ nhớ đệm CAPTCHA cục bộ
  • Quản lý TTL mã thông báo Redis
  • Xu hướng hiệu suất theo chuỗi thời gian
Os comentários estão desativados para este artigo.