Birden fazla çalışan aynı site için CAPTCHA'ları çözdüğünde ortak bir sorunu paylaşırlar: her çalışanın kendi oturumu vardır. Hedef site farklı çerezleri, farklı IP'leri ve farklı tarayıcı parmak izlerini görür. Oturum durumu yönetimi, bağlamı çalışanlar arasında senkronize ederek çözümlerin tutarlı olmasını ve hedef sitenin tutarlı oturumlar görmesini sağlar.
Oturum Durumu Sorunu
Worker 1 → Login → Solve CAPTCHA → Get cookie A → Submit form ✅
Worker 2 → New session → Solve CAPTCHA → Get cookie B → Submit form ✅
Worker 3 → Reuse cookie A? → Cookie expired → Solve CAPTCHA → Fail ❌
Paylaşılan durum olmadan çalışanlar, süresi dolmuş oturumlardaki çözümleri boşa harcar ve hedef sitelerin algılayabileceği tutarsız davranışlar üretir.
Oturum Durumu Neleri İçerir?
| Durum Bileşeni | Ömür boyu | Paylaşım Stratejisi |
|---|---|---|
| Kimlik doğrulama çerezleri | Dakikalardan saatlere | TTL ile Redis |
| CAPTCHA belirteçleri | 90–300 saniye | Redis listesi (kısa TTL) |
qa_session_cookie çerezleri |
~30 dakika | Redis karma |
| CSRF belirteçleri | Sayfa yükü başına | Paylaşmayın; her çalışanın kendine ait bir hakkı var |
| Tarayıcı test profil yapılandırması | Kalıcı | Çalışma zamanı durumu değil yapılandırma |
| Vekil ataması | Oturum başına | Redis destekli proxy havuzu |
Mimarlık
┌──────────────────────────────────────┐
│ Session State Store │
│ (Redis) │
│ │
│ cookies:{domain} → Hash │
│ tokens:{sitekey} → List │
│ proxies:pool → Set │
│ locks:{domain}:{worker} → String │
└─────┬──────────┬──────────┬──────────┘
│ │ │
┌───▼───┐ ┌──▼────┐ ┌──▼────┐
│Worker1│ │Worker2│ │Worker3│
└───────┘ └───────┘ └───────┘
Python Uygulaması
Oturum Deposu
import os
import json
import time
import redis
import requests
from datetime import datetime, timezone
r = redis.Redis(
host=os.environ.get("REDIS_HOST", "localhost"),
port=int(os.environ.get("REDIS_PORT", 6379)),
decode_responses=True
)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
class SessionStore:
"""Shared session state across distributed workers."""
def __init__(self, domain):
self.domain = domain
self.cookie_key = f"session:cookies:{domain}"
self.token_key = f"session:tokens:{domain}"
def save_cookies(self, cookies, ttl=1800):
"""Store cookies from a successful session."""
cookie_data = {name: value for name, value in cookies.items()}
r.hset(self.cookie_key, mapping=cookie_data)
r.expire(self.cookie_key, ttl)
def get_cookies(self):
"""Retrieve shared cookies."""
cookies = r.hgetall(self.cookie_key)
return cookies if cookies else None
def save_token(self, sitekey, token, ttl=80):
"""Store a solved CAPTCHA token."""
key = f"{self.token_key}:{sitekey}"
r.rpush(key, token)
r.expire(key, ttl)
def get_token(self, sitekey):
"""Pop a cached CAPTCHA token."""
key = f"{self.token_key}:{sitekey}"
return r.lpop(key)
def acquire_session_lock(self, worker_id, ttl=300):
"""Ensure only one worker manages the session at a time."""
lock_key = f"session:lock:{self.domain}"
return r.set(lock_key, worker_id, nx=True, ex=ttl)
def release_session_lock(self, worker_id):
"""Release session lock if this worker holds it."""
lock_key = f"session:lock:{self.domain}"
current = r.get(lock_key)
if current == worker_id:
r.delete(lock_key)
Paylaşılan Duruma Sahip Çalışan
class CaptchaWorker:
def __init__(self, worker_id, domain):
self.worker_id = worker_id
self.store = SessionStore(domain)
self.session = requests.Session()
def setup_session(self):
"""Load shared cookies into this worker's session."""
cookies = self.store.get_cookies()
if cookies:
for name, value in cookies.items():
self.session.cookies.set(name, value)
return True
return False
def solve_captcha(self, sitekey, pageurl):
"""Solve with token cache and session sharing."""
# Check for cached token
cached = self.store.get_token(sitekey)
if cached:
return {"solution": cached, "source": "cache"}
# 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"]
self.store.save_token(sitekey, token)
return {"solution": token, "source": "api"}
if result.get("request") != "CAPCHA_NOT_READY":
return {"error": result.get("request")}
return {"error": "TIMEOUT"}
def process_page(self, url, sitekey):
"""Full workflow: setup session → solve CAPTCHA → submit."""
# Load shared session
self.setup_session()
# Solve CAPTCHA
result = self.solve_captcha(sitekey, url)
if "error" in result:
return result
# Submit form with token
response = self.session.post(url, data={
"g-recaptcha-response": result["solution"]
})
# Share resulting cookies
self.store.save_cookies(dict(self.session.cookies))
return {"status": response.status_code, "source": result["source"]}
Proxy Havuzu Yönetimi
class ProxyPool:
"""Distribute proxies across workers to avoid IP conflicts."""
def __init__(self, proxies):
self.pool_key = "session:proxy_pool"
self.assigned_key = "session:proxy_assigned"
# Initialize pool
for proxy in proxies:
r.sadd(self.pool_key, proxy)
def acquire_proxy(self, worker_id, ttl=600):
"""Assign an unused proxy to a worker."""
# Check if worker already has one
existing = r.hget(self.assigned_key, worker_id)
if existing:
return existing
# Pop from available pool
proxy = r.spop(self.pool_key)
if proxy:
r.hset(self.assigned_key, worker_id, proxy)
r.expire(self.assigned_key, ttl)
return proxy
return None
def release_proxy(self, worker_id):
"""Return proxy to the pool."""
proxy = r.hget(self.assigned_key, worker_id)
if proxy:
r.sadd(self.pool_key, proxy)
r.hdel(self.assigned_key, worker_id)
JavaScript Uygulaması
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;
class SessionStore {
constructor(domain) {
this.domain = domain;
this.cookieKey = `session:cookies:${domain}`;
this.tokenKey = `session:tokens:${domain}`;
}
async saveCookies(cookies, ttl = 1800) {
const entries = Object.entries(cookies).flat();
if (entries.length > 0) {
await redis.hset(this.cookieKey, ...entries);
await redis.expire(this.cookieKey, ttl);
}
}
async getCookies() {
return await redis.hgetall(this.cookieKey);
}
async saveToken(sitekey, token, ttl = 80) {
const key = `${this.tokenKey}:${sitekey}`;
await redis.rpush(key, token);
await redis.expire(key, ttl);
}
async getToken(sitekey) {
return await redis.lpop(`${this.tokenKey}:${sitekey}`);
}
async acquireLock(workerId, ttl = 300) {
const result = await redis.set(`session:lock:${this.domain}`, workerId, "NX", "EX", ttl);
return result === "OK";
}
async releaseLock(workerId) {
const current = await redis.get(`session:lock:${this.domain}`);
if (current === workerId) await redis.del(`session:lock:${this.domain}`);
}
}
async function workerSolve(store, sitekey, pageurl) {
const cached = await store.getToken(sitekey);
if (cached) return { solution: cached, source: "cache" };
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) {
await store.saveToken(sitekey, poll.data.request);
return { solution: poll.data.request, source: "api" };
}
if (poll.data.request !== "CAPCHA_NOT_READY") return { error: poll.data.request };
}
return { error: "TIMEOUT" };
}
Devlet Yönetim Modelleri
| Desen | Ne Zaman Kullanılmalı |
|---|---|
| Oturum kilidi | Bir çalışan oturum açma işlemini yönetir, diğerleri ise çerezleri kullanır |
| Jeton havuzu | Yüksek verimlilik: belirteçleri önceden çözün ve dağıtın |
| Çerez paylaşımı | Çalışanların kimliği doğrulanmış oturumlara ihtiyacı var |
| Vekil yakınlığı | Hedef site IP oturumu bağlamayı izler |
Sorun giderme
| Sorun | Sebep | Düzeltme |
|---|---|---|
| Çalışanlar farklı oturumlar alıyor | Redis aracılığıyla paylaşılmayan çerezler | Başarılı isteklerden sonra save_cookies'nin çağrıldığını doğrulayın |
| Tokenın süresi, diğer çalışanlar onu kullanmadan önce doldu | TTL çok uzun veya ağ gecikmesi | TTL güvenlik marjını azaltın; Jetonları alındıktan sonraki 10 saniye içinde kullanın |
| Oturum kilidi hiçbir zaman yayınlanmadı | İşçi kaza yaptı | Kilit anahtarının otomatik serbest bırakılmasında TTL (varsayılan 300 saniye) |
| Hedef site çalışanları engelliyor | Tüm çalışanlar aynı proxy'yi kullanıyor | Çalışan başına benzeşimle proxy havuzunu kullanın |
SSS
Her çalışan çerezleri paylaşmalı mı?
Yalnızca kimliği doğrulanmış oturumlar gerektiren siteler için. Durum bilgisi olmayan CAPTCHA çözümü için (site anahtarını gönderin – jeton alın), çalışanların paylaşılan çerezlere ihtiyacı yoktur; yalnızca paylaşılan jetonlar yeterlidir.
Oturumun sona ermesini nasıl halledebilirim?
Redis TTL'yi oturum ömründen biraz daha kısa bir süreye ayarlayın. Çerezlerin süresi dolduğunda, bir çalışan oturum kilidini alır, yeniden kimlik doğrulaması yapar ve diğerleri için yeni çerezler saklar.
Peki ya tarayıcı tabanlı oturumlar (Puppeteer/Playwright)?
Tarayıcı çerezlerini page.cookies() ile serileştirin ve Redis'te saklayın. Diğer çalışanlar onlara page.setCookie() yüklüyor. Bu, ayrı tarayıcı örneklerinde ve makinelerde çalışır.
Sonraki Adımlar
Dağıtılmış CAPTCHA çalışanlarınızı verimli bir şekilde koordine edin;CaptchaAI API anahtarınızı alın.
İlgili kılavuzlar: