Tutorials

Toplu Sonuçların Akışı: CAPTCHA Çözümlerini Geldiklerinde İşleme

Kısa cevap: toplu bir CAPTCHA işinde sonuçları biriktirmeyin, geldikçe akıtın. Tüm görevleri gönderip tek bir toplu bekleme ile sonucu almak, çoktan hazır olan token'ları kuyrukta bekletir — ve token'ların ömrü sınırlıdır; hedef siteye geç ulaşan bir token reddedilir. Pratikte 500 görevlik bir grupta ilk token birkaç saniyede hazır olurken son token'ı dakikalarca bekleyebilirsiniz; aradaki bütün süre, aşağı akış işlem hattınızın boşta durduğu süredir.

Bu rehberde iki üretim kalıbı var: Python tarafında asyncio ile her çözümü tamamlandığı anda veren bir async generator, Node.js tarafında ise sonuçları olay olarak yayan bir EventEmitter. İkisi de aynı iki uç noktayı kullanır — görev gönderimi için in.php, sonuç sorgulaması için res.php.

Akışı ne zaman seçmelisiniz, ne zaman hepsini beklemelisiniz?

Akış her iş yükü için doğru cevap değil. Karar, sonucu tüketen tarafın sonuçları tek tek mi yoksa bütün halinde mi işlediğine bağlıdır.

Senaryo Önerilen yaklaşım
Token'ı doğrudan bir forma gönderen akışlar Akış — token gelir gelmez formu gönderin
Tüm sonuçların tek bir CSV'ye yazılması Hepsini toplayın — grup bitince tek seferde yazın
Canlı ilerleme gösteren bir panel Akış — her sonuçta paneli güncelleyin
Görevlerin birbirine bağımlı olduğu iş Hepsini toplayın — tamamlandıktan sonra sırayla işleyin
1.000 görevi aşan büyük gruplar Akış — bellekteki tepe noktasını düşürür

Üç yaklaşımın gecikme ve bellek maliyeti

Aşağıdaki değerler gözlemlenen verilere ve kullanıcı raporlarına dayanır. Sonuçlar ortama, hacme ve zamana göre değişebilir.

