Tutorials

Cloudflare Turnstile'ı Python requests ile çözme

Turnstile'ı çözmek için Selenium veya Playwright açmanız gerekmiyor: sayfanın HTML'inden sitekey değerini okuyup CaptchaAI'ye method=turnstile ile bir görev gönderirseniz, birkaç saniye sonra elinizde geçerli bir cf-turnstile-response token'ı olur. Geriye tek iş kalır — o token'ı formunuzun gövdesine koyup göndermek.

Bu yaklaşımın kazandırdığı şey RAM'dir. Headless bir Chrome örneği sunucunuzda 300–500 MB yer kaplar; aynı işi yapan bir requests oturumu birkaç megabaytla yetinir. Tek bir küçük VPS üzerinde onlarca eşzamanlı akış çalıştıran freelance otomasyon geliştiricileri için fark, iki katı sunucu faturası demektir.

Aşağıdaki akış üç adımdan oluşur ve tamamı standart kütüphane + requests ile çalışır.


Başlamadan önce elinizde ne olmalı

pip install requests

Gerekenler:

  • captchaai.com üzerinden alınmış bir CaptchaAI API anahtarı
  • Doğrulamayı içeren sayfanın tam URL'si (pageurl olarak gönderilecek)
  • Sayfadaki Turnstile sitekey değeri — bir sonraki adımda çıkaracağız

Hesabınız henüz yoksa panelden API anahtarınızı üretmek birkaç dakika sürer; anahtar 32 karakterlik bir dizedir ve tüm çağrılarda key parametresi olarak gider.


Adım 1: Sayfadan Turnstile sitekey'ini çıkarın

Turnstile widget'ı, sayfaya data-sitekey özniteliğiyle gömülür. Aşağıdaki fonksiyon üç yaygın gömme biçimini de yakalar; sayfayı gerçek bir tarayıcı gibi karşılamak için başlıkları da ayarlar.

import re
import requests

def extract_turnstile_sitekey(url):
    """Extract Cloudflare Turnstile sitekey from page HTML."""
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                      "AppleWebKit/537.36 Chrome/120.0.0.0",
        "Accept": "text/html,*/*;q=0.8",
        "Accept-Language": "en-US,en;q=0.9",
    }
    response = requests.get(url, headers=headers, timeout=15)

    patterns = [
        r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']',
        r"sitekey\s*:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]",
        r"siteKey\s*[=:]\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]",
    ]

    for pattern in patterns:
        match = re.search(pattern, response.text)
        if match:
            return match.group(1)

    return None


sitekey = extract_turnstile_sitekey("https://example.com/signup")
print(f"Sitekey: {sitekey}")

Adım 2: Çözüm görevini CaptchaAI'ye gönderin

sitekey elinizdeyken görevi in.php uç noktasına POST edersiniz. Yanıt anında token dönmez; size bir görev kimliği verir. json=1 parametresini eklemeyi unutmayın, aksi hâlde düz metin yanıt alırsınız.

import requests

API_KEY = "YOUR_API_KEY"

def submit_turnstile(sitekey, page_url):
    """Submit Turnstile solving task to CaptchaAI."""
    response = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    })

    data = response.json()

    if data.get("status") != 1:
        raise Exception(f"Submit failed: {data.get('request')}")

    return data["request"]


task_id = submit_turnstile("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://example.com/signup")
print(f"Task ID: {task_id}")

Adım 3: Token için sonucu sorgulayın

Görev kimliğiyle res.php uç noktasını periyodik olarak sorgularsınız. Beş saniyelik aralık pratikte iyi bir dengedir: daha sık sorgulamak boşuna istek üretir. Turnstile için çözüm süresi CaptchaAI'nin yayınladığı SLA'ya göre <10 s seviyesindedir, dolayısıyla ilk iki turda yanıt gelmesi olağandır.

ERROR_CAPTCHA_UNSOLVABLE yanıtını beklemeden hata olarak ele alın — bu görev bir daha çözülmeyecektir, tekrar sorgulamak yalnızca zaman aşımını uzatır.

import time

def poll_result(task_id, timeout=120):
    """Poll CaptchaAI for the solved Turnstile token."""
    start = time.time()

    while time.time() - start < timeout:
        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.get("status") == 1:
            return result["request"]

        if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
            raise Exception("Turnstile could not be solved")

    raise TimeoutError("Solve timed out")


token = poll_result(task_id)
print(f"Token: {token[:50]}...")

Üç adımın tamamı: uçtan uca çalışan betik

Aşağıdaki betik yukarıdaki üç adımı tek bir requests.Session() üzerinde birleştirir. Oturumu tekrar kullanmak önemlidir: sayfayı çeken istekle formu gönderen istek aynı çerezleri taşımalıdır, aksi hâlde token geçerli olsa bile gönderim reddedilebilir.

Örnekteki e-posta ve parola alanları yalnızca yer tutucudur; kendi kayıt formunuzun alan adlarıyla değiştirin.

import re
import time
import requests

API_KEY = "YOUR_API_KEY"
TARGET_URL = "https://example.com/signup"


def solve_turnstile(sitekey, page_url):
    """Full Turnstile solve: submit + poll."""
    # Submit
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "json": 1,
    })

    data = submit.json()
    if data.get("status") != 1:
        raise Exception(f"Submit error: {data.get('request')}")

    task_id = data["request"]
    print(f"Task submitted: {task_id}")

    # Poll
    for _ in range(30):
        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.get("status") == 1:
            return result["request"]

    raise TimeoutError("Solve timed out")


# --- Main flow ---
session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 Chrome/120.0.0.0",
    "Accept": "text/html,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
})

# 1. Get page and extract sitekey
response = session.get(TARGET_URL, timeout=15)
match = re.search(r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text)
if not match:
    raise ValueError("Turnstile sitekey not found")
sitekey = match.group(1)
print(f"Sitekey: {sitekey}")

# 2. Solve Turnstile
token = solve_turnstile(sitekey, TARGET_URL)
print(f"Token: {token[:50]}...")

# 3. Submit form with token
form_response = session.post(TARGET_URL, data={
    "cf-turnstile-response": token,
    "email": "user@example.com",
    "password": "SecurePass123",
})
print(f"Form status: {form_response.status_code}")

action parametresi isteyen Turnstile widget'ları

Bazı entegrasyonlar widget'a bir data-action özniteliği verir ve token'ı sunucu tarafında bu değere göre doğrular. Böyle bir sayfada action göndermezseniz token teknik olarak geçerli döner ama form yine de reddeder — hata ayıklarken en çok zaman kaybettiren senaryolardan biri budur.

Çözüm basit: data-action değerini de HTML'den okuyun ve görev gönderimine ekleyin.

def solve_turnstile_with_action(sitekey, page_url, action):
    """Solve Turnstile that requires an action parameter."""
    submit = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": sitekey,
        "pageurl": page_url,
        "action": action,  # Include the action from data-action attribute
        "json": 1,
    })

    data = submit.json()
    if data.get("status") != 1:
        raise Exception(f"Submit error: {data.get('request')}")

    task_id = data["request"]

    for _ in range(30):
        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.get("status") == 1:
            return result["request"]

    raise TimeoutError("Solve timed out")

Token'ı forma iliştirmenin üç kalıbı

Token'ı aldıktan sonra karşınıza çıkan tek soru şudur: hangi alan adıyla göndereceğim? Sayfanın form HTML'ini bir kez okuyup aşağıdaki üç kalıptan hangisine uyduğuna karar verin. Token'lar kısa ömürlüdür — çözümden sonra bekletmeden gönderin.

Kalıp 1: cf-turnstile-response ile klasik form POST'u

# Most common — Turnstile uses cf-turnstile-response field
response = session.post(form_url, data={
    "cf-turnstile-response": token,
    "email": "user@example.com",
})

Turnstile'ın standart alan adı budur ve gördüğünüz formların çoğu bunu kullanır.

Kalıp 2: JSON gövdeli API uç noktası

response = session.post(api_url, json={
    "turnstileToken": token,
    "email": "user@example.com",
})

Tek sayfalık uygulamalar (SPA) genelde formu değil doğrudan bir JSON API'sini besler; alan adı turnstileToken gibi camelCase olabilir.

Kalıp 3: Yeniden adlandırılmış özel alan

# Some sites rename the field — check the form HTML
response = session.post(form_url, data={
    "cf-turnstile-response": token,
    "captcha_token": token,  # Custom duplicate field
    "action": "signup",
})

Bazı ekipler alanı kendi şemalarına göre yeniden adlandırır. Emin değilseniz her iki adı da göndermek zararsızdır.


Üretime hazır çözücü sınıfı

Yukarıdaki parçalar öğrenmek için iyidir; üretimde ise yeniden deneme mantığına ve hangi hataların tekrar denenmeyeceğini bilen bir yapıya ihtiyacınız olur. Aşağıdaki sınıf tam olarak bunu yapar.

Dikkat edilecek nokta: ERROR_ZERO_BALANCE ve ERROR_WRONG_USER_KEY hataları yeniden denenmez. Bunlar geçici ağ sorunları değil hesap düzeyinde durumlardır; tekrar denemek yalnızca gecikme üretir. Bakiyeniz bittiğinde veya anahtar yanlış olduğunda döngüyü hemen kırmak, üretimde saatlerce sessizce dönen bir worker'dan çok daha iyidir.

import re
import time
import requests

class TurnstileSolver:
    """Production-ready Turnstile solver with retry logic."""

    API_URL = "https://ocr.captchaai.com"

    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries

    def extract_sitekey(self, session, url):
        """Extract Turnstile sitekey from page."""
        response = session.get(url, timeout=15)
        match = re.search(
            r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', response.text
        )
        return match.group(1) if match else None

    def solve(self, sitekey, page_url, action=None):
        """Solve Turnstile with retry logic. Returns token string."""
        for attempt in range(1, self.max_retries + 1):
            try:
                token = self._solve_once(sitekey, page_url, action)
                return token
            except TimeoutError:
                print(f"Attempt {attempt} timed out")
            except Exception as e:
                error_str = str(e)
                if "ERROR_ZERO_BALANCE" in error_str:
                    raise  # Don't retry billing errors
                if "ERROR_WRONG_USER_KEY" in error_str:
                    raise
                print(f"Attempt {attempt} failed: {e}")

        raise Exception(f"Failed after {self.max_retries} attempts")

    def _solve_once(self, sitekey, page_url, action=None):
        """Single solve attempt."""
        params = {
            "key": self.api_key,
            "method": "turnstile",
            "sitekey": sitekey,
            "pageurl": page_url,
            "json": 1,
        }
        if action:
            params["action"] = action

        submit = requests.post(f"{self.API_URL}/in.php", data=params, timeout=30)
        submit.raise_for_status()
        data = submit.json()

        if data.get("status") != 1:
            raise Exception(f"Submit error: {data.get('request')}")

        task_id = data["request"]

        for _ in range(30):
            time.sleep(5)
            result = requests.get(f"{self.API_URL}/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1,
            }, timeout=30).json()

            if result.get("status") == 1:
                return result["request"]
            if result.get("request") == "ERROR_CAPTCHA_UNSOLVABLE":
                raise Exception("CAPTCHA unsolvable")

        raise TimeoutError("Poll timed out")


# Usage
solver = TurnstileSolver("YOUR_API_KEY")
token = solver.solve("0x4AAAAAAAC3DHQhMMQ_Rxrg", "https://example.com/signup")

Sorun giderme tablosu

Belirti Sebep Çözüm
Token geliyor ama form reddediyor Yanlış sitekey veya eksik action sitekey'i yeniden çıkarın, data-action varsa gönderime ekleyin
sitekey bulunamıyor Widget JavaScript ile sonradan yükleniyor Dinamik sayfalarda Selenium veya Playwright ile HTML'i alın
Sayfayı çekerken HTTP 403 İstek tarayıcı başlıkları taşımıyor User-Agent ve Accept-Language başlıklarını ekleyin
Çözüm 60 saniyeyi aşıyor Kuyrukta yoğunluk Yoğun saatlerde olağandır; zaman aşımını yükseltin
Token bir kez çalışıp sonra reddediliyor Site her denemede yeni token bekliyor Her gönderim için ayrı bir token çözün, token'ı önbelleğe almayın

Yerel senaryo: Türkiye'de bir e-ticaret QA akışı

Türkiye'deki geliştirici pazarı ağırlıkla e-ticaret ve fintech entegrasyonlarına dayanıyor; bu yüzden en sık karşılaşılan pratik senaryo da kendi kayıt veya ödeme formunuzun otomatik testidir.

Diyelim ki staging.example.com/qa-signup adresindeki kayıt formunuz Turnstile ile korunuyor ve her gece 200 kayıt senaryosu koşuyorsunuz. Bunu headless tarayıcıyla yaparsanız 200 Chrome örneği açıp kapatmanız gerekir; yukarıdaki requests akışıyla aynı iş tek bir Python worker'ında sıraya girer.

Maliyet tarafı da bu noktada netleşiyor. CaptchaAI planları thread tabanlıdır: eşzamanlı çözüm sayısına göre ödersiniz, çözüm başına değil. Gecelik 200 senaryo için BASIC ($15/ay, 5 thread) fazlasıyla yeter; aynı akışı gündüz de sürekli çalıştıran ekipler STANDARD ($30/ay, 15 thread) ile rahat eder. TL kurundaki oynaklık düşünüldüğünde, aylık sabit USD tutarının önceden bilinmesi bütçe planlamasını kolaylaştıran bir ayrıntı.

Bir uyarı: test verisi ürettiğiniz akışlarda gerçek kişilere ait bilgileri kullanmayın. Topladığınız veri kişisel veri niteliği taşıyorsa KVKK kapsamına girer — dummy veriyle çalışın (+90 formatında sahte numaralar, tr-TR yerel ayarı, Europe/Istanbul saat dilimi) ve yalnızca yetkiniz olan sistemlerde test edin.


Sık sorulan sorular

Turnstile için gerçekten tarayıcıya ihtiyacım var mı?

Hayır. sitekey sayfanın statik HTML'inde göründüğü sürece bu yazıdaki requests akışı yeterlidir. Tarayıcı yalnızca widget JavaScript ile sonradan enjekte ediliyorsa gerekir — o durumda tarayıcıyı sadece HTML'i almak için kullanıp çözümü yine API'ye bırakabilirsiniz.

Token ne kadar süre geçerli kalır?

Turnstile token'ları kısa ömürlüdür ve tek kullanımlıktır. Çözümü aldıktan sonra gönderimi geciktirmeyin; bir token'ı ikinci bir istekte tekrar kullanmaya çalışmak neredeyse her zaman reddedilir.

sitekey doğru ama form yine reddediyor, nereye bakmalıyım?

Önce sayfadaki data-action özniteliğine bakın: varsa görev gönderimine action parametresini eklemeniz gerekir. Sonra oturum çerezlerini kontrol edin — sayfayı çeken ve formu gönderen isteklerin aynı requests.Session() üzerinden gitmesi şarttır.

Kaç thread'e ihtiyacım olduğunu nasıl hesaplarım?

Thread sayısı eşzamanlı çözüm sayısıdır, aylık toplam hacim değil. Turnstile için çözüm süresi <10 s seviyesinde olduğundan tek bir thread saatte yüzlerce çözüm çevirebilir. Sıralı çalışan bir betik için BASIC ($15/ay, 5 thread) genellikle yeterlidir; paralel worker'larınızın sayısı thread sayısını aşmaya başladığında bir üst plana geçin.

CaptchaAI hCaptcha'yı da çözüyor mu?

Hayır. CaptchaAI şu anda hCaptcha ve FunCaptcha (Arkose Labs) desteklemiyor. Desteklenen tarafta reCAPTCHA v2/v3, Cloudflare Turnstile ve Cloudflare doğrulama akışı, GeeTest v3, görüntü/OCR, grid ve BLS bulunuyor; CaptchaFox (beta), Friendly Captcha (beta) ve Lemin (beta) ise beta aşamasında. GeeTest v4 için çok yakında notu geçerli.


Özetle

Akışın tamamı üç satırda özetlenebilir: sitekey'i sayfa HTML'inden çıkarın, CaptchaAI API'sine method=turnstile ile görev gönderin, sonucu sorgulayıp dönen token'ı cf-turnstile-response alanıyla forma iliştirin. Sayfada data-action varsa action parametresini de eklemeyi unutmayın.

Tarayıcı açmadığınız için bellek tüketimi düşük, dağıtımı basit ve hata ayıklaması nettir. Turnstile, CaptchaAI'nin desteklediği tipler arasında <10 s SLA'sıyla düşük gecikmeli tarafta yer alıyor ve desteklenen tiplerde yüksek başarı oranı sunuyor — üretime almadan önce yeniden deneme mantığını ve token'ın tek kullanımlık olduğunu göz önünde bulundurun.

İlgili makaleler

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