Memecahkan CAPTCHA membutuhkan waktu dan uang. Jika token yang sama dapat digunakan kembali dalam jangka waktu validitasnya, caching menghilangkan panggilan API yang berlebihan. Panduan ini mencakup token mana yang dapat di-cache, berapa lama bertahan, dan cara menerapkan caching dengan aman.
Masa Pakai Token Berdasarkan Tipe CAPTCHA
| Tipe CAPTCHA | Masa pakai token | Dapat di-cache? | Catatan |
|---|---|---|---|
| reCAPTCHA v2 | ~120 detik | Terbatas | Umumnya sekali pakai per situs |
| reCAPTCHA v3 | ~120 detik | Terbatas | Skor bisa bervariasi per permintaan |
| reCAPTCHA Enterprise | ~120 detik | Tidak | Spesifik tindakan, sekali pakai |
| Cloudflare Turnstile | ~300 detik | Ya, dalam jendela waktu | Token bisa digunakan kembali hingga kedaluwarsa |
| Cloudflare Challenge | qa_validation_cookie ~15–30 menit |
Ya | Cookie bisa digunakan kembali untuk sesi |
| Image OCR | N/A (hasil teks) | Ya | Hasil tidak pernah kedaluwarsa |
| GeeTest v3 | ~60 detik | Tidak | Spesifik tantangan |
Wawasan utama: Cloudflare Challenge (qa_validation_cookie) dan Image OCR adalah yang paling mudah di-cache. Token reCAPTCHA memiliki jendela pendek dan sering kali hanya sekali pakai.
Saat cache berfungsi
Caching efektif ketika:
- Halaman yang sama, beberapa permintaan – misalnya, mengirimkan formulir yang sama beberapa kali
- Cloudflare
qa_validation_cookie— satu penyelesaian akan membuka seluruh sesi - OCR Massal — gambar yang sama muncul berulang kali (misalnya, CAPTCHA statis)
- validasi awal - selesaikan token sebelum dibutuhkan
Caching tidak berfungsi ketika:
- Situs ini memvalidasi setiap token hanya sekali
- Token terikat pada tindakan atau sesi tertentu
- Token sudah habis masa berlakunya
Python - cache dalam memori
import time
import hashlib
from typing import Optional
import requests
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
class TokenCache:
def __init__(self):
self.cache = {}
def _key(self, method: str, params: dict) -> str:
# Cache key from method + stable params
stable = {k: v for k, v in sorted(params.items())
if k not in ("key", "json")}
raw = f"{method}:{stable}"
return hashlib.sha256(raw.encode()).hexdigest()[:16]
def get(self, method: str, params: dict) -> Optional[str]:
key = self._key(method, params)
entry = self.cache.get(key)
if entry and entry["expires_at"] > time.time():
print(f"Cache HIT: {key}")
return entry["token"]
if entry:
del self.cache[key]
return None
def set(self, method: str, params: dict, token: str, ttl: int):
key = self._key(method, params)
self.cache[key] = {
"token": token,
"expires_at": time.time() + ttl,
}
print(f"Cached: {key} (TTL: {ttl}s)")
def invalidate(self, method: str, params: dict):
key = self._key(method, params)
self.cache.pop(key, None)
def cleanup(self):
now = time.time()
expired = [k for k, v in self.cache.items() if v["expires_at"] <= now]
for k in expired:
del self.cache[k]
# TTL per CAPTCHA type
TTL_MAP = {
"userrecaptcha": 100, # 120s lifetime, 20s safety margin
"turnstile": 240, # 300s lifetime, 60s margin
"cloudflare_challenge": 900,# 15min lifetime, 5min margin
"base64": 86400, # OCR result never expires — cache 24h
}
class CachedSolver:
def __init__(self, api_key: str):
self.api_key = api_key
self.cache = TokenCache()
def solve(self, method: str, params: dict) -> str:
# Check cache first
cached = self.cache.get(method, params)
if cached:
return cached
# Solve via API
token = self._api_solve(method, params)
ttl = TTL_MAP.get(method, 60)
self.cache.set(method, params, token, ttl)
return token
def _api_solve(self, method: str, params: dict) -> str:
data = {
"key": self.api_key,
"method": method,
"json": 1,
**params
}
resp = requests.post(SUBMIT_URL, data=data, timeout=15)
result = resp.json()
if result.get("status") != 1:
raise Exception(result.get("error_text", result.get("request")))
task_id = result["request"]
return self._poll(task_id)
def _poll(self, task_id: str, max_wait: int = 120) -> str:
elapsed = 0
while elapsed < max_wait:
time.sleep(5)
elapsed += 5
resp = requests.get(RESULT_URL, params={
"key": self.api_key,
"action": "get",
"id": task_id,
"json": 1
}, timeout=10)
result = resp.json()
if result.get("status") == 1:
return result["request"]
if result.get("request") == "CAPCHA_NOT_READY":
continue
raise Exception(result.get("error_text", result.get("request")))
raise Exception(f"Timeout: {task_id}")
# Usage
solver = CachedSolver(api_key="YOUR_API_KEY")
# First call — hits API
token1 = solver.solve("turnstile", {
"sitekey": "0x4AAAA-SITEKEY",
"pageurl": "https://example.com"
})
print(f"Token 1: {token1[:40]}...")
# Second call within TTL — cache hit, no API call
token2 = solver.solve("turnstile", {
"sitekey": "0x4AAAA-SITEKEY",
"pageurl": "https://example.com"
})
print(f"Token 2: {token2[:40]}...")
print(f"Same token: {token1 == token2}") # True
Node.js — cache dalam memori
const axios = require("axios");
const crypto = require("crypto");
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const TTL_MAP = {
userrecaptcha: 100,
turnstile: 240,
cloudflare_challenge: 900,
base64: 86400,
};
class TokenCache {
constructor() {
this.cache = new Map();
}
_key(method, params) {
const stable = Object.entries(params)
.filter(([k]) => k !== "key" && k !== "json")
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => `${k}=${v}`)
.join("&");
return crypto.createHash("sha256").update(`${method}:${stable}`).digest("hex").slice(0, 16);
}
get(method, params) {
const key = this._key(method, params);
const entry = this.cache.get(key);
if (entry && entry.expiresAt > Date.now()) {
console.log(`Cache HIT: ${key}`);
return entry.token;
}
if (entry) this.cache.delete(key);
return null;
}
set(method, params, token, ttlMs) {
const key = this._key(method, params);
this.cache.set(key, { token, expiresAt: Date.now() + ttlMs });
console.log(`Cached: ${key} (TTL: ${ttlMs / 1000}s)`);
}
}
class CachedSolver {
constructor(apiKey) {
this.apiKey = apiKey;
this.cache = new TokenCache();
}
async solve(method, params) {
const cached = this.cache.get(method, params);
if (cached) return cached;
const token = await this._apiSolve(method, params);
const ttl = (TTL_MAP[method] || 60) * 1000;
this.cache.set(method, params, token, ttl);
return token;
}
async _apiSolve(method, params) {
const resp = await axios.post(SUBMIT_URL, null, {
params: { key: this.apiKey, method, json: 1, ...params },
timeout: 15000,
});
if (resp.data.status !== 1) {
throw new Error(resp.data.error_text || resp.data.request);
}
return this._poll(resp.data.request);
}
async _poll(taskId, maxWait = 120000) {
let elapsed = 0;
while (elapsed < maxWait) {
await new Promise((r) => setTimeout(r, 5000));
elapsed += 5000;
const resp = await axios.get(RESULT_URL, {
params: { key: this.apiKey, action: "get", id: taskId, json: 1 },
timeout: 10000,
});
if (resp.data.status === 1) return resp.data.request;
if (resp.data.request === "CAPCHA_NOT_READY") continue;
throw new Error(resp.data.error_text || resp.data.request);
}
throw new Error("Timeout");
}
}
// Usage
(async () => {
const solver = new CachedSolver("YOUR_API_KEY");
const token1 = await solver.solve("turnstile", {
sitekey: "0x4AAAA-SITEKEY",
pageurl: "https://example.com",
});
console.log(`Token 1: ${token1.slice(0, 40)}...`);
const token2 = await solver.solve("turnstile", {
sitekey: "0x4AAAA-SITEKEY",
pageurl: "https://example.com",
});
console.log(`Token 2: ${token2.slice(0, 40)}...`);
console.log(`Same token: ${token1 === token2}`);
})();
Redis cache untuk sistem terdistribusi
Untuk penyiapan multipekerja, gunakan Redis alih-alih cache dalam memori:
import redis
import json
r = redis.Redis(host="localhost", port=6379, db=0)
def cache_token(method, params, token, ttl):
key = f"captcha:{method}:{hash(frozenset(params.items()))}"
r.setex(key, ttl, token)
def get_cached_token(method, params):
key = f"captcha:{method}:{hash(frozenset(params.items()))}"
return r.get(key)
Redis secara otomatis menangani masa berlaku TTL dan bekerja di berbagai proses.
Pola validasi awal
Pecahkan token sebelum dibutuhkan. Simpan buffer token siap pakai:
from collections import deque
from threading import Thread
token_buffer = deque(maxlen=5)
def pre_solve_worker(solver, method, params):
while True:
if len(token_buffer) < 3:
try:
token = solver._api_solve(method, params)
ttl = TTL_MAP.get(method, 60)
token_buffer.append({
"token": token,
"expires_at": time.time() + ttl
})
except Exception as e:
print(f"validasi awal di staging failed: {e}")
time.sleep(2)
# Start validasi staging in background
thread = Thread(
target=pre_solve_worker,
args=(solver, "turnstile", {"sitekey": "0x4AAAA-KEY", "pageurl": "https://example.com"}),
daemon=True
)
thread.start()
# Consume token QA staging
def get_presolved():
while token_buffer:
entry = token_buffer.popleft()
if entry["expires_at"] > time.time():
return entry["token"]
return None
Aturan Invalidasi Cache
| Pemicu | Tindakan |
|---|---|
| Token ditolak oleh situs target | Invalidasi dan solve ulang |
| TTL sudah kedaluwarsa | Dihapus otomatis dari cache |
| Proxy berubah | Invalidasi token Cloudflare (terikat IP) |
| Konfigurasi CAPTCHA situs diperbarui | Hapus semua token yang di-cache untuk situs tersebut |
Pemecahan Masalah
| Masalah | Penyebab | Solusi |
|---|---|---|
| Token yang di-cache ditolak | Token kedaluwarsa atau sekali pakai | Kurangi TTL atau nonaktifkan cache untuk tipe tersebut |
| Cache tidak pernah hit | Params berbeda antar panggilan | Normalisasi param sebelum di-hash |
| Token basi di Redis | TTL terlalu panjang | Turunkan TTL dengan safety margin |
| Memory terus tumbuh | Tidak ada pembersihan | Panggil cleanup() secara berkala atau gunakan Redis dengan TTL |
Pertanyaan Umum
Bisakah saya menyimpan token reCAPTCHA v2 dalam cache?
Terkadang. Banyak situs hanya menerima token satu kali. Uji dengan mengirimkan token yang sama dua kali – jika pengiriman kedua berhasil, cache akan berfungsi untuk situs tersebut.
Berapa banyak yang bisa dihemat dari cache?
Untuk Cloudflare Challenge, satu penyelesaian dapat mencakup keseluruhan sesi 15–30 menit. Hal ini dapat mengurangi biaya sebesar 90%+ untuk scraping frekuensi tinggi pada domain yang sama.
Apakah penyelesaian awal layak dilakukan?
Ya, jika saluran pipa Anda memiliki permintaan yang dapat diprediksi. Penyelesaian awal menghilangkan waktu tunggu dengan mengorbankan potensi pemborosan token jika permintaan turun.
Optimalkan Biaya CAPTCHA dengan CaptchaAI
Mulai menyimpan token dalam cache di captchaai.com.
Panduan Terkait
- Pengaturan Timeout Kustom per Tipe CAPTCHA
- Referensi Metode pengaturan token
- Membangun Antrian Pemecahan CAPTCHA di Python