Yaklaşım İlk sonuca kadar süre Bellek İşlem hattı gecikmesi
Hepsinin bitmesini bekleme En yavaş görev kadar Tüm sonuçlar bellekte Yüksek
Çözüldükçe akıtma En erken biten görev kadar Aynı anda tek sonuç Düşük
Mikro parti (10'luk gruplar) İlk parti kadar Aynı anda 10 sonuç Orta

Aradaki fark tek bir görevde göze batmaz; 300–500 görevlik gecelik bir işte ise ilk sonucun işlenmeye başladığı an, işin toplam penceresini belirler.

Python: sonuçları anında üreten bir async generator

asyncio ve aiohttp ile her çözüm, tamamlandığı anda generator üzerinden dışarı verilir. Sonuçlar gönderim sırasına göre değil, tamamlanma sırasına göre gelir; bu yüzden her sonuç kendi index değerini taşır.

import asyncio
import aiohttp
import time

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


async def submit_task(session, task_data):
    """Submit a single CAPTCHA task."""
    params = {
        "key": API_KEY,
        "method": task_data.get("method", "userrecaptcha"),
        "json": 1,
    }
    if params["method"] == "userrecaptcha":
        params["googlekey"] = task_data["sitekey"]
        params["pageurl"] = task_data["pageurl"]
    elif params["method"] == "turnstile":
        params["sitekey"] = task_data["sitekey"]
        params["pageurl"] = task_data["pageurl"]

    async with session.post(SUBMIT_URL, data=params) as resp:
        result = await resp.json(content_type=None)
        if result.get("status") != 1:
            return None, result.get("request", "unknown")
        return result["request"], None


async def poll_task(session, task_id, timeout=300):
    """Poll until solved or timeout."""
    start = time.monotonic()
    while time.monotonic() - start < timeout:
        await asyncio.sleep(5)
        params = {"key": API_KEY, "action": "get", "id": task_id, "json": 1}
        async with session.get(RESULT_URL, params=params) as resp:
            result = await resp.json(content_type=None)

        if result.get("request") == "CAPCHA_NOT_READY":
            continue
        if result.get("status") == 1:
            return result["request"], None
        return None, result.get("request", "unknown")

    return None, "TIMEOUT"


async def solve_one(session, index, task_data, semaphore):
    """Solve a single task within concurrency limits."""
    async with semaphore:
        start = time.monotonic()
        task_id, error = await submit_task(session, task_data)
        if error:
            return {"index": index, "status": "failed", "error": error, "time": 0}

        token, error = await poll_task(session, task_id)
        elapsed = time.monotonic() - start

        if token:
            return {"index": index, "status": "solved", "token": token, "time": round(elapsed, 1)}
        return {"index": index, "status": "failed", "error": error, "time": round(elapsed, 1)}


async def stream_results(tasks, max_concurrent=20):
    """
    Async generator that yields each result as it completes.
    Results arrive in completion order, not submission order.
    """
    semaphore = asyncio.Semaphore(max_concurrent)

    async with aiohttp.ClientSession() as session:
        pending = set()
        for i, task in enumerate(tasks):
            coro = solve_one(session, i, task, semaphore)
            pending.add(asyncio.ensure_future(coro))

        while pending:
            done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
            for future in done:
                yield future.result()


async def main():
    tasks = [
        {"sitekey": "SITE_KEY", "pageurl": f"https://example.com/page{i}"}
        for i in range(50)
    ]

    solved = 0
    failed = 0

    async for result in stream_results(tasks, max_concurrent=15):
        # Process each result immediately
        if result["status"] == "solved":
            solved += 1
            print(f"  [{solved + failed}/{len(tasks)}] Task {result['index']} SOLVED in {result['time']}s")

            # Use token immediately — don't wait for batch
            # await submit_form(result["token"])
            # await save_to_database(result)
        else:
            failed += 1
            print(f"  [{solved + failed}/{len(tasks)}] Task {result['index']} FAILED: {result['error']}")

    print(f"\nDone: {solved} solved, {failed} failed")


asyncio.run(main())

Bağımlılığı yükleyin:

pip install aiohttp

Semafor ve FIRST_COMPLETED neden birlikte çalışır

stream_results fonksiyonundaki asyncio.wait(..., return_when=FIRST_COMPLETED) çağrısı işin kalbidir: bekleyen görev kümesi her tamamlanan görevde yeniden kurulur, böylece hiçbir sonuç en yavaş görevin arkasında sıraya girmez. Semafor da gönderimleri kademelendirir — 500 isteği aynı anda açmak yerine, planınızdaki thread sayısı kadarını havada tutar.

Node.js: EventEmitter ile sonuç yayını

Node.js tarafında aynı davranışı olay tabanlı kurarsınız: her çözüm bir result olayı olarak yayılır, grup bittiğinde done tetiklenir. Dinleyici tarafında token'ı hemen kullanır ve sonuç nesnesini elinizde tutmazsınız.

const { EventEmitter } = require("events");

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

class CaptchaStream extends EventEmitter {
  constructor(maxConcurrent = 15) {
    super();
    this.maxConcurrent = maxConcurrent;
    this.active = 0;
    this.queue = [];
    this.total = 0;
    this.completed = 0;
  }

  async submitAndPoll(index, taskData) {
    const params = new URLSearchParams({
      key: API_KEY,
      method: taskData.method || "userrecaptcha",
      googlekey: taskData.sitekey,
      pageurl: taskData.pageurl,
      json: "1",
    });

    const start = Date.now();
    const submitResp = await (await fetch(SUBMIT_URL, { method: "POST", body: params })).json();

    if (submitResp.status !== 1) {
      return { index, status: "failed", error: submitResp.request, time: 0 };
    }

    const taskId = submitResp.request;
    for (let i = 0; i < 60; i++) {
      await new Promise((r) => setTimeout(r, 5000));
      const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
      const poll = await (await fetch(url)).json();

      if (poll.request === "CAPCHA_NOT_READY") continue;
      const elapsed = ((Date.now() - start) / 1000).toFixed(1);
      if (poll.status === 1) return { index, status: "solved", token: poll.request, time: elapsed };
      return { index, status: "failed", error: poll.request, time: elapsed };
    }
    return { index, status: "failed", error: "TIMEOUT", time: ((Date.now() - start) / 1000).toFixed(1) };
  }

  async processNext() {
    if (this.queue.length === 0 || this.active >= this.maxConcurrent) return;

    const { index, taskData } = this.queue.shift();
    this.active++;

    try {
      const result = await this.submitAndPoll(index, taskData);
      this.emit("result", result);
    } catch (err) {
      this.emit("result", { index, status: "failed", error: err.message });
    } finally {
      this.active--;
      this.completed++;

      if (this.completed === this.total) {
        this.emit("done");
      } else {
        this.processNext();
      }
    }
  }

  start(tasks) {
    this.total = tasks.length;
    this.queue = tasks.map((taskData, index) => ({ index, taskData }));

    // Launch initial batch
    const initial = Math.min(this.maxConcurrent, tasks.length);
    for (let i = 0; i < initial; i++) {
      this.processNext();
    }
    return this;
  }
}

// Usage
const tasks = Array.from({ length: 50 }, (_, i) => ({
  sitekey: "SITE_KEY",
  pageurl: `https://example.com/page${i}`,
}));

const stream = new CaptchaStream(15);
let solved = 0, failed = 0;

stream.on("result", (result) => {
  if (result.status === "solved") {
    solved++;
    console.log(`[${solved + failed}/${tasks.length}] Task ${result.index} SOLVED (${result.time}s)`);
    // Use token immediately
    // submitForm(result.token);
  } else {
    failed++;
    console.log(`[${solved + failed}/${tasks.length}] Task ${result.index} FAILED: ${result.error}`);
  }
});

stream.on("done", () => {
  console.log(`\nComplete: ${solved} solved, ${failed} failed`);
});

stream.start(tasks);

Dinleyicide sonucu tüketip bırakın

result olayında token'ı hemen kullanın, sonuç nesnesini bir dizide biriktirmeyin; aksi halde olay tabanlı akışın bellek avantajı kaybolur. done olayı sayaç tabanlıdır, bu yüzden hata veren görevlerin de completed sayacını artırdığından emin olun.

Eşzamanlılık sınırını planınızın thread sayısına göre ayarlayın

Koddaki max_concurrent / maxConcurrent değeri keyfi bir sayı değildir: CaptchaAI faturalaması çözüm başına değil, eşzamanlı thread başınadır ve her plan, thread başına sınırsız çözüm içerir. Sınırı planınızın üstüne çekmek çözümü hızlandırmaz, yalnızca istekleri bekletir.

Plan Aylık fiyat Thread Uygun grup büyüklüğü
BASIC $15/ay 5 thread Küçük denemeler, tek geliştirici testleri
STANDARD $30/ay 15 thread Günlük 100–300 görevlik işler
ADVANCE $90/ay 50 thread Gecelik toplu QA koşuları
PREMIUM $170/ay 100 thread Sürekli çalışan işlem hatları

Somut bir örnek: İstanbul'daki bir e-ticaret ekibi, ödeme adımını her gece staging ortamında 300 formla test ediyor. ADVANCE ($90/ay, 50 thread) ile eşzamanlılığı 50'de tutup sonuçları akıtınca ilk formlar, grubun geri kalanı hâlâ çözülürken gönderilmeye başlıyor. Türkiye'deki ekipler için ek bir avantaj, ücretin USD ve sabit olması: kur oynaklığına rağmen aylık maliyet öngörülebilir kalır. Veri kazıma senaryolarında ise akıttığınız sonuçların yanında kişisel veri topluyorsanız KVKK yükümlülüklerini de aynı işlem hattında ele almanız gerekir.

Sınırı yükseltmeden önce üç şeyi ölçün:

  • görev başına ortalama çözüm süresi — reCAPTCHA v2, Turnstile ve GeeTest v3 farklı sürelerde tamamlanır;
  • gruptaki tür dağılımı: karışık türlerde akış, en yavaş türün bütün işi yavaşlatmasını engelleyen asıl mekanizmadır;
  • aşağı akış tarafının saniyede kaç token'ı gerçekten işleyebildiği.

Üretimde sık karşılaşılan sorunlar

Akışta hatalar tek göreve hapsolur; aşağıdaki tablo üretimde en sık gördüğümüz belirtileri ve düzeltmelerini toplar.

Sorun Sebep Düzeltme
Sonuçlar karışık sırada geliyor Normal — akış, önce biten görevi önce verir Orijinal göreve dönmek için result.index kullanın
Akış sürerken bellek büyümeye devam ediyor Tüm sonuçlar bir dizide tutuluyor Sonucu işleyicide işleyip elden çıkarın
İlk sonuç çok geç geliyor Tüm görevler aynı anda gönderilmiş Semafor veya eşzamanlılık sınırıyla gönderimi kademelendirin
EventEmitter uyarısı: MaxListenersExceeded Akışa çok fazla dinleyici bağlanmış setMaxListeners() kullanın veya olay türü başına tek dinleyici bırakın
Async generator kilitleniyor Bekleyen kümede çözülmemiş bir görev kalmış poll_task içine zaman aşımı koyun; her future'ın tamamlandığından veya hata verdiğinden emin olun
Token hedef sitede reddediliyor Token işlenmeden önce bekletilmiş Token'ı akıştan aldığınız anda gönderin, kuyruğa almayın

Sık sorulan sorular

Eşzamanlılık sınırını kaça ayarlamalıyım?

Planınızdaki thread sayısını aşmayın. ADVANCE ($90/ay, 50 thread) kullanıyorsanız 50 üst sınırdır; daha yükseği çözüm süresini kısaltmaz, yalnızca istekleri bekletir. Örneklerdeki 15 değeri STANDARD ($30/ay, 15 thread) için tam oturur.

Akış sırasında bellek neden hâlâ büyüyor?

Genellikle sonuçlar bir listede toplandığı için. Akışın anlamı, her sonucu işleyicide tüketip referansı bırakmaktır; token'ı forma gönderdikten veya veritabanına yazdıktan sonra sonuç nesnesini elinizde tutmayın.

Sonuçlar karışık geldiğinde orijinal görevi nasıl eşleştiririm?

Her sonuç kendi index alanını taşır ve gönderim listesindeki konumla doğrudan eşleşir. Sıra aşağı akış için kritikse sonuçları sıralı bir yapıda tamponlayın, ardışık blokları tamamlandıkça boşaltın.

Bir görev takılırsa tüm akış durur mu?

Hayır — poll_task içindeki zaman aşımı takılan görevi TIMEOUT hatasıyla kapatır ve akış diğer sonuçlarla devam eder. Zaman aşımını kaldırırsanız yanıtsız tek bir görev bütün grubu bekletebilir, bu yüzden onu kaldırmayın.

Bu kalıpla hangi CAPTCHA türlerini çözebilirim?

reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile ve Cloudflare doğrulama akışı, GeeTest v3 ile görüntü/OCR ve metin tabanlı CAPTCHA'lar desteklenir; CaptchaFox (beta), Friendly Captcha (beta) ve Lemin (beta) beta aşamasındadır. hCaptcha ve FunCaptcha desteklenmiyor, GeeTest v4 için çok yakında ifadesi geçerli — bu türleri toplu akış planınıza dahil etmeyin.

İlgili makaleler

Sonraki adımlar

Çözümleri bekletmeyin: CaptchaAI API anahtarınızı alın ve ilk akış tabanlı işlem hattınızı bugün kurun.

İlgili kılavuzlar:

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