Integrations

Selenium Wire ile istek yakalama: CaptchaAI ile CAPTCHA çözme

Sitekey'i DOM'da aramayı bırakın: değer zaten ağ trafiğinde duruyor. Selenium Wire, tarayıcının yaptığı her HTTP isteğini Python nesnesi olarak önünüze koyduğu için recaptcha/api2/anchor isteğinin URL'sindeki k= parametresini, sayfa iframe'i çizmeyi bitirmeden okuyabilirsiniz. Tek satırlık bu fark, find_element zincirlerine dayanan entegrasyonları kırılgan olmaktan çıkarır.

Aşağıdaki Python sınıfı ağ trafiğinden CAPTCHA türünü tanıyor, görevi CaptchaAI'nin in.php uç noktasına gönderiyor, res.php'yi sorguluyor ve dönen token'ı forma yazıyor. Devamında başlık ekleme, istek kesme, HAR dışa aktarma ve eşzamanlılık planlaması var.


DOM sorgusu neden kırılır, ağ trafiği neden dayanıklıdır?

Klasik yaklaşım şudur: sayfayı açarsınız, [data-sitekey] seçicisini sorgularsınız, değeri alırsınız. Üretimde bu üç noktada tıkanır — ve tıkandığında testiniz "CAPTCHA yok" sanıp sessizce yanlış sonuç üretir.

  • Widget iframe içinde yüklenir. Ana belgede data-sitekey yoktur; seçici boş döner.
  • Parametreler AJAX ile gelir. Sitekey, sayfa yüklendikten saniyeler sonra bir JSON yanıtının içinde iner.
  • Sürüm ayrımı görünmez. DOM'a bakarak reCAPTCHA v2 ile v3'ü ayırmak zordur; ağ trafiğinde anchor ve render= istekleri bunu net biçimde söyler.

Ağ katmanı üçünün de dışındadır: tarayıcı hangi kaynağı çekiyorsa istek listesinde görünür, iframe içinde olması bunu değiştirmez.


Selenium Wire standart Selenium'a ne ekler?

Yetenek Standart Selenium Selenium Wire
İstekleri okuma Yok Var
Başlık değiştirme Sınırlı Tam kontrol
Yanıtı yakalama Yok Var
Proxy desteği Temel Kimlik doğrulamalı proxy
Ağ günlüğü Yalnızca DevTools Python nesnesi
HAR dışa aktarma Yok Var

Pratikte kazandığınız şey tek bir liste: driver.requests. Tüm istekleri başlık ve yanıt gövdeleriyle tutar.


Kurulum

pip install seleniumwire selenium webdriver-manager requests

Import satırı dışında mevcut Selenium kodunuz değişmez: from seleniumwire import webdriver yazdığınız anda aynı WebDriver API'si ağ yetenekleriyle çalışır.


Çözücü sınıfı: tanı, gönder, sorgula, enjekte et

Aşağıdaki sınıf dört işi üstleniyor ve bu dört adım her entegrasyonun iskeletidir:

  1. detect_captcha_from_requests — istek listesini tarar, anchor / reload / challenges.cloudflare.com imzalarından türü ve sitekey'i çıkarır.
  2. solve_captcha — türe göre method, googlekey veya key ve pageurl alanlarını doldurup görevi gönderir.
  3. Sorgulama döngüsü — res.php'yi beş saniyede bir sorgular; CAPCHA_NOT_READY yanıtında beklemeye devam eder.
  4. inject_token — dönen token'ı g-recaptcha-response veya cf-turnstile-response alanına yazar.
from seleniumwire import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import requests
import time
import json

class SeleniumWireCaptchaSolver:
    BASE_URL = "https://ocr.captchaai.com"

    def __init__(self, api_key, proxy=None):
        self.api_key = api_key
        self.proxy = proxy
        self.driver = None

    def create_driver(self, headless=True):
        options = webdriver.ChromeOptions()
        if headless:
            options.add_argument("--headless=new")
        options.add_argument("--no-sandbox")
        options.add_argument("--no-sandbox")
        options.add_argument("--window-size=1920,1080")

        wire_options = {}
        if self.proxy:
            wire_options["proxy"] = {
                "http": self.proxy,
                "https": self.proxy,
            }

        self.driver = webdriver.Chrome(
            service=Service(ChromeDriverManager().install()),
            options=options,
            seleniumwire_options=wire_options,
        )
        return self.driver

    def detect_captcha_from_requests(self):
        """Detect CAPTCHA type from intercepted network requests."""
        for request in self.driver.requests:
            url = request.url

            if "recaptcha/api2/anchor" in url or "recaptcha/enterprise/anchor" in url:
                # Extract sitekey from reCAPTCHA iframe URL
                import re
                match = re.search(r"k=([A-Za-z0-9_-]+)", url)
                if match:
                    return {
                        "type": "recaptcha_v2",
                        "sitekey": match.group(1),
                        "page_url": self.driver.current_url,
                    }

            if "recaptcha/api2/reload" in url or "recaptcha/enterprise/reload" in url:
                return {
                    "type": "recaptcha_v3",
                    "sitekey": self._extract_v3_sitekey(),
                    "page_url": self.driver.current_url,
                }

            if "challenges.cloudflare.com" in url:
                sitekey = self._extract_turnstile_sitekey()
                if sitekey:
                    return {
                        "type": "turnstile",
                        "sitekey": sitekey,
                        "page_url": self.driver.current_url,
                    }

        return None

    def _extract_v3_sitekey(self):
        for request in self.driver.requests:
            if "recaptcha" in request.url and "render=" in request.url:
                import re
                match = re.search(r"render=([A-Za-z0-9_-]+)", request.url)
                if match:
                    return match.group(1)
        return self.driver.execute_script(
            "return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
        )

    def _extract_turnstile_sitekey(self):
        return self.driver.execute_script(
            "return document.querySelector('.cf-turnstile[data-sitekey]')?.getAttribute('data-sitekey')"
        )

    def solve_captcha(self, captcha_info):
        """Solve detected CAPTCHA via CaptchaAI API."""
        params = {"key": self.api_key, "json": 1}

        if captcha_info["type"] == "recaptcha_v2":
            params.update({
                "method": "userrecaptcha",
                "googlekey": captcha_info["sitekey"],
                "pageurl": captcha_info["page_url"],
            })
        elif captcha_info["type"] == "recaptcha_v3":
            params.update({
                "method": "userrecaptcha",
                "googlekey": captcha_info["sitekey"],
                "pageurl": captcha_info["page_url"],
                "version": "v3",
                "action": "verify",
            })
        elif captcha_info["type"] == "turnstile":
            params.update({
                "method": "turnstile",
                "key": captcha_info["sitekey"],
                "pageurl": captcha_info["page_url"],
            })

        # Submit
        resp = requests.post(f"{self.BASE_URL}/in.php", data=params)
        data = resp.json()
        if data["status"] != 1:
            raise Exception(f"Submit: {data['request']}")

        task_id = data["request"]

        # Poll
        for _ in range(60):
            time.sleep(5)
            resp = requests.get(f"{self.BASE_URL}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1,
            })
            data = resp.json()
            if data["request"] == "CAPCHA_NOT_READY":
                continue
            if data["status"] != 1:
                raise Exception(f"Solve: {data['request']}")
            return data["request"]

        raise Exception("Timeout")

    def inject_token(self, captcha_type, token):
        """Inject solved token into the page."""
        if captcha_type in ("recaptcha_v2", "recaptcha_v3"):
            self.driver.execute_script(f"""
                document.querySelector('#g-recaptcha-response').value = '{token}';
                // Try hidden textareas in iframes
                document.querySelectorAll('[name="g-recaptcha-response"]').forEach(
                    el => el.value = '{token}'
                );
            """)
        elif captcha_type == "turnstile":
            self.driver.execute_script(f"""
                const input = document.querySelector('[name="cf-turnstile-response"]');
                if (input) input.value = '{token}';
            """)

    def close(self):
        if self.driver:
            self.driver.quit()

YOUR_API_KEY yer tutucusunu koda gömmeyin; ortam değişkeninden okuyun. Panelden aldığınız 32 karakterlik anahtar bütün türler için aynıdır.


Uçtan uca örnek: staging giriş formunun QA testi

Türkiye'deki ekiplerin çoğu bu akışı e-ticaret ve fintech tarafında kuruyor: her sürümden önce giriş formunun ve ödeme adımının kendi staging ortamında çalıştığı doğrulanmalı, formda da bir CAPTCHA duruyor. Aşağıdaki senaryo kendi staging adresinizde formu dolduruyor, CAPTCHA'yı ağ trafiğinden tanıyıp çözüyor ve gönderiyor.

def automate_login():
    solver = SeleniumWireCaptchaSolver(
        api_key="YOUR_API_KEY",
        proxy="http://user:pass@proxy.example.com:8080"
    )

    try:
        driver = solver.create_driver(headless=True)

        # Navigate and wait for CAPTCHA resources to load
        driver.get("https://staging.example.com/qa-login")
        time.sleep(3)

        # Fill form
        driver.find_element("id", "email").send_keys("user@example.com")
        driver.find_element("id", "password").send_keys("password123")

        # Detect CAPTCHA from network requests
        captcha = solver.detect_captcha_from_requests()

        if captcha:
            print(f"Found {captcha['type']} (sitekey: {captcha['sitekey'][:20]}...)")
            token = solver.solve_captcha(captcha)
            solver.inject_token(captcha["type"], token)
            print(f"Token injected: {token[:50]}...")

        # Submit
        driver.find_element("id", "submit").click()
        time.sleep(3)

        print(f"Current URL: {driver.current_url}")

    finally:
        solver.close()

automate_login()

Gece çalışan bir iş için zamanlamayı Europe/Istanbul saat diliminde kurun ve test kullanıcılarını tr-TR verileriyle besleyin; böylece rapor saatleri ekibin mesai saatleriyle örtüşür.


İstekleri değiştirmenin üç pratik yolu

Her isteğe başlık eklemek

request_interceptor, sayfanın yaptığı her istekten önce çalışır. Ortak Accept-Language değerini veya QA ortamınızın beklediği özel başlığı buradan geçirin.

def add_headers_interceptor(request):
    request.headers["X-Custom-Header"] = "value"
    request.headers["Accept-Language"] = "en-US,en;q=0.9"

driver = solver.create_driver()
driver.request_interceptor = add_headers_interceptor

Gereksiz üçüncü taraf isteklerini kesmek

Analitik ve reklam istekleri CAPTCHA'nın yüklenmesini geciktirir, test süresini uzatır. request.abort() ile bunları en baştan kesebilirsiniz.

def block_trackers(request):
    blocked = ["analytics", "tracking", "ads", "facebook.net", "doubleclick"]
    if any(b in request.url for b in blocked):
        request.abort()

driver.request_interceptor = block_trackers

Yanıt gövdesini test amaçlı değiştirmek

response_interceptor, kendi servisinizden dönen yapılandırmayı test sırasında değiştirmenizi sağlar — yalnızca sahibi olduğunuz staging servislerinde kullanın.

def modify_response(request, response):
    if "captcha-config" in request.url:
        # Modify CAPTCHA configuration response
        import json
        body = json.loads(response.body.decode("utf-8"))
        body["difficulty"] = "easy"
        response.body = json.dumps(body).encode("utf-8")

driver.response_interceptor = modify_response

Selenium Wire ile ağ katmanında hata ayıklama

Sınıf çalıştıktan sonra işin geri kalanı ağ katmanında geçer: sitekey'i JSON yanıtından çıkarmak, trafiği proxy üzerinden geçirmek ve sorunu kanıtlayan bir kayıt üretmek.

AJAX yanıtlarından sitekey çıkarma

Bazı siteler CAPTCHA yapılandırmasını HTML'e gömmez, ilk etkileşimden sonra bir JSON uç noktasından çeker. Aşağıdaki fonksiyon, verdiğiniz zaman aşımı boyunca driver.requests listesini tarayıp sitekey geçen ilk JSON yanıtını döndürür.

def extract_captcha_from_api(driver, timeout=10):
    """Watch network for CAPTCHA configuration API calls."""
    start = time.time()

    while time.time() - start < timeout:
        for request in driver.requests:
            if request.response and "captcha" in request.url.lower():
                try:
                    data = json.loads(request.response.body.decode())
                    if "sitekey" in str(data) or "siteKey" in str(data):
                        return data
                except (json.JSONDecodeError, UnicodeDecodeError):
                    pass
        time.sleep(0.5)

    return None

Birden fazla proxy ile çalışmak

Selenium Wire, kimlik doğrulamalı proxy'yi (user:pass@host:port) yerleşik destekler; standart Selenium'da eklenti isteyen bu ayar tek sözlükle biter. Sürücü örneği başına farklı bir çıkış tanımlayabilir, exclude_hosts ile statik kaynakları kapsam dışında bırakabilirsiniz.

from seleniumwire import webdriver

proxies = [
    "http://user:pass@proxy1.example.com:8080",
    "http://user:pass@proxy2.example.com:8080",
    "http://user:pass@proxy3.example.com:8080",
]

current_proxy = [0]

def rotate_proxy(request):
    proxy = proxies[current_proxy[0] % len(proxies)]
    request.headers["Proxy-Authorization"] = None  # Reset
    current_proxy[0] += 1

wire_options = {
    "proxy": {
        "http": proxies[0],
        "https": proxies[0],
    }
}

driver = webdriver.Chrome(seleniumwire_options=wire_options)

CAPTCHA trafiğini tek tabloda görmek

Bir entegrasyon sessizce başarısız olduğunda ilk soru şudur: tarayıcı CAPTCHA kaynaklarını gerçekten çekti mi? Aşağıdaki fonksiyon ilgili isteklerin durum kodunu, içerik türünü ve boyutunu tek listede toplar.

def analyze_captcha_traffic(driver):
    """Analyze all CAPTCHA-related network traffic."""
    captcha_requests = []

    for request in driver.requests:
        if any(k in request.url for k in ["recaptcha", "turnstile", "hcaptcha", "geetest"]):
            captcha_requests.append({
                "url": request.url,
                "method": request.method,
                "status": request.response.status_code if request.response else None,
                "content_type": request.response.headers.get("Content-Type") if request.response else None,
                "size": len(request.response.body) if request.response and request.response.body else 0,
            })

    print(f"\nCAPTCHA Network Traffic ({len(captcha_requests)} requests):")
    for req in captcha_requests:
        print(f"  [{req['status']}] {req['method']} {req['url'][:80]}...")

    return captcha_requests

HAR dışa aktarımı: destek biletine eklenecek dosya

Sorun sizde değil de hedef sayfanın davranışındaysa, ekran görüntüsü yerine HAR dosyası gönderin. Aşağıdaki fonksiyon istek ve yanıt başlıklarını standart HAR biçiminde kaydeder.

from seleniumwire.utils import decode

def export_har(driver, filename="captcha_traffic.har"):
    """Export HAR file for debugging CAPTCHA issues."""
    import json

    har = {
        "log": {
            "version": "1.2",
            "entries": []
        }
    }

    for request in driver.requests:
        if not request.response:
            continue

        entry = {
            "request": {
                "method": request.method,
                "url": request.url,
                "headers": [{"name": k, "value": v} for k, v in request.headers.items()],
            },
            "response": {
                "status": request.response.status_code,
                "headers": [{"name": k, "value": v} for k, v in request.response.headers.items()],
            },
        }
        har["log"]["entries"].append(entry)

    with open(filename, "w") as f:
        json.dump(har, f, indent=2)

    print(f"HAR exported: {filename} ({len(har['log']['entries'])} entries)")

HAR dosyaları çerez ve başlık taşır; paylaşmadan önce hassas alanları temizleyin, saklama süresi belirleyin.


Eşzamanlılık ve maliyet: kaç thread gerekiyor?

CaptchaAI thread tabanlı çalışır: ödediğiniz şey eşzamanlı çözüm kapasitesidir, çözüm başına ücret yoktur. Bir thread, o an işlenen tek bir CAPTCHA demektir.

  • BASIC ($15/ay, 5 thread) — tek CI hattı ve gece koşan birkaç senaryo için yeterli.
  • ADVANCE ($90/ay, 50 thread) — paralel test filoları ve düzenli veri kazıma işleri bu bantta rahat eder.

Kaba hesabı çözüm sürelerinden yapın: reCAPTCHA v2 60 saniyenin, Cloudflare Turnstile 10 saniyenin, GeeTest v3 ise 12 saniyenin altında tamamlanır. Kuyrukta bekleyen görev sayısı sürekli artıyorsa darboğaz thread sayısıdır. Kur dalgalanmasının bütçeyi zorladığı bir pazarda aylık sabit USD tutarı planlamayı kolaylaştırır.

Kapsam konusunda net olun: CaptchaAI reCAPTCHA v2 ve v3 (Enterprise sürümleri dahil), Cloudflare Turnstile ve Cloudflare doğrulama akışı, GeeTest v3, görüntü/OCR, grid ve BLS türlerini çözer. CaptchaFox (beta), Friendly Captcha (beta) ve Lemin (beta) beta aşamasındadır. hCaptcha ve FunCaptcha (Arkose Labs) desteklenmiyor; GeeTest v4 için yalnızca "çok yakında" ifadesi geçerlidir.


Sorun giderme

Sorun Olası neden Çözüm
seleniumwire import hatası Paket kurulu değil pip install seleniumwire çalıştırın
driver.requests boş dönüyor Liste, istekler tamamlanmadan sorgulandı Gezinmeden sonra kısa bir bekleme ekleyin
Proxy üzerinden SSL hatası Sertifika zinciri doğrulanamıyor seleniumwire_options['verify_ssl'] = False ayarlayın
Bellek kullanımı sürekli artıyor İstek geçmişi birikiyor Periyodik olarak del driver.requests çağırın
Sayfalar yavaş açılıyor Tüm trafik proxy üzerinden geçiyor exclude_hosts ile statik alan adlarını kapsam dışında bırakın
CAPTCHA tespit edilemiyor Widget iframe içinde yükleniyor iframe URL'leri için driver.requests listesini tarayın

Sık sorulan sorular

Ağ trafiğinden hangi CAPTCHA türlerini tanıyabilirim?

Ayırt edici bir ağ imzası olan her türü. reCAPTCHA v2 api2/anchor, reCAPTCHA v3 render= parametresi, Cloudflare Turnstile ise challenges.cloudflare.com isteğiyle kendini belli eder. Yukarıdaki sınıf bu üçünü tanıyor; GeeTest v3 için de aynı desene kendi kontrolünüzü ekleyebilirsiniz.

Token'ı enjekte ettim ama form yine reddediyor, neden?

Birincisi, pageurl değeri tarayıcının o an bulunduğu adresle birebir aynı olmalıdır; sondaki eğik çizgi farkı bile yeter. İkincisi, token kısa ömürlüdür — enjeksiyondan sonra formu bekletmeden gönderin. Üçüncüsü, callback bekleyen widget'larda yalnızca gizli alanı doldurmak yetmez; sayfanın kendi doğrulama fonksiyonunu da tetiklemeniz gerekir.

Uzun süren oturumlarda bellek neden şişiyor?

driver.requests listesi sürücü açık kaldığı sürece büyür ve yanıt gövdelerini de tutar. Saatlerce çalışan işlerde her senaryo sonunda del driver.requests çağırın, sürücüyü belirli aralıklarla yeniden başlatın.

KVKK açısından nelere dikkat etmeliyim?

Yakalanan trafik ve dışa aktardığınız HAR dosyaları kişisel veri içerebilir: test kullanıcısının e-postası, oturum başlıkları, form alanları. Bu dosyaları paylaşılan bir depoya atmadan önce maskeleyin, erişimi ekiple sınırlayın ve saklama süresi tanımlayın. Testlerde gerçek müşteri verisi yerine üretilmiş tr-TR verisi kullanın.


İlgili kılavuzlar


Ağ trafiğini okumaya bugün başlayın — CaptchaAI anahtarınızı alın ve ilk token'ı Selenium Wire ile forma yazın.

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