SQLite không cần thiết lập máy chủ, chạy bằng Python và xử lý hàng nghìn lượt ghi mỗi giây. Đó là cách nhanh nhất để thêm tính năng theo dõi giải quyết cục bộ, bộ nhớ đệm mã thông báo và yêu cầu loại bỏ trùng lặp vào quy trình giải quyết CAPTCHA.
Khi nào nên sử dụng SQLite
| Trường hợp sử dụng | SQLite | Thay thế tốt hơn |
|---|---|---|
| Phát triển máy đơn | ✅ | — |
| Sản xuất quy mô nhỏ (< 1K giải quyết/hr) | ✅ | — |
| Theo dõi kết quả kiểm tra | ✅ | — |
| Sản xuất nhiều máy chủ | ⏳ | PostgreSQL, MongoDB |
| Phân phối thông lượng cao | ⏳ | Redis, DynamoDB |
| Bảng điều khiển phân tích thời gian thực | ⏳ | TimescaleDB, InfluxDB |
Thiết kế lược đồ
CREATE TABLE IF NOT EXISTS captcha_solves (
id INTEGER PRIMARY KEY AUTOINCREMENT,
captcha_id TEXT,
type TEXT NOT NULL,
sitekey TEXT,
pageurl TEXT,
status TEXT NOT NULL DEFAULT 'submitted',
solution TEXT,
error TEXT,
submitted_at TEXT NOT NULL DEFAULT (datetime('now')),
solved_at TEXT,
elapsed_ms INTEGER,
polls INTEGER DEFAULT 0,
project TEXT
);
CREATE INDEX IF NOT EXISTS idx_submitted_at ON captcha_solves(submitted_at);
CREATE INDEX IF NOT EXISTS idx_type_status ON captcha_solves(type, status);
CREATE INDEX IF NOT EXISTS idx_sitekey ON captcha_solves(sitekey);
-- Token cache for reuse within TTL
CREATE TABLE IF NOT EXISTS token_cache (
sitekey TEXT NOT NULL,
pageurl TEXT NOT NULL,
token TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT NOT NULL,
used INTEGER DEFAULT 0,
PRIMARY KEY (sitekey, pageurl, token)
);
CREATE INDEX IF NOT EXISTS idx_cache_lookup
ON token_cache(sitekey, pageurl, used, expires_at);
Triển khai Python
Thiết lập cơ sở dữ liệu
import os
import time
import sqlite3
from datetime import datetime, timedelta, timezone
import requests
DB_PATH = os.environ.get("CAPTCHA_DB", "captcha_solves.db")
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL") # Better concurrent read performance
conn.execute("PRAGMA busy_timeout=5000")
return conn
def init_db():
conn = get_db()
conn.executescript("""
CREATE TABLE IF NOT EXISTS captcha_solves (
id INTEGER PRIMARY KEY AUTOINCREMENT,
captcha_id TEXT,
type TEXT NOT NULL,
sitekey TEXT,
pageurl TEXT,
status TEXT NOT NULL DEFAULT 'submitted',
solution TEXT,
error TEXT,
submitted_at TEXT NOT NULL DEFAULT (datetime('now')),
solved_at TEXT,
elapsed_ms INTEGER,
polls INTEGER DEFAULT 0,
project TEXT
);
CREATE INDEX IF NOT EXISTS idx_submitted_at ON captcha_solves(submitted_at);
CREATE INDEX IF NOT EXISTS idx_type_status ON captcha_solves(type, status);
CREATE TABLE IF NOT EXISTS token_cache (
sitekey TEXT NOT NULL,
pageurl TEXT NOT NULL,
token TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
expires_at TEXT NOT NULL,
used INTEGER DEFAULT 0,
PRIMARY KEY (sitekey, pageurl, token)
);
CREATE INDEX IF NOT EXISTS idx_cache_lookup
ON token_cache(sitekey, pageurl, used, expires_at);
""")
conn.close()
init_db()
Giải quyết và theo dõi
def solve_recaptcha(sitekey, pageurl, project=None):
conn = get_db()
# Check cache first
cached = get_cached_token(conn, sitekey, pageurl)
if cached:
conn.close()
return cached
# Insert tracking record
now = datetime.now(timezone.utc).isoformat()
cursor = conn.execute(
"INSERT INTO captcha_solves (type, sitekey, pageurl, submitted_at, project) "
"VALUES (?, ?, ?, ?, ?)",
("recaptcha_v2", sitekey, pageurl, now, project)
)
row_id = cursor.lastrowid
conn.commit()
# 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:
conn.execute(
"UPDATE captcha_solves SET status=?, error=? WHERE id=?",
("error", data.get("request"), row_id)
)
conn.commit()
conn.close()
return None
captcha_id = data["request"]
conn.execute(
"UPDATE captcha_solves SET captcha_id=?, status=? WHERE id=?",
(captcha_id, "polling", row_id)
)
conn.commit()
# Poll
polls = 0
for _ in range(60):
time.sleep(5)
polls += 1
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:
solved_at = datetime.now(timezone.utc).isoformat()
submitted = datetime.fromisoformat(now)
elapsed = int((datetime.now(timezone.utc) - submitted).total_seconds() * 1000)
conn.execute(
"UPDATE captcha_solves SET status=?, solution=?, solved_at=?, "
"elapsed_ms=?, polls=? WHERE id=?",
("solved", result["request"], solved_at, elapsed, polls, row_id)
)
# Cache the token
cache_token(conn, sitekey, pageurl, result["request"])
conn.commit()
conn.close()
return result["request"]
if result.get("request") != "CAPCHA_NOT_READY":
conn.execute(
"UPDATE captcha_solves SET status=?, error=?, polls=? WHERE id=?",
("error", result.get("request"), polls, row_id)
)
conn.commit()
conn.close()
return None
conn.execute(
"UPDATE captcha_solves SET status=?, polls=? WHERE id=?",
("timeout", polls, row_id)
)
conn.commit()
conn.close()
return None
Bộ nhớ đệm mã thông báo
def cache_token(conn, sitekey, pageurl, token, ttl_seconds=90):
expires_at = (datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)).isoformat()
conn.execute(
"INSERT OR REPLACE INTO token_cache (sitekey, pageurl, token, expires_at) "
"VALUES (?, ?, ?, ?)",
(sitekey, pageurl, token, expires_at)
)
def get_cached_token(conn, sitekey, pageurl):
now = datetime.now(timezone.utc).isoformat()
row = conn.execute(
"SELECT token FROM token_cache "
"WHERE sitekey=? AND pageurl=? AND used=0 AND expires_at > ? "
"ORDER BY expires_at ASC LIMIT 1",
(sitekey, pageurl, now)
).fetchone()
if row:
conn.execute(
"UPDATE token_cache SET used=1 WHERE token=?",
(row["token"],)
)
conn.commit()
return row["token"]
return None
Truy vấn phân tích
def get_stats(hours=24):
conn = get_db()
cutoff = (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
total = conn.execute(
"SELECT COUNT(*) FROM captcha_solves WHERE submitted_at >= ?", (cutoff,)
).fetchone()[0]
solved = conn.execute(
"SELECT COUNT(*) FROM captcha_solves WHERE submitted_at >= ? AND status='solved'",
(cutoff,)
).fetchone()[0]
avg_time = conn.execute(
"SELECT AVG(elapsed_ms) FROM captcha_solves "
"WHERE submitted_at >= ? AND status='solved'",
(cutoff,)
).fetchone()[0]
conn.close()
return {
"total": total,
"solved": solved,
"success_rate": (solved / total * 100) if total else 0,
"avg_time_ms": round(avg_time) if avg_time else 0
}
def cleanup_old_records(days=30):
conn = get_db()
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
conn.execute("DELETE FROM captcha_solves WHERE submitted_at < ?", (cutoff,))
conn.execute("DELETE FROM token_cache WHERE expires_at < ?",
(datetime.now(timezone.utc).isoformat(),))
conn.execute("VACUUM")
conn.commit()
conn.close()
Triển khai JavaScript
const Database = require("better-sqlite3");
const axios = require("axios");
const db = new Database(process.env.CAPTCHA_DB || "captcha_solves.db");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
db.pragma("journal_mode = WAL");
db.exec(`
CREATE TABLE IF NOT EXISTS captcha_solves (
id INTEGER PRIMARY KEY AUTOINCREMENT,
captcha_id TEXT, type TEXT NOT NULL, sitekey TEXT, pageurl TEXT,
status TEXT DEFAULT 'submitted', solution TEXT, error TEXT,
submitted_at TEXT DEFAULT (datetime('now')),
solved_at TEXT, elapsed_ms INTEGER, polls INTEGER DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_submitted ON captcha_solves(submitted_at);
`);
async function solveAndStore(sitekey, pageurl) {
const submittedAt = new Date().toISOString();
const insert = db.prepare(
"INSERT INTO captcha_solves (type, sitekey, pageurl, submitted_at) VALUES (?, ?, ?, ?)"
);
const { lastInsertRowid } = insert.run("recaptcha_v2", sitekey, pageurl, submittedAt);
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) {
db.prepare("UPDATE captcha_solves SET status=?, error=? WHERE id=?")
.run("error", submit.data.request, lastInsertRowid);
return null;
}
const captchaId = submit.data.request;
db.prepare("UPDATE captcha_solves SET captcha_id=?, status=? WHERE id=?")
.run(captchaId, "polling", lastInsertRowid);
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 elapsed = Date.now() - new Date(submittedAt).getTime();
db.prepare(
"UPDATE captcha_solves SET status=?, solution=?, solved_at=?, elapsed_ms=?, polls=? WHERE id=?"
).run("solved", poll.data.request, new Date().toISOString(), elapsed, polls, lastInsertRowid);
return poll.data.request;
}
if (poll.data.request !== "CAPCHA_NOT_READY") {
db.prepare("UPDATE captcha_solves SET status=?, error=?, polls=? WHERE id=?")
.run("error", poll.data.request, polls, lastInsertRowid);
return null;
}
}
db.prepare("UPDATE captcha_solves SET status=?, polls=? WHERE id=?")
.run("timeout", polls, lastInsertRowid);
return null;
}
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
database is locked |
Ghi đồng thời không có chế độ WAL | Thêm PRAGMA journal_mode=WAL |
| Tệp cơ sở dữ liệu phát triển lớn | Không có thói quen dọn dẹp | Chạy cleanup_old_records() hàng ngày |
| Truy vấn chậm trên tập dữ liệu lớn | Thiếu chỉ mục | Thêm chỉ mục trên submitted_at và type |
| Bộ nhớ đệm mã thông báo đã cũ | Mã thông báo hết hạn không được làm sạch | Dọn dẹp các mục đã hết hạn trước khi tra cứu |
Câu hỏi thường gặp
SQLite có thể xử lý việc giải CAPTCHA khối lượng lớn không?
SQLite xử lý lên tới ~1.000 ghi/second khi bật chế độ WAL. Đối với các thiết lập một máy thực hiện ít hơn 1.000 phép giải/hour, điều đó đáng tin cậy. Ngoài ra, hãy xem xét PostgreSQL hoặc MongoDB.
Tôi có nên lưu trữ mã thông báo giải pháp đầy đủ không?
Lưu trữ nó một thời gian ngắn để gỡ lỗi, sau đó để quy trình dọn dẹp xóa các bản ghi cũ. Mã thông báo sẽ hết hạn sau 90–120 giây nên chúng sẽ vô dụng sau đó.
Làm cách nào để di chuyển từ SQLite sang cơ sở dữ liệu sản xuất?
Xuất bằng sqlite3 captcha_solves.db ".dump" và nhập vào PostgreSQL hoặc MongoDB. Lược đồ dịch trực tiếp - SQLite là bước đệm tuyệt vời từ phát triển đến sản xuất.
bài viết liên quan
Các bước tiếp theo
Thêm tính năng theo dõi giải quyết gọn nhẹ vào quy trình của bạn —lấy khóa API CaptchaAI của bạnvà bắt đầu ghi kết quả ngay hôm nay.
Hướng dẫn liên quan: