API Tutorials

CAPTCHA API Çağrıları için Devre Kesici Kalıbı

API'niz art arda hata vermeye başladığında otomasyon script'iniz ne yapıyor? Çoğu kurulumda cevap can sıkıcı: aynı isteği tekrar tekrar göndermeye devam ediyor, ta ki thread'leriniz tükenene kadar. Devre kesici (circuit breaker) deseni bunu üç durumlu bir anahtarla çözer — yeni istekleri anında durdurur, belirli bir soğuma süresi bekler, sonra tek bir test isteğiyle API'nin toparlanıp toparlanmadığını kontrol eder.

Thread tabanlı bir CaptchaAI planında bunun karşılığı çok somuttur: başarısız isteklerle meşgul kalan her thread, o an çözülebilecek gerçek bir CAPTCHA görevini bekletir ve saatlik iş hacminizi düşürür. Bu rehberde production'a hazır bir devre kesici sınıfını hem Python hem Node.js ile sıfırdan kurar, eşik değerlerini trafiğinize göre nasıl seçeceğinizi ve yeniden deneme (retry) mantığıyla nasıl birleştireceğinizi adım adım görürsünüz.


Devre kesicinin üç durumu

Devre kesici, API çağrılarınızı sürekli olarak üç durum arasında geçirir. Kapalı durumda çalışma normaldir: istekler doğrudan API'ye ulaşır, başarısızlıklar yalnızca arka planda sayılır. Hata sayısı belirlediğiniz eşiği aştığında devre Açık durumuna geçer; bu andan itibaren yeni istekler API hiç çağrılmadan anında reddedilir. Soğuma süresi dolduktan sonra devre Yarı Açık duruma geçer ve tek bir test isteğine izin verir — başarılı olursa devre kapanır, başarısız olursa devre yeniden açılır.

Gerçek senaryo: İstanbul merkezli bir e-ticaret otomasyon ekibi, STANDARD plan ($30/ay, 15 thread) üzerinde gece yarısı kampanya kontrol görevlerini çalıştırıyor. Bir partner API'sinin Europe/Istanbul saatiyle 03:00 civarında kısa süreli kesintiler yaşadığı gecelerde, devre kesici olmadan 15 thread'in tamamı art arda başarısız isteklerle meşgul kalıyor ve gerçek CAPTCHA görevleri sıraya giremiyor. Devre kesici üçüncü ardışık hatada devreyi açıyor, thread'leri anında serbest bırakıyor ve 30 saniye sonra tek bir test isteğiyle servisin dönüp dönmediğini kontrol ediyor.


Eşik değerlerini doğru seçmek

Devre kesicinin davranışını iki parametre belirler: failure_threshold devrenin ne zaman açılacağını, recovery_timeout ise ne kadar bekleyeceğini belirler. Trafiğinize göre başlangıç noktası:

  • Düşük trafik (< 10 istek/dk): failure_threshold = 3, recovery_timeout = 30 saniye
  • Yüksek trafik (> 100 istek/dk): failure_threshold = 10, recovery_timeout = 60 saniye

Eşiği, ara sıra oluşan tekil zaman aşımlarını tolere edecek kadar yüksek tutun — tek bir gecikmiş yanıt devreyi açmamalı — ama arızalı bir API'ye istek göndermeyi gerçekten durduracak kadar da düşük bırakın. Yüksek trafikli bir entegrasyonda eşiği çok düşük tutmak, geçici bir ağ dalgalanmasında bile devrenin gereksiz yere açılmasına ve thread'lerin boşa düşmesine yol açar. Değerleri sabit kod olarak yazmak yerine ortam değişkeni (environment variable) üzerinden okumak, hangi ortamda hangi eşiğin kullanıldığını canlıya çıkmadan test etmenizi kolaylaştırır.


Python ile devre kesici uygulaması

Aşağıdaki sınıf, threading.Lock ile korunan basit ama production'a uygun bir durum makinesi kurar. solve_captcha fonksiyonu CaptchaAI'nin in.php ve res.php uç noktalarını çağırır; devre kesici bu fonksiyonu sarmalayarak art arda gelen hataları izler.

import time
import threading
import requests

SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
API_KEY = "YOUR_API_KEY"


