Aynı sitekey'i saniyede onlarca kez çözüyorsanız, her istek için sıfırdan yeni bir token üretmek hem thread'lerinizi hem de değerli saniyelerinizi boşa harcar. Oysa hâlâ geçerli olan bir token, süresi dolana kadar defalarca kullanılabilir. Yapmanız gereken tek şey onu doğru süre boyunca bir yerde tutmak — ve Redis'in yerleşik TTL (time-to-live) mekanizması tam olarak bu iş için biçilmiş kaftandır: token'ı kaydeder, geri sayar ve süresi dolduğunda otomatik olarak siler.
Sayılara bakalım. reCAPTCHA token'ları yaklaşık 90–120 saniye, Cloudflare Turnstile token'ları ise 300 saniye civarında geçerli kalır. Bu pencerede aynı hedef için gelen her yeni istek, taze bir çözüm beklemek yerine önbellekteki token'ı alabilir. Bu rehberde Redis üzerinde üç şeyi kuruyoruz: süresi dolmaya yakın token'ları önbelleğe alma, hazır token havuzu tutma ve aynı anda uçuşta olan istekleri tekilleştirme.
Redis'te token yaşam döngüsü
Akışın tamamı tek bir karar etrafında döner: istek geldiğinde önbellekte geçerli bir token var mı? Varsa doğrudan onu döndürürüz; yoksa CaptchaAI API'sine gider, dönen token'ı güvenlik marjıyla birlikte Redis'e yazarız.
Solve Request → Check Redis → Cache Hit?
│ │
│ No │ Yes → Return cached token
▼
CaptchaAI API
│
▼
Store in Redis (TTL = token_lifetime - safety_margin)
│
▼
Return token
Kritik nokta şu satırda gizli: TTL = token_lifetime - safety_margin. Token'ı gerçek ömrünün son saniyesine kadar önbellekte tutarsanız, onu alan uygulamanın hedef siteye göndermeye vakti kalmadan süresi dolabilir. Bu yüzden her zaman bir güvenlik payı bırakıyoruz.
Python ile uygulama
Bağlantı ve TTL yapılandırması
Redis bağlantısını ortam değişkenlerinden kuruyoruz ve her CAPTCHA türü için ayrı bir TTL tanımlıyoruz. Değerler saniye cinsindendir ve her biri gerçek token ömründen daha kısadır — işte güvenlik marjı burada devreye giriyor.
import os
import time
import json
import redis
import requests
r = redis.Redis(
host=os.environ.get("REDIS_HOST", "localhost"),
port=int(os.environ.get("REDIS_PORT", 6379)),
db=0,
decode_responses=True
)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
# TTLs with safety margin (seconds before actual expiration)
TOKEN_TTLS = {
"recaptcha_v2": 80, # Actual: ~120s, cache for 80s
"recaptcha_v3": 80,
"hcaptcha": 80,
"turnstile": 250, # Actual: ~300s, cache for 250s
}
API anahtarınızı bu adımdan önce CaptchaAI panelinden edinmeniz ve CAPTCHAAI_API_KEY ortam değişkenine tanımlamanız gerekir.
Token önbelleğini okuma ve yazma
Önbellek anahtarını sitekey ve pageurl çiftinden üretiyoruz; böylece her hedef kendi kuyruğuna sahip olur. Okurken TTL'i bir kez daha kontrol ediyoruz: listede en az 10 saniye kalmamışsa token'ı kullanmıyoruz.
def cache_key(sitekey, pageurl):
"""Generate Redis key for a specific CAPTCHA target."""
return f"captcha:token:{sitekey}:{pageurl}"
def get_cached_token(sitekey, pageurl):
"""Pop a cached token from the queue."""
key = cache_key(sitekey, pageurl)
token = r.lpop(key)
if token:
# Verify TTL still valid on the list
ttl = r.ttl(key)
if ttl > 10: # At least 10 seconds remaining
return token
return None
def cache_token(sitekey, pageurl, token, captcha_type="recaptcha_v2"):
"""Push a solved token to the cache with appropriate TTL."""
key = cache_key(sitekey, pageurl)
ttl = TOKEN_TTLS.get(captcha_type, 80)
r.rpush(key, token)
r.expire(key, ttl)
Önbellek kontrollü çözüm akışı
Ana fonksiyon üç aşamalıdır: önce önbelleği kontrol et, sonra aynı hedefi başka bir worker zaten çözüyor mu diye bak, en son gerçekten API'ye git. İkinci adımdaki set(..., nx=True, ex=120) bir tekilleştirme kilidi kurar — böylece on worker aynı anda aynı CAPTCHA'yı çözmeye kalkışmaz, biri çözerken diğerleri sonucu sorgulayarak bekler.
def solve_recaptcha(sitekey, pageurl, captcha_type="recaptcha_v2"):
"""Solve reCAPTCHA with Redis cache check."""
# 1. Check cache
cached = get_cached_token(sitekey, pageurl)
if cached:
return {"solution": cached, "source": "cache"}
# 2. Check if solve is already in progress (dedup)
lock_key = f"captcha:lock:{sitekey}:{pageurl}"
if not r.set(lock_key, "1", nx=True, ex=120):
# Another worker is solving — wait for result
for _ in range(60):
time.sleep(2)
cached = get_cached_token(sitekey, pageurl)
if cached:
return {"solution": cached, "source": "cache_wait"}
return {"error": "TIMEOUT_WAITING_FOR_OTHER_WORKER"}
try:
# 3. Solve via 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:
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:
token = result["request"]
cache_token(sitekey, pageurl, token, captcha_type)
return {"solution": token, "source": "api"}
if result.get("request") != "CAPCHA_NOT_READY":
return {"error": result.get("request")}
return {"error": "TIMEOUT"}
finally:
r.delete(lock_key)
Görevi in.php uç noktasına method=userrecaptcha ile gönderip res.php üzerinden sonucu sorguluyoruz. Turnstile için aynı desen geçerlidir; yalnızca method parametresi turnstile olarak değişir, önbellek mantığı hiç değişmez.
Yüksek hacim için token havuzu
Saniyenin altında yanıt beklediğiniz yoğun hedeflerde tek tek çözüm beklemek yavaş kalır. Bunun yerine arka planda sürekli token üretip hazır tutan bir havuz çalıştırırsınız; isteğiniz geldiğinde havuzdan anında bir token alırsınız. Aşağıdaki TokenPool bir daemon thread ile havuzu istenen boyutta tutar.
import threading
class TokenPool:
def __init__(self, sitekey, pageurl, pool_size=5, captcha_type="recaptcha_v2"):
self.sitekey = sitekey
self.pageurl = pageurl
self.pool_size = pool_size
self.captcha_type = captcha_type
self.pool_key = f"captcha:pool:{sitekey}:{pageurl}"
self._running = False
def start(self):
self._running = True
thread = threading.Thread(target=self._refill_loop, daemon=True)
thread.start()
def stop(self):
self._running = False
def _refill_loop(self):
while self._running:
current = r.llen(self.pool_key)
if current < self.pool_size:
self._solve_and_add()
time.sleep(2)
def _solve_and_add(self):
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": self.sitekey,
"pageurl": self.pageurl,
"json": 1
})
data = resp.json()
if data.get("status") != 1:
return
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:
ttl = TOKEN_TTLS.get(self.captcha_type, 80)
r.rpush(self.pool_key, result["request"])
r.expire(self.pool_key, ttl)
return
if result.get("request") != "CAPCHA_NOT_READY":
return
def get_token(self):
return r.lpop(self.pool_key)
# Usage
pool = TokenPool("6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-", "https://example.com")
pool.start()
# When you need a token:
token = pool.get_token()
Havuz boyutunu (pool_size) trafiğinize göre ayarlayın: havuz sürekli boşalıyorsa ya boyutu artırın ya da doldurma thread'lerini çoğaltın. Bu noktada CaptchaAI'nin plan yapısı önem kazanır — faturalandırma çözüm başına değil, eşzamanlı thread başınadır ve her planda thread başına sınırsız çözüm gelir. Yani daha büyük bir havuzu beslemek istiyorsanız, tek yapmanız gereken daha fazla thread'e sahip bir plana geçmektir; sürpriz çözüm ücretleri çıkmaz.
Node.js ile uygulama
Aynı deseni ioredis ve axios ile Node.js tarafında da kurabilirsiniz. Mantık birebir aynıdır: önbelleği kontrol et, kilit al, çöz, yaz.
const Redis = require("ioredis");
const axios = require("axios");
const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379");
const API_KEY = process.env.CAPTCHAAI_API_KEY;
const TOKEN_TTLS = { recaptcha_v2: 80, recaptcha_v3: 80, hcaptcha: 80, turnstile: 250 };
function cacheKey(sitekey, pageurl) {
return `captcha:token:${sitekey}:${pageurl}`;
}
async function getCachedToken(sitekey, pageurl) {
const key = cacheKey(sitekey, pageurl);
const token = await redis.lpop(key);
if (token) {
const ttl = await redis.ttl(key);
if (ttl > 10) return token;
}
return null;
}
async function solveWithCache(sitekey, pageurl, type = "recaptcha_v2") {
// Check cache
const cached = await getCachedToken(sitekey, pageurl);
if (cached) return { solution: cached, source: "cache" };
// Dedup lock
const lockKey = `captcha:lock:${sitekey}:${pageurl}`;
const locked = await redis.set(lockKey, "1", "NX", "EX", 120);
if (!locked) {
for (let i = 0; i < 60; i++) {
await new Promise((r) => setTimeout(r, 2000));
const waitCached = await getCachedToken(sitekey, pageurl);
if (waitCached) return { solution: waitCached, source: "cache_wait" };
}
return { error: "TIMEOUT_WAITING" };
}
try {
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) 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 key = cacheKey(sitekey, pageurl);
const ttl = TOKEN_TTLS[type] || 80;
await redis.rpush(key, poll.data.request);
await redis.expire(key, ttl);
return { solution: poll.data.request, source: "api" };
}
if (poll.data.request !== "CAPCHA_NOT_READY") return { error: poll.data.request };
}
return { error: "TIMEOUT" };
} finally {
await redis.del(lockKey);
}
}
Redis anahtar tasarımı
Önbellek, kilit, havuz ve istatistikler için ayrı anahtar desenleri kullanmak sistemi hem okunur hem yönetilebilir kılar. Her deseni ne için kullandığınızı ve TTL'ini net tutun:
| Anahtar deseni | Amaç | TTL |
|---|---|---|
captcha:token:{sitekey}:{pageurl} |
Önbelleğe alınan çözülmüş token'lar | 80–250 sn (tür başına) |
captcha:lock:{sitekey}:{pageurl} |
Uçuştaki çözümler için tekilleştirme kilidi | 120 sn |
captcha:pool:{sitekey}:{pageurl} |
Hazır token havuzu | 80–250 sn |
captcha:stats:{date} |
Günlük çözüm sayaçları | 7 gün |
Önbellek performansını izleme
Önbelleğin işe yarayıp yaramadığını görmek için isabet oranını (hit rate) takip edin. Redis'in keyspace_hits ve keyspace_misses sayaçları bu hesabın hazır girdisidir.
def cache_stats():
info = r.info("stats")
hits = info.get("keyspace_hits", 0)
misses = info.get("keyspace_misses", 0)
total = hits + misses
return {
"hit_rate": f"{hits / total * 100:.1f}%" if total else "0%",
"hits": hits,
"misses": misses,
"active_keys": r.dbsize()
}
İsabet oranı düşükse ya TTL'leriniz gereğinden kısadır (token'lar kullanılmadan siliniyor) ya da trafiğiniz aynı sitekey'de yoğunlaşmıyordur — ikinci durumda önbellekten zaten fazla fayda beklememelisiniz. Uzun vadeli çözüm geçmişini ve analitiği ayrı tutmak isterseniz, CAPTCHA çözüm geçmişini MongoDB'de saklama rehberi bu işi tamamlar.
Sık karşılaşılan sorunlar
Önbellek katmanı eklerken en çok karşılaşılan dört durum ve pratik çözümleri:
| Sorun | Sebep | Düzeltme |
|---|---|---|
| Önbellekteki token hedef site tarafından reddedildi | Token kullanılmadan önce süresi doldu | TTL güvenlik marjını artırın veya token'ı hemen tüketin |
| Kilit hiç açılmadı | Worker çözüm sırasında çöktü | Kilit anahtarındaki TTL (120 sn) otomatik temizler |
| Token havuzu sürekli boş | Çözüm süresi doldurma hızını aşıyor | Havuz boyutunu artırın veya doldurma thread'i ekleyin |
| Redis belleği şişiyor | Anahtarlarda TTL yok | Her anahtarın bir TTL'si olmalı; redis-cli --bigkeys ile kontrol edin |
Çok worker'lı kurulumlarda kilitlerin ve oturum durumunun nasıl paylaşıldığını derinlemesine görmek için dağıtık worker'lar arası oturum durumu rehberine ve çözüm isteklerini veritabanı kilidiyle tekilleştirme rehberine bakabilirsiniz.
Türkiye'deki ekipler için birkaç not
Türkiye'de otomasyon ve veri kazıma projeleri çoğunlukla e-ticaret ve fintech entegrasyonları etrafında yoğunlaşır; ödeme ve satın alma akışlarının QA testleri sırasında aynı doğrulama sayfası defalarca tetiklenir. Böyle bir test senaryosunda Redis önbelleği, her koşuda yeni token beklemenizi ortadan kaldırıp test sürelerini kısaltır. Bir uyarı: kazıdığınız veri kişisel veri içeriyorsa KVKK kapsamına girer; token önbelleğe alma tekniğini yalnızca yetkiniz olan, kendi sahip olduğunuz QA ve veri toplama akışlarında kullanın.
USD bazlı, thread başına sabit fiyatlandırma da Türkiye'deki geliştiriciler için pratik bir avantajdır: kur dalgalanmasından bağımsız olarak aylık maliyetiniz öngörülebilir kalır. Örneğin küçük bir önbellekli iş akışına BASIC ($15/ay, 5 thread) ile başlayabilir, havuzunuz büyüdükçe daha yüksek thread'li bir plana geçebilirsiniz.
Sık sorulan sorular
Redis mi yoksa uygulama içi bellek mi kullanmalıyım?
Tek bir süreçte çalışıyorsanız uygulama içi bir sözlük yeterli olabilir. Ama birden fazla worker veya sunucu token'ları paylaşacaksa Redis'in merkezî yapısı ve TTL desteği şart olur — herkes aynı önbelleği okur ve doldurur.
TTL güvenlik marjını nasıl belirlerim?
Token'ın gerçek ömründen 30–40 saniye çıkarın. reCAPTCHA token'ları yaklaşık 120 saniye geçerlidir, bu yüzden 80 saniyelik bir önbellek makuldür; bu size token'ı hedefe göndermek için yaklaşık 40 saniyelik güvenli bir pencere bırakır.
Aynı token iki ayrı worker'a verilirse ne olur?
lpop her token'ı listeden atomik olarak çeker, dolayısıyla aynı token iki kez dağıtılmaz. Asıl risk aynı CAPTCHA'nın iki kez çözülmesidir; tekilleştirme kilidi tam da bunu engeller ve gereksiz çözümleri önler.
Önbellekten gelen token neden reddedildi?
Neredeyse her zaman zamanlama sorunudur: token, uygulamanız onu kullanmadan önce hedef sitede geçerliliğini yitirmiştir. TTL güvenlik marjını büyütün veya get_cached_token içindeki 10 saniyelik eşiği yükseltin.
Sonraki adımlar
Redis destekli token önbelleğe alma ile CAPTCHA işlem hattınızı hızlandırın — CaptchaAI API anahtarınızı alın ve ilk önbelleğinizi bugün kurun.
İlgili rehberler: