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_at và type |
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