class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time = 0
        self.state = "closed"  # closed, open, half-open
        self._lock = threading.Lock()

    def call(self, func, *args, **kwargs):
        with self._lock:
            if self.state == "open":
                if time.time() - self.last_failure_time > self.recovery_timeout:
                    self.state = "half-open"
                    print("[circuit] State: half-open — testing one request")
                else:
                    remaining = self.recovery_timeout - (
                        time.time() - self.last_failure_time
                    )
                    raise CircuitOpenError(
                        f"Circuit open — retry in {remaining:.0f}s"
                    )

        try:
            result = func(*args, **kwargs)
            with self._lock:
                self.failure_count = 0
                if self.state == "half-open":
                    print("[circuit] State: closed — API recovered")
                self.state = "closed"
            return result
        except Exception as e:
            with self._lock:
                self.failure_count += 1
                self.last_failure_time = time.time()
                if self.failure_count >= self.failure_threshold:
                    self.state = "open"
                    print(
                        f"[circuit] State: open — "
                        f"{self.failure_count} failures"
                    )
            raise


class CircuitOpenError(Exception):
    pass


def solve_captcha(sitekey, page_url):
    resp = requests.post(SUBMIT_URL, data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": "1",
    }, timeout=15)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit error: {data['request']}")

    task_id = data["request"]
    for _ in range(24):
        time.sleep(5)
        poll = requests.get(RESULT_URL, params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": "1",
        }, timeout=15).json()
        if poll["status"] == 1:
            return poll["request"]
        if poll["request"] != "CAPCHA_NOT_READY":
            raise Exception(f"Poll error: {poll['request']}")
    raise TimeoutError(f"Task {task_id} timed out")


# Usage
breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=30)

for i in range(10):
    try:
        token = breaker.call(
            solve_captcha, "6Le-SITEKEY", "https://example.com"
        )
        print(f"[task-{i}] Solved: {token[:40]}...")
    except CircuitOpenError as e:
        print(f"[task-{i}] Skipped: {e}")
    except Exception as e:
        print(f"[task-{i}] Failed: {e}")

Devrenin üç durum arasında nasıl geçtiğini gösteren örnek çıktı:

[task-0] Solved: 03AGdBq26ZfPxL...
[task-1] Solved: 03AGdBq27AbCdE...
[task-2] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-3] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[task-4] Failed: Submit error: ERROR_NO_SLOT_AVAILABLE
[circuit] State: open — 3 failures
[task-5] Skipped: Circuit open — retry in 28s
[task-6] Skipped: Circuit open — retry in 25s
...
[circuit] State: half-open — testing one request
[task-8] Solved: 03AGdBq28FgHiJ...
[circuit] State: closed — API recovered

Node.js ile devre kesici uygulaması

Aynı durum makinesi Node.js tarafında da birebir çalışır. axios ile gönderilen istekler CircuitBreaker.call() metodunun içinden geçer; durum değişimleri Python sürümüyle aynı formatta konsola yazılır. Tek fark, kilit mekanizmasıdır: Node.js tek iş parçacıklı çalıştığından threading.Lock'a karşılık gelen bir yapıya gerek yoktur, async/await akışı durumu zaten sıralı biçimde günceller.

class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.recoveryTimeout = options.recoveryTimeout || 60000;
    this.failureCount = 0;
    this.lastFailureTime = 0;
    this.state = 'closed';
  }

  async call(fn, ...args) {
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
        this.state = 'half-open';
        console.log('[circuit] State: half-open');
      } else {
        const remaining = this.recoveryTimeout - (Date.now() - this.lastFailureTime);
        throw new Error(`Circuit open — retry in ${Math.ceil(remaining / 1000)}s`);
      }
    }

    try {
      const result = await fn(...args);
      this.failureCount = 0;
      if (this.state === 'half-open') {
        console.log('[circuit] State: closed — recovered');
      }
      this.state = 'closed';
      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();
      if (this.failureCount >= this.failureThreshold) {
        this.state = 'open';
        console.log(`[circuit] State: open — ${this.failureCount} failures`);
      }
      throw error;
    }
  }
}

// Usage
const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';
const breaker = new CircuitBreaker({ failureThreshold: 3, recoveryTimeout: 30000 });

async function solveCaptcha(sitekey, pageurl) {
  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) 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: API_KEY, 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');
}

