API Tutorials

CAPTCHA Çözme Başarısız Olduğunda Zarif Bozulma

Gece 02.00'de çalışan toplu bir işin tek bir zaman aşımı yüzünden tümüyle çökmesi, otomasyon geliştiricilerinin en sık yaşadığı senaryolardan biridir. CAPTCHA çözümü birçok nedenle başarısız olabilir — zaman aşımı, hatalı parametre, sıfır bakiye veya hız sınırı — ve ilk hatada tüm hattı durdurmak, saatlerce süren ilerlemeyi bir anda yok eder. Doğru yaklaşım, hattı çökertmek yerine kademeli olarak geriye çekmektir: başarısız görevi atlayın, yeniden deneyin, kuyruğa alın ya da alternatif bir akışa düşürün. Bu rehber, bu dört savunma katmanını Python ve Node.js ile üretime hazır kodla kurar.


Hataları önce doğru sınıflandırın

Doğru kurtarma stratejisini seçmenin ilk koşulu, hatayı doğru sınıflandırmaktır. Aşağıdaki tablo, CaptchaAI API'sinden dönen tipik hata kodlarını ve her biri için önerilen tepkiyi özetler.

Başarısızlık türü Hata kodu Kurtarma stratejisi
Zaman aşımı CAPCHA_NOT_READY (sorgulama denemeleri aşıldı) Yeni bir CAPTCHA doğrulamasıyla yeniden deneyin
Hatalı parametre ERROR_BAD_PARAMETERS Günlüğe yazın ve atlayın; çıkarma mantığını düzeltin
Yanlış sitekey ERROR_WRONG_GOOGLEKEY sitekey'i yeniden çıkarın
Sıfır bakiye ERROR_ZERO_BALANCE Duraklatın, uyarı gönderin, bakiye yüklenene kadar bekleyin
Hız sınırı ERROR_TOO_MUCH_REQUESTS Üstel geri çekilme (exponential backoff) uygulayın
API erişilemez Bağlantı hatası Devre kesici (circuit breaker) + yeniden deneme

Buradaki ayrım kritik: ERROR_BAD_PARAMETERS ve ERROR_WRONG_GOOGLEKEY kalıcı hatalardır — yeniden denemek yalnızca bakiyeyi tüketir. Zaman aşımı ve hız sınırı ise geçicidir ve yeniden denemeye değer. Aşağıdaki üç desen, bu ayrımı koda döker.


Desen 1: Atla ve devam et

En basit savunma katmanı, tek tek başarısızlıkların kabul edilebilir olduğu toplu işlerde işe yarar. Örneğin binlerce ürün sayfasını tarayan bir QA işinde, birkaç sayfanın atlanması işi durdurmaya değmez. solve_or_skip çökmek yerine None döndürür; ana döngü atlanan öğeyi kaydeder ve devam eder:

import requests
import time

API_KEY = "YOUR_API_KEY"


def solve_or_skip(captcha_type, sitekey, page_url, max_retries=2):
    """Try to solve; return None on failure instead of crashing."""
    for attempt in range(max_retries):
        try:
            token = solve_captcha(captcha_type, sitekey, page_url)
            if token:
                return token
        except Exception as e:
            print(f"Attempt {attempt + 1} failed: {e}")

    return None  # Skip this item


def process_urls(urls):
    results = []
    skipped = []

    for url in urls:
        sitekey = extract_sitekey(url)
        if not sitekey:
            skipped.append({"url": url, "reason": "no_sitekey"})
            continue

        token = solve_or_skip("recaptcha_v2", sitekey, url)
        if token:
            data = submit_form(url, token)
            results.append({"url": url, "data": data})
        else:
            skipped.append({"url": url, "reason": "solve_failed"})

    print(f"Processed: {len(results)}, Skipped: {len(skipped)}")
    return results, skipped

Sonuçta işlenen ve atlanan öğelerin sayısını görürsünüz; atlananları ayrı bir listede tutmak, sonradan incelemeyi ve yeniden işlemeyi kolaylaştırır.


Desen 2: Yeniden deneme kuyruğu

Atlamak her zaman kabul edilebilir değildir. Geçici bir hata yüzünden atlanan bir görev, birkaç dakika sonra sorunsuz çözülebilir. Yeniden deneme kuyruğu, başarısız görevleri artan gecikmeyle (backoff) tekrar sıraya alır ve daha sonra işler:

from collections import deque
import json

class RetryQueue:
    def __init__(self, max_retries=3, backoff_base=60):
        self.queue = deque()
        self.max_retries = max_retries
        self.backoff_base = backoff_base

    def add(self, task):
        task["retry_count"] = task.get("retry_count", 0) + 1
        if task["retry_count"] <= self.max_retries:
            task["retry_after"] = time.time() + (
                self.backoff_base * task["retry_count"]
            )
            self.queue.append(task)
            return True
        return False  # Exceeded max retries

    def get_ready(self):
        """Get tasks ready for retry."""
        ready = []
        remaining = deque()
        now = time.time()

        while self.queue:
            task = self.queue.popleft()
            if task["retry_after"] <= now:
                ready.append(task)
            else:
                remaining.append(task)

        self.queue = remaining
        return ready

    def save(self, filepath="retry_queue.json"):
        with open(filepath, "w") as f:
            json.dump(list(self.queue), f)

    def load(self, filepath="retry_queue.json"):
        try:
            with open(filepath) as f:
                self.queue = deque(json.load(f))
        except FileNotFoundError:
            pass


# Usage
retry_q = RetryQueue()

def process_with_retry(task):
    try:
        token = solve_captcha(task["type"], task["sitekey"], task["url"])
        if token:
            return submit_form(task["url"], token)
        else:
            retry_q.add(task)
    except Exception:
        retry_q.add(task)

# Process retry queue periodically
def drain_retry_queue():
    ready = retry_q.get_ready()
    for task in ready:
        process_with_retry(task)

backoff_base ile her denemede bekleme süresi büyür; max_retries sınırı ise bir görevin sonsuza kadar dönüp durmasını engeller. Kuyruğu diske yazan save/load metotları, süreç yeniden başlasa bile bekleyen görevlerin kaybolmamasını sağlar.


Desen 3: Bozulmuş mod

Tek tek görevler değil de çözüm hizmetinin tamamı erişilemez hale geldiğinde, her isteği ayrı ayrı denemek yalnızca zaman kaybettirir. Bozulmuş mod, belirli sayıda ardışık hatadan sonra devreye girer; API çağrılarını bir süreliğine tümüyle durdurup sınırlı bir davranışa geçer:

class CaptchaSolver:
    def __init__(self, api_key):
        self.api_key = api_key
        self.degraded = False
        self.failure_count = 0
        self.failure_threshold = 5
        self.recovery_time = None

    def solve(self, captcha_type, sitekey, page_url):
        if self.degraded:
            if time.time() < self.recovery_time:
                return self._degraded_action(page_url)
            else:
                self.degraded = False
                self.failure_count = 0

        try:
            token = self._solve_api(captcha_type, sitekey, page_url)
            self.failure_count = 0
            return token
        except Exception as e:
            self.failure_count += 1
            if self.failure_count >= self.failure_threshold:
                self._enter_degraded_mode()
            raise

    def _enter_degraded_mode(self):
        self.degraded = True
        self.recovery_time = time.time() + 300  # 5 min
        print("Entering degraded mode for 5 minutes")
        # Send alert

    def _degraded_action(self, url):
        """What to do when solving is unavailable."""
        # Option A: Skip CAPTCHA pages entirely
        return None

        # Option B: Queue for later
        # retry_queue.add({"url": url, ...})
        # return None

        # Option C: Try alternative solver
        # return self._solve_with_backup_api(...)

    def _solve_api(self, captcha_type, sitekey, page_url):
        # Normal CaptchaAI API call
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url,
            "json": "1",
        }).json()

        if resp["status"] != 1:
            raise Exception(resp["request"])

        task_id = resp["request"]
        for _ in range(24):
            time.sleep(5)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": "1"
            }).json()
            if result["status"] == 1:
                return result["request"]
            if result["request"] != "CAPCHA_NOT_READY":
                raise Exception(result["request"])

        raise Exception("TIMEOUT")

failure_threshold değerine ulaşıldığında çözücü bozulmuş moda girer ve recovery_time dolana kadar _degraded_action çalışır. Bu metotta üç seçenek görürsünüz: sayfayı tamamen atlamak, görevi kuyruğa almak veya alternatif bir çözücüye düşmek.


Node.js: Üç deseni birleştiren çözücü

Aynı üç katmanı tek bir sınıfta toplamak istiyorsanız, aşağıdaki Node.js ResilientSolver sınıfı yeniden deneme kuyruğunu, hata sayacını ve bozulmuş modu bir arada tutar. ERROR_ZERO_BALANCE gibi kritik hatalarda daha uzun bir bozulma süresi uygular:

class ResilientSolver {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.retryQueue = [];
    this.failureCount = 0;
    this.degraded = false;
  }

  async solve(type, sitekey, pageUrl) {
    if (this.degraded) {
      this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
      return null;
    }

    try {
      const token = await this._callApi(type, sitekey, pageUrl);
      this.failureCount = 0;
      return token;
    } catch (err) {
      this.failureCount++;

      if (err.message === 'ERROR_ZERO_BALANCE') {
        this._enterDegraded(600000); // 10 min
        return null;
      }

      if (this.failureCount >= 5) {
        this._enterDegraded(300000); // 5 min
      }

      this.retryQueue.push({ type, sitekey, pageUrl, addedAt: Date.now() });
      return null;
    }
  }

  _enterDegraded(durationMs) {
    this.degraded = true;
    console.warn(`Degraded mode for ${durationMs / 1000}s`);
    setTimeout(() => {
      this.degraded = false;
      this.failureCount = 0;
      this.drainRetryQueue();
    }, durationMs);
  }

  async drainRetryQueue() {
    const tasks = this.retryQueue.splice(0);
    for (const task of tasks) {
      await this.solve(task.type, task.sitekey, task.pageUrl);
    }
  }

  async _callApi(type, sitekey, pageUrl) {
    // Standard submit + poll
    const axios = require('axios');
    const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
      params: { key: this.apiKey, method: 'userrecaptcha', googlekey: sitekey, pageurl: pageUrl, json: 1 },
    });
    if (submit.data.status !== 1) throw new Error(submit.data.request);

    const taskId = submit.data.request;
    for (let i = 0; i < 24; i++) {
      await new Promise(r => setTimeout(r, 5000));
      const poll = await axios.get('https://ocr.captchaai.com/res.php', {
        params: { key: this.apiKey, action: 'get', id: taskId, json: 1 },
      });
      if (poll.data.status === 1) return poll.data.request;
      if (poll.data.request !== 'CAPCHA_NOT_READY') throw new Error(poll.data.request);
    }
    throw new Error('TIMEOUT');
  }
}

