Tutorials

CAPTCHA İşlemleri için Yapılandırılmış Günlük Kaydı

Bir CAPTCHA çözümü düştüğünde tek bir sorunun yanıtı gerekir: hangi görev, hangi türde, kaç milisaniye sonra ve hangi hata koduyla başarısız oldu? "Error solving captcha" satırı bunların hiçbirini söylemez. Yapılandırılmış loglama, her çözümü task_id, captcha_type, solve_time_ms ve error alanlarını taşıyan tek satırlık bir JSON kaydına çevirir; metin taramak yerine alana göre filtreler, yanıtı saniyeler içinde bulursunuz.

Aşağıdaki yapılandırmayı Python (structlog) ve Node.js (pino) için doğrudan mevcut çözüm fonksiyonunuza yerleştirebilirsiniz.


Önce log alan şemasını sabitleyin

Kütüphane seçmeden önce hangi alanları yazacağınıza karar verin. Şema baştan sabit değilse iki servis farklı anahtar adları kullanır ve merkezî sorgularınız ilk haftada bozulur. Aşağıdaki küme gereken minimumdur.

Alan Tür Açıklama
event dize Etkinlik adı: captcha_submitted, captcha_solved, vb.
task_id dize Korelasyon için CaptchaAI görev kimliği
captcha_type dize recaptcha_v2, turnstile, image, vb.
site_url dize Hedef sayfa URL'si
solve_time_ms tamsayı Gönderimden çözüme kadar geçen toplam süre
poll_attempts tamsayı Yapılan sorgulama isteği sayısı
error dize CaptchaAI'den gelen hata kodu
token_length tamsayı Dönen token'ın uzunluğu

Bu alanların en kritiği task_id'dir: gönderim, sorgulama ve token enjeksiyonunu tek bir çözüme bağlayan anahtar odur. Diğerleri olmadan da hata ayıklarsınız; task_id olmadan elinizde kopuk satırlar kalır.

Düz metin loglar neden aynı işi görmez

Düz metin bir satır insan gözü için okunabilir ama makine için işe yaramaz: alana göre filtreleyemez, iki olayı ilişkilendiremez, otomatik uyarı üretemezsiniz. JSON ise her bilgiyi ayrı bir anahtara çevirir.

Düz metin Yapılandırılmış JSON
Captcha solved in 12.3s {"event":"captcha_solved","task_id":"abc123","type":"recaptcha_v2","solve_time_ms":12300}
Ayrıştırılması zor Makine tarafından okunabilir
Yalnızca Grep araması Herhangi bir alana göre filtrele
Korelasyon yok task_id; gönderme → sorgulama → enjeksiyon adımlarını birbirine bağlar

Python tarafı: structlog yapılandırması

structlog'u üç işlemciyle kurmak yeterli: ISO zaman damgası, seviye alanı ve JSON renderer. Ayrı formatlayıcıya gerek kalmaz, çıktı doğrudan stdout'a aranabilir JSON olarak düşer.

import structlog
import time

structlog.configure(
    processors=[
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.add_log_level,
        structlog.processors.JSONRenderer(),
    ],
    logger_factory=structlog.PrintLoggerFactory(),
)

log = structlog.get_logger()

Çözüm yaşam döngüsünü loglayın

log.bind() bağlam alanlarını — CAPTCHA türü, hedef URL, kısaltılmış sitekey — bir kez bağlar; sonraki her satır bunları otomatik taşır. Görev kimliği yanıtla döner dönmez ikinci bir bind() ile onu da ekleyin.

import requests

API_KEY = "YOUR_API_KEY"


def solve_captcha(captcha_type, sitekey, page_url, proxy=None):
    solve_log = log.bind(
        captcha_type=captcha_type,
        site_url=page_url,
        sitekey=sitekey[:12] + "...",
    )

    # Submit
    start = time.time()
    solve_log.info("captcha_submit_start")

    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": page_url,
        "json": "1",
    }).json()

    if resp["status"] != 1:
        solve_log.error("captcha_submit_failed", error=resp["request"])
        return None

    task_id = resp["request"]
    submit_ms = int((time.time() - start) * 1000)
    solve_log = solve_log.bind(task_id=task_id)
    solve_log.info("captcha_submitted", submit_ms=submit_ms)

    # Poll
    for attempt in range(24):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get", "id": task_id, "json": "1"
        }).json()

        if result["status"] == 1:
            solve_ms = int((time.time() - start) * 1000)
            solve_log.info(
                "captcha_solved",
                solve_time_ms=solve_ms,
                poll_attempts=attempt + 1,
                token_length=len(result["request"]),
            )
            return result["request"]

        if result["request"] != "CAPCHA_NOT_READY":
            solve_log.error(
                "captcha_solve_failed",
                error=result["request"],
                poll_attempts=attempt + 1,
            )
            return None

    solve_log.warning("captcha_solve_timeout", poll_attempts=24)
    return None

Çıktı:

{"event":"captcha_submit_start","captcha_type":"recaptcha_v2","site_url":"https://example.com","sitekey":"6Le-wvkSAAAA...","timestamp":"2025-07-15T10:30:00Z","level":"info"}
{"event":"captcha_submitted","task_id":"71845302","submit_ms":245,"timestamp":"2025-07-15T10:30:00Z","level":"info"}
{"event":"captcha_solved","task_id":"71845302","solve_time_ms":18230,"poll_attempts":4,"token_length":580,"timestamp":"2025-07-15T10:30:18Z","level":"info"}

Üç satırın da aynı task_id değerini taşıdığına dikkat edin. task_id="71845302" sorgusu, tek bir çözümün tüm yaşam döngüsünü sırayla önünüze getirir.


Node.js tarafı: pino ile aynı şema

pino, düşük ek yükle aynı JSON çıktısını üretir. Kritik nokta kütüphane değil disiplin: olay adlarını (captcha_submitted, captcha_solved, captcha_solve_failed) Python tarafıyla birebir aynı tutun ki iki dildeki servisleriniz tek şemada toplansın.

const pino = require('pino');

const log = pino({
  level: 'info',
  timestamp: pino.stdTimeFunctions.isoTime,
});

Bağlamı log.child() ile bağlayın

log.child(), structlog'daki log.bind() ile aynı işi görür: bağlam alanlarını türetilmiş bir logger'a bağlar; görev kimliği geldiğinde ikinci bir child ekleyin.

const axios = require('axios');

const API_KEY = 'YOUR_API_KEY';

async function solveCaptcha(captchaType, sitekey, pageUrl) {
  const taskLog = log.child({
    captchaType,
    siteUrl: pageUrl,
    sitekey: sitekey.substring(0, 12) + '...',
  });

  const start = Date.now();
  taskLog.info('captcha_submit_start');

  const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: {
      key: API_KEY, method: 'userrecaptcha',
      googlekey: sitekey, pageurl: pageUrl, json: 1,
    },
  });

  if (submit.data.status !== 1) {
    taskLog.error({ error: submit.data.request }, 'captcha_submit_failed');
    return null;
  }

  const taskId = submit.data.request;
  const boundLog = taskLog.child({ taskId });
  boundLog.info({ submitMs: Date.now() - start }, 'captcha_submitted');

  for (let attempt = 1; attempt <= 24; attempt++) {
    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) {
      boundLog.info({
        solveTimeMs: Date.now() - start,
        pollAttempts: attempt,
        tokenLength: poll.data.request.length,
      }, 'captcha_solved');
      return poll.data.request;
    }

    if (poll.data.request !== 'CAPCHA_NOT_READY') {
      boundLog.error({ error: poll.data.request, pollAttempts: attempt }, 'captcha_solve_failed');
      return null;
    }
  }

  boundLog.warn({ pollAttempts: 24 }, 'captcha_solve_timeout');
  return null;
}

Logları filtreleyin ve uyarı kurun

JSON loglar bir kez oluştuğunda yerelde jq ile ya da merkezî bir toplayıcıda (ELK, Grafana Loki) alana göre sorgulayabilirsiniz. Standart çıktıya yazmak, altyapıdan bağımsız kalmanın en pratik yoludur.

Son bir saatteki başarısız çözümleri bulun

# With jq
cat captcha.log | jq 'select(.level == "error" and .event == "captcha_solve_failed")'

Hata oranı yükselince ayrı bir olay yazın

Kayan bir pencerede başarı/başarısızlık oranını takip edin; eşik aşıldığında captcha_error_rate_high olayını yazın. Uyarı sisteminiz ham log satırlarını değil bu tek olay adını dinler — kuralın bakımı çok daha kolaydır.

# Count errors vs successes in a rolling window
from collections import deque

class ErrorRateMonitor:
    def __init__(self, window_size=100, threshold=0.2):
        self.results = deque(maxlen=window_size)
        self.threshold = threshold

    def record(self, success):
        self.results.append(success)
        if len(self.results) >= 50:
            error_rate = 1 - sum(self.results) / len(self.results)
            if error_rate > self.threshold:
                log.warning(
                    "captcha_error_rate_high",
                    error_rate=round(error_rate, 3),
                    window=len(self.results),
                )

Çözüm sürelerini thread planlamasına bağlayın

Loglar yalnızca sorun giderme aracı değil, aynı zamanda kapasite verisidir. Plan kararını iki alanla verin:

  • solve_time_ms → p50 ve p95, çözümün gerçekte ne kadar sürdüğünü söyler.
  • Eşzamanlı açık görev sayısı → kaç thread'e ihtiyacınız olduğunu söyler.

Belirleyici olan ikincisidir. CaptchaAI thread bazlı faturalandırır, çözüm başına değil: her plan thread başına sınırsız çözüm içerir, sınırı koyan eşzamanlılıktır.

Ölçümden plana: bir senaryo

İstanbul'daki bir e-ticaret müşterisi için ödeme adımı regresyon testleri yazdığınızı düşünün. Test paketi Europe/Istanbul saatiyle 03:00'te koşuyor ve iki haftalık loglar şunu gösteriyor:

Logdan gelen ölçüm Değer Plan kararına etkisi
p50 çözüm süresi 9 sn Test paketi süresini planlamaya yeter
p95 çözüm süresi 18 sn Zaman aşımı eşiğini buna göre kurun
Tepe eşzamanlılık 4 görev Gereken thread sayısını doğrudan verir

Bu profil için BASIC ($15/ay, 5 thread) rahatça yeter. Müşteri kampanya öncesi test hacmini katlarsa, eşzamanlılık eğrisi sizi kampanya haftasına girmeden ADVANCE ($90/ay, 50 thread) gibi bir plana yönlendirir. Fiyatlar USD üzerinden sabit olduğu için TL kur oynaklığından etkilenmeyen, öngörülebilir bir aylık maliyet elde edersiniz.

Aynı log akışını Prometheus ve Grafana ile çözüm oranı izlemeye besleyebilir, uzun vadeli analiz için çözüm sonuçlarını PostgreSQL'de saklayabilir veya kullanım panelinizi doğrudan bu alanların üzerine kurabilirsiniz.


Sorun giderme

Sorun Sebep Düzeltme
Loglar çok ayrıntılı Her sorgulama girişiminin loglanması Yalnızca gönderilen, çözülen ve başarısız olayları loglayın
Olaylar ilişkilendirilemiyor Eksik görev kimliği task_id'yi log.bind() veya log.child() ile erken bağlayın
Loglar aranamıyor Düz metin formatı structlog veya pino ile JSON'a geçin
İki servisin logları birleşmiyor Farklı olay adları Olay adlarını ve alan şemasını iki dilde tek listede sabitleyin
Loglardaki hassas veriler Tam API anahtarının loglanması API anahtarlarını asla loglamayın; sitekey'leri kısaltın

Loglara ne yazmamalısınız

Tablonun son satırı özellikle önemli: loglarda tam API anahtarı, e-posta veya başka kişisel veri tutmak yalnızca güvenlik değil, KVKK açısından da risktir. Üç kural yeter:

  • API anahtarlarını ve token'ların tamamını hiçbir seviyede yazmayın; token için yalnızca token_length tutun.
  • sitekey gibi uzun tanımlayıcıları ilk 12 karaktere kısaltın — hata ayıklamaya yeter.
  • Log saklama sürenizi yetkili QA ve veri toplama akışlarının ihtiyacıyla sınırlayın, süresi dolanı otomatik silin.

Sık sorulan sorular

Yapılandırılmış loglama üretim performansını yavaşlatır mı?

Pratikte hayır. Hem structlog hem pino satır başına birkaç mikrosaniyelik ek yük getirir; asıl maliyet log hacmindedir. Sorgulama döngüsünün her turunu değil yalnızca gönderim, çözüm ve hata olaylarını yazarsanız çözüm başına üç satırda kalırsınız.

Mevcut logging kütüphanemi değiştirmek zorunda mıyım?

Hayır. Python'un standart logging modülüne bir JSON formatlayıcı takarak da aynı şemayı üretebilirsiniz; structlog'un kazandırdığı bind() ile bağlam taşımaktır. Belirleyici olan kütüphane değil, alan adlarının tutarlılığıdır.

task_id'yi loglara ne zaman bağlamalıyım?

Gönderim yanıtıyla döner dönmez, sorgulama döngüsüne girmeden önce. Erken bağlarsanız tüm satırlar aynı task_id ile filtrelenir; geç bağlarsanız hatanın oluştuğu satırda kimlik bulunmaz.

Log satırlarında token'ın tamamını tutabilir miyim?

Tutmayın. Yalnızca uzunluğunu (token_length) yazın. Token'lar 500 karakteri aşabilir; tamamını loglamak gereksiz hacim ve sızıntı riski yaratır. CAPCHA_NOT_READY gibi beklenen ara durumları da yazmayın, yalnızca nihai sonucu loglayın.

Bu şema hangi CAPTCHA türlerinde çalışır?

Alan şeması türden bağımsızdır: yalnızca captcha_type değeri değişir. Aynı loglama katmanını reCAPTCHA v2, Cloudflare Turnstile, GeeTest v3 ve görüntü/OCR akışlarında değiştirmeden kullanabilirsiniz. Tek koşul, olay adlarını her entegrasyonda aynı tutmanız.


Gözlemlenebilir bir CAPTCHA akışıyla başlayın

Ücretsiz hesabınızı captchaai.com üzerinden açın, API anahtarınızı alın ve ilk çözümünüzü daha bugün task_id ile uçtan uca izleyin.


İlgili kılavuzlar

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