Explainers

Cloudflare Turnstile Uygulama Tespit Kılavuzu

Bir sayfada Cloudflare Turnstile'ı tespit etmek, aslında üç sinyalden en az birini bulmaya indirgenir:

  • challenges.cloudflare.com/turnstile script etiketi
  • cf-turnstile sınıflı konteyner
  • data-sitekey niteliği (veya JS yapılandırmasındaki sitekey)

Bu sinyalleri hangi yöntemle arayacağınız ise Turnstile'ın sayfaya nasıl yerleştirildiğine bağlıdır: kaynak HTML'de statik mi, turnstile.render() çağrısıyla mı, yoksa kullanıcı etkileşiminden sonra dinamik olarak mı. Aşağıda üç senaryonun her biri için çalışan Python ve Node.js kodu var — sitekey'i çıkarın, widget modunu belirleyin ve token'ı CaptchaAI ile alın.


Turnstile'ın üç yerleştirme biçimi

Turnstile bir sayfaya üç farklı şekilde eklenir ve her biri farklı bir tespit yaklaşımı gerektirir:

Yerleştirme Nasıl görünür? Tespit zorluğu
HTML içinde Sayfa kaynağında doğrudan <div class="cf-turnstile" data-sitekey="..."> Kolay (statik HTML)
JavaScript ile turnstile.render() bir script içinde çağrılır Orta (JS ayrıştırma)
Dinamik yükleme Widget, kullanıcı eylemi veya XHR sonrasında gelir Zor (JS çalıştırma gerekir)

Hangi yöntemle başlayacağınızı seçmek için kısa bir yol haritası:

  • Sayfa kaynağında cf-turnstile görünüyorsa → Yöntem 1 (statik HTML)
  • Yalnızca bir turnstile.render() çağrısı varsa → Yöntem 2 (JavaScript API)
  • Widget etkileşimden veya XHR'den sonra geliyorsa → Yöntem 3 (Selenium/Puppeteer)

Örneğin bir e-ticaret ekibinin ödeme akışını otomatik test ettiğini düşünün: staging.example.com/qa-login sayfasındaki Turnstile widget'ı bazen doğrudan kaynak HTML'de bulunur, bazen de kullanıcı sepete ürün ekledikten sonra XHR ile gelir. Bu yüzden tek bir yönteme güvenmek yerine üçünü de bilmek gerekir; hangisinin devreye gireceği widget'ın o sayfada nasıl yüklendiğine bağlıdır.


Yöntem 1: statik HTML'den tespit

En basit Turnstile entegrasyonu cf-turnstile sınıfını ve data-sitekey niteliğini kullanır. Bu durumda tek bir HTTP isteği ve birkaç regex yeterlidir — tarayıcı çalıştırmaya gerek yoktur. Aşağıdaki fonksiyon sitekey'in yanında widget modunu, temayı ve action değerini de çıkarır:

import re
import requests

def detect_turnstile_html(url):
    """Detect Turnstile from static 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)
    html = response.text

    result = {
        "turnstile_found": False,
        "sitekey": None,
        "mode": None,
        "theme": None,
        "action": None,
        "script_loaded": False,
    }

    # Check for Turnstile script
    if "challenges.cloudflare.com/turnstile" in html:
        result["script_loaded"] = True

    # Check for widget container
    if "cf-turnstile" in html:
        result["turnstile_found"] = True

        # Extract sitekey
        sitekey_match = re.search(
            r'data-sitekey=["\']([0-9x][A-Za-z0-9_-]+)["\']', html
        )
        if sitekey_match:
            result["sitekey"] = sitekey_match.group(1)

        # Extract mode
        if 'data-size="invisible"' in html:
            result["mode"] = "invisible"
        elif 'data-appearance="interaction-only"' in html:
            result["mode"] = "non-interactive"
        else:
            result["mode"] = "managed"

        # Extract theme
        theme_match = re.search(r'data-theme=["\'](\w+)["\']', html)
        if theme_match:
            result["theme"] = theme_match.group(1)

        # Extract action
        action_match = re.search(r'data-action=["\']([^"\']+)["\']', html)
        if action_match:
            result["action"] = action_match.group(1)

    return result


# Usage
info = detect_turnstile_html("https://staging.example.com/qa-login")
if info["turnstile_found"]:
    print(f"Sitekey: {info['sitekey']}")
    print(f"Mode: {info['mode']}")

Yöntem 2: turnstile.render() (JavaScript API) tespiti

Bazı siteler widget'ı HTML niteliği yerine JavaScript içinden turnstile.render() ile oluşturur. Bu durumda sitekey'i, çağrının ikinci argümanındaki yapılandırma nesnesinden çekmeniz gerekir:

import re

def detect_turnstile_js_api(html):
    """Detect Turnstile from JavaScript render calls."""
    patterns = [
        # turnstile.render('#element', {sitekey: '...'})
        r"turnstile\.render\s*\(\s*['\"]([^'\"]+)['\"]\s*,\s*\{([^}]+)\}",
        # turnstile.render(element, {sitekey: '...'})
        r"turnstile\.render\s*\([^,]+,\s*\{([^}]+)\}",
    ]

    for pattern in patterns:
        match = re.search(pattern, html, re.DOTALL)
        if match:
            config_text = match.group(match.lastindex)

            # Extract sitekey from config object
            sitekey_match = re.search(
                r"sitekey\s*:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", config_text
            )
            # Extract callback
            callback_match = re.search(
                r"callback\s*:\s*(\w+|function)", config_text
            )
            # Extract action
            action_match = re.search(
                r"action\s*:\s*['\"]([^'\"]+)['\"]", config_text
            )
            # Extract appearance
            appearance_match = re.search(
                r"appearance\s*:\s*['\"]([^'\"]+)['\"]", config_text
            )

            return {
                "found": True,
                "method": "javascript_api",
                "sitekey": sitekey_match.group(1) if sitekey_match else None,
                "callback": callback_match.group(1) if callback_match else None,
                "action": action_match.group(1) if action_match else None,
                "appearance": appearance_match.group(1) if appearance_match else None,
            }

    return {"found": False, "method": None}

Yöntem 3: dinamik yüklemeyi tespit (Selenium/Puppeteer)

Widget ilk HTML'de yoksa ve yalnızca kullanıcı etkileşiminden sonra geliyorsa, sayfayı gerçek bir tarayıcıda çalıştırmanız gerekir. Selenium ve Puppeteer, JavaScript'i yürütür ve DOM oluştuktan sonra sitekey'i okumanıza izin verir:

Python (Selenium)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re

def detect_turnstile_dynamic(url):
    """Detect dynamically loaded Turnstile using Selenium."""
    options = webdriver.ChromeOptions()
    options.add_argument("--no-sandbox")
    driver = webdriver.Chrome(options=options)

    try:
        driver.get(url)

        # Wait for page to fully load
        WebDriverWait(driver, 10).until(
            lambda d: d.execute_script("return document.readyState") == "complete"
        )

        result = {
            "turnstile_found": False,
            "sitekey": None,
            "iframe_present": False,
            "response_field": False,
        }

        # Check for Turnstile iframe
        iframes = driver.find_elements(By.CSS_SELECTOR, "iframe[src*='challenges.cloudflare.com']")
        if iframes:
            result["turnstile_found"] = True
            result["iframe_present"] = True

        # Check for cf-turnstile container
        containers = driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile, [data-sitekey]")
        for container in containers:
            sitekey = container.get_attribute("data-sitekey")
            if sitekey:
                result["turnstile_found"] = True
                result["sitekey"] = sitekey

        # Check for hidden response field
        response_fields = driver.find_elements(
            By.CSS_SELECTOR, "[name='cf-turnstile-response'], [name='g-recaptcha-response']"
        )
        if response_fields:
            result["response_field"] = True

        # Check page source for JS API render
        page_source = driver.page_source
        js_match = re.search(
            r"sitekey\s*:\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]", page_source
        )
        if js_match and not result["sitekey"]:
            result["sitekey"] = js_match.group(1)
            result["turnstile_found"] = True

        return result

    finally:
        driver.quit()

Node.js (Puppeteer)

const puppeteer = require("puppeteer");

async function detectTurnstileDynamic(url) {
  const browser = await puppeteer.launch({
    headless: "new",
    args: ["--no-sandbox"],
  });

  const page = await browser.newPage();

  const result = {
    turnstileFound: false,
    sitekey: null,
    iframePresent: false,
    responseField: false,
    scriptUrl: null,
  };

  // Monitor network for Turnstile script
  page.on("response", (response) => {
    if (response.url().includes("challenges.cloudflare.com/turnstile")) {
      result.scriptUrl = response.url();
    }
  });

  await page.goto(url, { waitUntil: "networkidle2" });

  // Check for Turnstile container
  const sitekey = await page.evaluate(() => {
    const el = document.querySelector(
      ".cf-turnstile, [data-sitekey]"
    );
    return el ? el.getAttribute("data-sitekey") : null;
  });

  if (sitekey) {
    result.turnstileFound = true;
    result.sitekey = sitekey;
  }

  // Check for Turnstile iframe
  const iframes = await page.$$("iframe[src*='challenges.cloudflare.com']");
  if (iframes.length > 0) {
    result.turnstileFound = true;
    result.iframePresent = true;
  }

  // Check for response field
  const responseField = await page.$(
    "[name='cf-turnstile-response']"
  );
  result.responseField = !!responseField;

  await browser.close();
  return result;
}

detectTurnstileDynamic("https://staging.example.com/qa-login").then(console.log);

Tüm yöntemleri birleştiren tespit sınıfı

Üç yaklaşımı tek bir sınıfta toplarsanız, herhangi bir URL'yi verip geriye sitekey, mod ve yerleştirme biçimini alırsınız. Aşağıdaki TurnstileDetector, script etiketini, cf-turnstile konteynerini ve birden çok sitekey kalıbını sırayla dener:

import re
import requests

class TurnstileDetector:
    """Detect Cloudflare Turnstile across all implementation methods."""

    TURNSTILE_SCRIPT = "challenges.cloudflare.com/turnstile"
    SITEKEY_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_-]+)['\"]",
        r"TURNSTILE_SITE_KEY\s*[=:]\s*['\"]([0-9x][A-Za-z0-9_-]+)['\"]",
    ]

    def __init__(self, url, html=None):
        self.url = url
        self.html = html
        if not self.html:
            self._fetch()

    def _fetch(self):
        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(self.url, headers=headers, timeout=15)
        self.html = response.text

    def detect(self):
        """Run all detection methods and return results."""
        return {
            "url": self.url,
            "turnstile_present": self.has_turnstile(),
            "sitekey": self.extract_sitekey(),
            "mode": self.detect_mode(),
            "implementation": self.detect_implementation(),
            "script_loaded": self.has_script(),
            "response_field": self.has_response_field(),
            "action": self.extract_action(),
            "theme": self.extract_theme(),
        }

    def has_turnstile(self):
        return (
            self.has_script()
            or "cf-turnstile" in self.html
            or self.extract_sitekey() is not None
        )

    def has_script(self):
        return self.TURNSTILE_SCRIPT in self.html

    def has_response_field(self):
        return "cf-turnstile-response" in self.html

    def extract_sitekey(self):
        for pattern in self.SITEKEY_PATTERNS:
            match = re.search(pattern, self.html)
            if match:
                return match.group(1)
        return None

    def detect_mode(self):
        if 'data-size="invisible"' in self.html or "size: 'invisible'" in self.html:
            return "invisible"
        if 'data-appearance="interaction-only"' in self.html:
            return "non-interactive"
        if "cf-turnstile" in self.html:
            return "managed"
        return "unknown"

    def detect_implementation(self):
        if "cf-turnstile" in self.html and re.search(r"data-sitekey=", self.html):
            return "html_implicit"
        if "turnstile.render" in self.html:
            return "javascript_explicit"
        if self.has_script() and not "cf-turnstile" in self.html:
            return "dynamic_loading"
        return "unknown"

    def extract_action(self):
        match = re.search(r'data-action=["\']([^"\']+)["\']', self.html)
        if match:
            return match.group(1)
        match = re.search(r"action\s*:\s*['\"]([^'\"]+)['\"]", self.html)
        return match.group(1) if match else None

    def extract_theme(self):
        match = re.search(r'data-theme=["\'](\w+)["\']', self.html)
        return match.group(1) if match else "auto"


# Usage
detector = TurnstileDetector("https://staging.example.com/qa-login")
info = detector.detect()

if info["turnstile_present"]:
    print(f"Sitekey: {info['sitekey']}")
    print(f"Mode: {info['mode']}")
    print(f"Implementation: {info['implementation']}")

detect() çağrısı tek seferde şu alanları döndürür:

  • sitekey, mode ve implementation
  • script_loaded ve response_field bayrakları
  • action ve theme (varsa)

Turnstile modunu belirleme

Tespit ettiğiniz widget'ın hangi modda olduğu kullanıcı deneyimini etkiler. Üç mod vardır ve detect_mode() üçünü de tanır:

  • Managed (yönetilen): varsayılan mod; Cloudflare gerektiğinde etkileşimli bir adım gösterir.
  • Non-interactive: data-appearance="interaction-only"; kutu görünür ama tıklama gerektirmez.
  • Invisible (görünmez): data-size="invisible" veya size: 'invisible'; ekranda hiçbir şey görünmez.

Çözüm isteği açısından üç mod da aynı şekilde ele alınır — CaptchaAI için yalnızca sitekey ve sayfa URL'si gerekir.


Tespit sonrası: CaptchaAI ile çözme

sitekey elinizde olduğunda geri kalan akış üç adımdan oluşur:

  1. sitekey ve pageurl'yi method=turnstile ile in.php'ye gönderin.
  2. res.php'yi 5 saniyelik aralıklarla sorgulayın (Turnstile tipik olarak 10 saniyenin altında çözülür).
  3. Dönen token'ı cf-turnstile-response alanına yerleştirip formu gönderin.

data-action değeri varsa onu da istekle birlikte göndermeyi unutmayın; bazı siteler bunu sunucu tarafında doğrular:

import requests
import time

API_KEY = "YOUR_API_KEY"

def solve_detected_turnstile(detection_result):
    """Solve Turnstile using detection results."""
    if not detection_result["turnstile_present"]:
        raise ValueError("No Turnstile detected")

    if not detection_result["sitekey"]:
        raise ValueError("Sitekey not found — may need browser-based extraction")

    params = {
        "key": API_KEY,
        "method": "turnstile",
        "sitekey": detection_result["sitekey"],
        "pageurl": detection_result["url"],
        "json": 1,
    }

    # Include action if present
    if detection_result.get("action"):
        params["action"] = detection_result["action"]

    submit = requests.post("https://ocr.captchaai.com/in.php", data=params)
    task_id = submit.json()["request"]

    for _ in range(60):
        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("Turnstile solve timed out")


# Full workflow
detector = TurnstileDetector("https://example.com/signup")
info = detector.detect()

if info["turnstile_present"]:
    token = solve_detected_turnstile(info)
    print(f"Token: {token[:50]}...")

Sık karşılaşılan kenar durumları

Gerçek dünyada sitekey her zaman ilk HTML'de düzgünce durmaz. Aşağıda en sık karşılaşacağınız durumlar ve pratik çözümleri var:

Senaryo Zorluk Çözüm
sitekey harici bir JS dosyasında Sayfa HTML'sinde yok Bağlantılı JavaScript dosyalarını sitekey kalıpları için ayrıştırın
sitekey API yanıtından geliyor XHR çağrısından sonra yükleniyor Ağ isteklerini izleyip JSON yanıtlarındaki sitekey'i yakalayın
Aynı sayfada birden çok Turnstile Farklı sitekey'ler bir arada sitekey'i gönderdiğiniz form ile eşleştirin
Turnstile gölge DOM içinde Normal seçicilerle erişilemiyor Tarayıcı bağlamında shadowRoot.querySelector kullanın
Sunucu tarafında üretilen sitekey Şablon değişkenlerine gömülü Yapılandırma nesneleri için <script> etiketlerini tarayın
Turnstile kimlik doğrulama arkasında Herkese açık sayfada görünmüyor Önce oturum açın, ardından tespit edin

Sorun giderme

Tespit ya da çözüm beklediğiniz gibi çalışmıyorsa, belirtiyi aşağıdaki tabloyla eşleştirin — çoğu sorun bu beş kalıptan birine denk gelir:

Belirti Sebep Düzeltme
Script etiketi var ama sitekey bulunamıyor JS API, sitekey'i başka bir kaynaktan alıyor Bağlantılı tüm JS dosyalarını ve XHR yanıtlarını kontrol edin
Yanlış sitekey çıkarıldı Sayfada birden fazla CAPTCHA widget'ı var sitekey'leri çevredeki form öğeleriyle eşleştirin
Tespit çalışıyor ama çözüm başarısız oluyor Doğrulama için action parametresi gerekiyor data-action değerini çözüm isteğine ekleyin
Widget ilk HTML'de yok Kullanıcı etkileşiminden sonra dinamik yükleniyor Tam sayfayı oluşturmak için Selenium/Puppeteer kullanın
cf-turnstile-response alanı boş Widget henüz tamamlanmadı Widget'ın yüklenmesini bekleyin

Sık sorulan sorular

Turnstile'ı reCAPTCHA'dan nasıl ayırt ederim?

Üç işaret ikisini birbirinden net biçimde ayırır:

  • Sınıf: Turnstile cf-turnstile, reCAPTCHA g-recaptcha
  • Script kaynağı: Turnstile challenges.cloudflare.com/turnstile, reCAPTCHA www.google.com/recaptcha
  • Gizli alan: Turnstile cf-turnstile-response, reCAPTCHA g-recaptcha-response

Görünmez (invisible) Turnstile widget'ını nasıl tespit ederim?

Görünmez modda ekranda bir kutu görünmez ama işaretler yerinde durur: data-size="invisible" niteliği veya JS yapılandırmasında size: 'invisible'. TurnstileDetector.detect_mode() bu iki kalıbı da kontrol ederek modu invisible olarak döndürür.

sitekey'i koduma sabit yazabilir miyim?

Yazmamanız önerilir. Site operatörleri sitekey'i istedikleri zaman döndürebilir; sabit kodlanmış bir değer bir gün sessizce geçersiz olur. Her çalıştırmada sitekey'i sayfadan taze olarak çıkarın.

CaptchaAI Turnstile çözümü ne kadar sürer ve maliyeti nedir?

Turnstile tipik olarak 10 saniyenin altında, yüksek başarı oranıyla çözülür ve tespit yöntemi bu süreyi etkilemez — çözücü yalnızca sitekey ve sayfa URL'sine bakar. Fiyatlandırma thread tabanlıdır: BASIC ($15/ay, 5 thread) ile başlayıp iş yükünüze göre yukarı çıkabilirsiniz.

sitekey'i hiç bulamazsam ne yapmalıyım?

sitekey harici bir JS dosyasında, bir API yanıtında olabilir ya da çalışma anında üretiliyor olabilir. Tarayıcı DevTools'un Ağ sekmesiyle isteği izleyin veya sayfa tamamen oluştuktan sonra değeri okumak için Selenium/Puppeteer kullanın.


Özet

Cloudflare Turnstile'ı tespit etmek dört ipucunu kontrol etmeye dayanır:

  • Turnstile script etiketi (challenges.cloudflare.com/turnstile)
  • cf-turnstile konteyneri
  • data-sitekey niteliği
  • turnstile.render() çağrıları

Basit entegrasyonlarda statik HTML ayrıştırması yeterlidir; dinamik yüklenen widget'lar için tam sayfayı Selenium veya Puppeteer ile oluşturun. sitekey elinizdeyken CaptchaAI'nin Turnstile çözücüsü tüm modları aynı şekilde işler ve token'ı tipik olarak 10 saniyenin altında, yüksek başarı oranıyla döndürür.

İlgili Makaleler

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