Yeniden deneme karar matrisi

Her hata yeniden denemeye değmez. Kararı üç soruyla verin:

  • Yalnızca hata geçici görünüyorsa ve görevin çevresindeki oturum durumu hâlâ geçerliyse yeniden deneyin.
  • İstek, kullanıcı eylemlerini taklit etmeye gerek kalmadan belirleyici biçimde yeniden oluşturulabiliyorsa adımı yeniden çalıştırın.
  • Daha fazla denemenin yalnızca bakiyeyi boşa harcayacağı kalıcı hatalarda görevi atlayın veya hattı duraklatın.

Üretimde kademeli bozulma: pratik bir örnek

Diyelim ki bir e-ticaret müşteriniz için her gece Europe/Istanbul saatiyle 02.00'de çalışan bir QA doğrulama işi yazdınız. İş, staging ortamındaki yüzlerce ödeme akışını test ediyor ve her sayfada bir reCAPTCHA v2 doğrulaması var. CaptchaAI birkaç dakikalığına hız sınırına takılırsa üç katman sırayla devreye girer: önce görevler yeniden deneme kuyruğuna alınır, ardışık hatalar eşiği aşarsa çözücü bozulmuş moda geçer ve sınır kalktığında kuyruk otomatik olarak boşaltılır. Böylece sabaha yarım kalmış bir işle değil, tamamlanmış bir raporla uyanırsınız. Kişisel veri içeren test verileriyle çalışıyorsanız, bu akışı yalnızca yetkili QA ortamlarında ve KVKK uyumlu biçimde tutmayı unutmayın.

Sorun giderme

Sorun Neden Çözüm
Tüm görevler atlanıyor Bozulmuş mod çok agresif tetiklendi failure_threshold değerini artırın
Yeniden deneme kuyruğu sürekli büyüyor Görevler hiçbir zaman başarılı olmuyor max_retries belirleyin; kalıcı hataları teslim edilemeyen mektup kuyruğuna taşıyın
İyileşme çok yavaş Bozulmuş mod zaman aşımı fazla uzun recovery_time süresini kısaltın; durum kontrolü (health check) probu ekleyin
Yeniden başlatmada kuyruk kayboluyor Bellek içi kuyruk Kuyruğu dosyaya veya veritabanına kalıcı hale getirin

Sık sorulan sorular

Hangi hata kodlarını asla yeniden denememeliyim?

ERROR_BAD_PARAMETERS ve ERROR_WRONG_GOOGLEKEY kalıcı hatalardır; parametreleri düzeltmeden yeniden denemek yalnızca bakiyeyi tüketir. Yalnızca zaman aşımı ve hız sınırı gibi geçici hataları yeniden deneyin.

Bozulmuş mod thread kullanımını ve faturayı etkiler mi?

CaptchaAI thread bazlı faturalandırır; ücret çözüm başına değil, eşzamanlı thread başına alınır. Bozulmuş moddayken API çağrısı yapılmadığı için o an aktif bir thread tutulmaz, ancak plan ücretiniz aylık sabittir — örneğin BASIC ($15/ay, 5 thread) planında boşta kalan thread'ler için ek ücret ödemezsiniz.

Yeniden deneme kuyruğu her yeniden başlatmada sıfırlanıyor, ne yapmalıyım?

Bellek içi kuyruklar süreç yeniden başlayınca kaybolur. Yukarıdaki RetryQueue.save/load metotlarıyla kuyruğu diske yazın ya da Redis gibi kalıcı bir depoya taşıyın; böylece bekleyen görevler yeniden başlatmadan sonra da korunur.

failure_threshold değerini nasıl seçmeliyim?

Çok düşük bir eşik (örneğin 2) tek tük geçici hatalarda bile tüm hattı bozulmuş moda sokar; çok yüksek bir eşik ise gerçek bir kesintide çok geç tepki verir. Çoğu iş yükü için 5 civarı ardışık hata iyi bir başlangıçtır; kendi hata oranınıza göre ince ayar yapın.


CaptchaAI ile dayanıklı bir çözüm hattı kurun

Yukarıdaki desenlerin hepsi tek bir CaptchaAI API anahtarıyla çalışır. Ücretsiz hesabınızı açın, anahtarınızı captchaai.com üzerinden alın ve ilk dayanıklı hattınızı bugün kurun.


İlgili kılavuzlar

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