(async () => {
  for (let i = 0; i < 10; i++) {
    try {
      const token = await breaker.call(solveCaptcha, '6Le-SITEKEY', 'https://example.com');
      console.log(`[task-${i}] Solved: ${token.substring(0, 40)}...`);
    } catch (err) {
      console.log(`[task-${i}] ${err.message}`);
    }
  }
})();

Yeniden deneme (retry) mantığıyla birlikte kullanma

Yeniden deneme mantığını devre kesicinin içinde çalıştırın, dışında değil. Devre kesici yalnızca son, kesin başarısızlıkları sayar — yani retry mantığı tükendikten sonraki hatayı:

def solve_with_retry(sitekey, page_url, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            return solve_captcha(sitekey, page_url)
        except Exception:
            if attempt == max_retries:
                raise
            time.sleep(2 ** attempt)

# Circuit breaker wraps the retry function
token = breaker.call(solve_with_retry, "6Le-SITEKEY", "https://example.com")

Sorun giderme: sık karşılaşılan hatalar

Devre kesici kurulumlarında en sık karşılaşılan dört sorun ve düzeltmeleri aşağıda. Çoğu, eşik değerlerinin trafiğinizle uyuşmamasından ya da paylaşılan durumun kilitlenmemesinden kaynaklanır:

  • Devre çok hızlı açılıyor — Eşik çok düşük ayarlanmış. Çözüm: failure_threshold değerini artırın.
  • Devre hiç kapanmıyorrecovery_timeout çok uzun. Çözüm: 30-60 saniye aralığına düşürün.
  • Çok thread'li kullanımda yarış durumu (race condition) — Durum değişkeninde kilit yok. Çözüm: Python'da threading.Lock, Node.js'te atomik güncellemeler kullanın.
  • Kısmi kesinti sırasında tüm istekler engelleniyor — Tüm uç noktalar tek bir devre kesiciyi paylaşıyor. Çözüm: Gönderme ve yoklama uç noktaları için ayrı devre kesiciler tanımlayın.

Sık sorulan sorular

Gönderme ve yoklama uç noktaları için ayrı devre kesiciler mi kullanmalıyım?

Büyük ölçekli sistemlerde evet. Gönderme (in.php) uç noktası hata verirken yoklama (res.php) çalışmaya devam edebilir, ya da tam tersi olabilir. Ayrı devre kesiciler size daha hassas bir kontrol sağlar.

Devre kesici CaptchaAI thread kullanımımı nasıl etkiler?

Devre açıkken hiçbir istek API'ye gitmediği için ilgili thread'ler anında serbest kalır. Devre kesici olmadan aynı thread'ler başarısız isteklerle meşgul kalmaya devam eder — özellikle STANDARD ve üzeri thread tabanlı planlarda bu, gerçek görevlere ayrılabilecek kapasiteyi doğrudan korur.

Devre kesici ile yeniden deneme (retry) mantığı arasındaki fark nedir?

Yeniden deneme tek bir isteği birkaç kez tekrar dener; devre kesici ise sistemin API'yi çağırmayı tamamen durdurup durdurmayacağına karar verir. İkisi birbirini dışlamaz: retry mantığı devre kesicinin içinde çalışır, yukarıdaki bölümde gösterildiği gibi.

reCAPTCHA v2 dışındaki CAPTCHA türlerinde de kullanılabilir mi?

Evet. Devre kesici, API'ye HTTP isteği gönderen herhangi bir fonksiyonu sarmalayabilir. reCAPTCHA v3, Cloudflare Turnstile, GeeTest v3 veya BLS CAPTCHA görevleri için tek değişiklik solve_captcha fonksiyonunun içeriği (method parametresi) olur; devre kesici sınıfının kendisi aynı kalır.


CaptchaAI ile dayanıklı CAPTCHA iş akışları kurun

API anahtarınızı captchaai.com üzerinden ücretsiz hesap açarak alın ve devre kesiciyi ilk otomasyon akışınıza ekleyin. Tek bir test makinesi için BASIC ($15/ay, 5 thread) yeterlidir; hacim büyüdükçe aynı devre kesici kodunu değiştirmeden üst plana geçebilirsiniz.


İlgili kılavuzlar

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