Tutorials

CAPTCHA token'larını yeniden kullanım için önbelleğe alma

Kısa cevap: her CAPTCHA token'ı önbelleğe alınmaz. En çok kazancı Cloudflare oturum çerezi ile OCR sonuçları verir; reCAPTCHA token'ları ise çoğu sitede tek kullanımlıktır. Her tekrar eden çözüm çağrısı hem thread sürenizi hem de bakiyenizi harcadığından, doğru token'ları yeniden kullanmak doğrudan maliyet düşürür. Bu rehber hangi token'ların güvenle yeniden kullanılabileceğini, ne kadar dayandıklarını ve önbelleğe almayı üretimde nasıl uygulayacağınızı adım adım gösterir.

Kısa özet:

  • En yüksek kazanç: Cloudflare oturum çerezi (qa_session_cookie) ve OCR sonuçları — tek çözüm uzun süre iş görür.
  • Sınırlı kazanç: reCAPTCHA v2/v3 token'ları — kısa ömürlü ve çoğu sitede tek kullanımlık.
  • Anahtar kural: TTL'yi her zaman token'ın gerçek ömründen kısa tutun.

Önbelleğe alma ne zaman işe yarar, ne zaman yaramaz?

Her şeyden önce iş yükünüzün önbelleğe almaya uygun olup olmadığına karar verin. Önbelleğe alma şu durumlarda kazanç sağlar:

  1. Aynı sayfa, çok sayıda istek — örneğin aynı formun art arda gönderilmesi
  2. Cloudflare qa_session_cookie — tek çözüm tüm oturumun kilidini açar
  3. Toplu OCR — aynı görsel tekrar tekrar geliyorsa (ör. statik CAPTCHA)
  4. Hazır token havuzu — token'ları talep gelmeden hazırlamak

Şu durumlarda ise işe yaramaz:

  • Site her token'ı yalnızca bir kez doğruluyorsa
  • Token belirli bir aksiyona veya oturuma bağlıysa
  • Token'ın süresi zaten dolmuşsa

Somut bir örnek: Türkiye'deki bir e-ticaret ekibi, staging ortamında ödeme adımını (staging.example.com/qa-login) gün boyunca defalarca test ediyorsa, Cloudflare oturum çerezini tek seferde çözüp yeniden kullanmak çözüm çağrılarını büyük ölçüde azaltır. CaptchaAI thread bazlı faturalandırıldığından (örneğin BASIC: $15/ay, 5 thread), her önbellek isabeti bir thread'i anında serbest bırakır ve aynı bütçeyle daha fazla test çalıştırırsınız. Kişisel veri içeren sayfalarda kazımanın KVKK kapsamına girdiğini unutmayın; önbelleğe almayı yetkili QA ve test akışlarıyla sınırlayın.


Güvenli bir TTL nasıl seçilir?

Önbelleğin altın kuralı basit: TTL'yi (önbellek ömrünü) token'ın gerçek geçerlilik süresinden kısa tutun. Amaç, süresi dolmak üzere olan bir token'ı yeniden kullanmamaktır. Her tür için token ömründen bir güvenlik marjı çıkarın — kesin ömürleri uygulamadan hemen önceki referans tablosunda bulacaksınız.

  • Geniş marj reddedilen token riskini düşürür, ama isabet oranını da azaltır.
  • Dar marj daha çok isabet verir, ama süresi dolmuş token döndürme riskini artırır.
  • Başlangıç noktası olarak ömrün yaklaşık %20'sini marj bırakın; reddedilen token oranını ölçtükçe ayarlayın.

Gerçek bir maliyet örneği

Aynı alan adında saatte 1.000 istek çalıştırdığınızı ve Cloudflare oturum çerezini 20 dakika boyunca yeniden kullanabildiğinizi varsayalım. Önbelleksiz her istek ayrı bir çözüm çağrısı demektir; önbellekle saatte yalnızca birkaç çözüm yeterli olur.

Yaklaşım Saatlik çözüm çağrısı Göreli maliyet
Önbelleksiz ~1.000 Taban
20 dk oturum çerezi önbelleği ~3 Belirgin biçimde düşük

Rakamlar iş yükünüze göre değişir; kendi trafiğinizde ölçün ve doğrulayın. Buradaki amaç kesin bir oran vaat etmek değil, kazancın yüksek frekanslı ve tekrar eden yüklerde nereden geldiğini göstermektir.


Token'ı ne zaman geçersiz kılmalısınız?

Önbelleğe güvenmeden önce, bir token'ın ne zaman "ölü" sayılacağını bilin:

  • Hedef site token'ı reddetti — hemen geçersiz kılın ve yeniden çözün.
  • TTL doldu — önbellekten otomatik kaldırılır.
  • Proxy veya çıkış IP'si değişti — Cloudflare token'ları IP'ye bağlıdır; geçersiz kılın.
  • Site CAPTCHA yapılandırmasını değiştirdi — o siteye ait tüm önbelleği temizleyin.

Uygulama referansı: CAPTCHA türüne göre token ömürleri

Kod yazmadan önce, TTL değerlerinizi aşağıdaki token ömürlerinden türetin:

CAPTCHA türü Token ömrü Önbelleğe alınabilir mi? Notlar
reCAPTCHA v2 ~120 saniye Sınırlı Çoğu sitede tek kullanımlık
reCAPTCHA v3 ~120 saniye Sınırlı Puan istekten isteğe değişebilir
reCAPTCHA Enterprise ~120 saniye Hayır Aksiyona özel, tek kullanımlık
Cloudflare Turnstile ~300 saniye Evet, pencere içinde Token süresi dolana kadar yeniden kullanılabilir
Cloudflare doğrulama akışı qa_session_cookie ~15–30 dk. Evet Çerez oturum boyunca yeniden kullanılabilir
Görüntü OCR'si Yok (metin sonucu) Evet Sonuç hiçbir zaman sona ermez
GeeTest v3 ~60 saniye Hayır Doğrulamaya özel

Önemli: En çok kazancı Cloudflare doğrulama akışı (qa_session_cookie) ve Görüntü OCR verir. reCAPTCHA token'larının pencereleri kısadır ve çoğu zaman tek kullanımlıktır.


Python ile bellek içi token önbelleği

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 ile bellek içi token önbelleği

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}`);
})();

Dağıtık sistemler için Redis token önbelleği

Çok çalışanlı (multi-worker) kurulumlarda bellek içi önbellek her süreçte ayrı yaşar. Süreçler arasında paylaşılan ortak bir önbellek için Redis kullanın:

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, TTL sona ermesini otomatik yönetir ve token'ları tüm süreçler arasında paylaşır.


Test token havuzu: token'ları önceden hazırlama

Yoğun ve öngörülebilir yüklerde token'ları talep gelmeden hazırlayıp küçük bir havuzda tutabilirsiniz. Bu, istek anındaki bekleme süresini ortadan kaldırır — karşılığında, talep düşerse birkaç token boşa gidebilir.

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"Ön QA testi failed: {e}")
        time.sleep(2)

# Start ön QA testir in background
thread = Thread(
    target=pre_solve_worker,
    args=(solver, "turnstile", {"sitekey": "0x4AAAA-KEY", "pageurl": "https://example.com"}),
    daemon=True
)
thread.start()

# Consume ön QA testid tokens
def get_presolved():
    while token_buffer:
        entry = token_buffer.popleft()
        if entry["expires_at"] > time.time():
            return entry["token"]
    return None

Önbellek isabet oranını nasıl ölçersiniz?

Önbelleği ölçmeden çalıştırmayın. En basit yöntem, her get çağrısında isabet (HIT) ve ıskalama (MISS) sayaçlarını tutmaktır. Yukarıdaki Python örneğindeki Cache HIT günlükleri bunun başlangıç noktasıdır: bir gün boyunca HIT ve MISS satırlarını sayın, oranı çıkarın ve TTL'yi buna göre ayarlayın.

  • İsabet oranı düşükse (ör. %20'nin altında) parametreleriniz çağrılar arasında farklılaşıyor ya da TTL'niz çok kısa demektir.
  • Reddedilen token oranı yüksekse TTL'niz token ömrüne fazla yakın; marjı biraz genişletin.

Sorun giderme

Sorun Sebep Çözüm
Önbellekteki token reddedildi Token'ın süresi dolmuş ya da tek kullanımlık Bu tür için TTL'yi düşürün veya önbelleği kapatın
Önbellek hiç isabet etmiyor Parametreler çağrılar arasında farklı Karma (hash) öncesi parametreleri normalleştirin
Redis'te bayat token'lar TTL çok uzun Güvenlik marjıyla TTL'yi düşürün
Bellek büyüyor Temizleme yapılmıyor cleanup()'ı periyodik çağırın ya da TTL'li Redis kullanın

Sık sorulan sorular

Turnstile token'ı ne kadar süre yeniden kullanılabilir?

Turnstile token'ı yaklaşık 300 saniye geçerlidir ve bu pencere içinde yeniden kullanılabilir. Güvenli bir marj için TTL'yi 240 saniye civarında tutun.

Bellek içi önbellek mi, Redis mi kullanmalıyım?

Tek süreçli bir uygulama için bellek içi önbellek yeterlidir. Birden fazla worker ya da sunucu varsa, token'ları paylaşmak için Redis kullanın; aksi halde her süreç kendi ayrı önbelleğini tutar.

Önbelleğe alınan token neden reddedilir?

En sık nedenler:

  • Token tek kullanımlık ve ikinci kez gönderiliyor (reCAPTCHA'da yaygın).
  • Token'ın süresi dolmuş; TTL'niz gerçek ömürden uzun.
  • Token çıkış IP'sine bağlı ve proxy değişmiş (Cloudflare).

reCAPTCHA token'ları çoğu sitede tek seferliktir; bu tür için önbelleği kapatın veya TTL'yi kısaltın.

Önbelleğe alma maliyeti ne kadar düşürür?

Cloudflare doğrulama akışında tek bir çözüm 15–30 dakikalık oturumu kapsayabilir. Aynı alan adında yüksek frekanslı kazımada bu, çözüm maliyetini %90'a varan oranda azaltabilir.

OCR sonuçlarını ne kadar süre önbellekte tutabilirim?

Görüntü OCR sonucu bir metin dizesidir ve süresi dolmaz. Aynı görsel tekrar geliyorsa sonucu günlerce önbellekte tutabilirsiniz; pratikte 24 saatlik bir TTL, bellek büyümesini sınırlarken çoğu iş yükünü kapsar.


CaptchaAI ile CAPTCHA maliyetlerini düşürün

Token'ları önbelleğe alıp gereksiz API çağrılarınızı azaltmaya captchaai.com üzerinden başlayın.


İlgili kılavuzlar

Bu makale için yorumlar devre dışı bırakılmıştır.