Penjelasan Mendalam

Panduan Deteksi Implementasi Cloudflare Turnstile

Sebelum Anda bisa solve Cloudflare Turnstile, Anda perlu mendeteksinya di halaman dan mengekstrak sitekey. Turnstile dapat tertanam melalui atribut HTML, panggilan JavaScript API, atau dimuat secara dinamis setelah rendering halaman. Panduan ini mencakup setiap metode deteksi — dari parsing HTML sederhana hingga analisis JavaScript runtime.


Metode Implementasi Turnstile

Situs menyematkan Turnstile dengan tiga cara, masing-masing memerlukan pendekatan deteksi berbeda:

Metode Cara Kerjanya Kesulitan Deteksi
HTML implisit <div class="cf-turnstile" data-sitekey="..."> di source halaman Mudah (HTML statis)
JavaScript eksplisit turnstile.render() dipanggil dalam skrip Sedang (parse JS)
Pemuatan dinamis Widget dimuat setelah aksi pengguna atau XHR Sulit (butuh eksekusi JS)

Metode 1: Deteksi HTML Statis

Integrasi Turnstile paling sederhana menggunakan kelas cf-turnstile dan atribut data-sitekey:

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']}")

Metode 2: Deteksi JavaScript API

Beberapa situs menggunakan turnstile.render() sebagai pengganti atribut HTML:

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}

Metode 3: Deteksi Pemuatan Dinamis (Selenium/Puppeteer)

Saat Turnstile dimuat secara dinamis setelah interaksi halaman:

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()
    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: [],
  });

  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);

Kelas Deteksi Komprehensif

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']}")

Solve Setelah Deteksi

Setelah terdeteksi, solve dengan CaptchaAI:

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]}...")

Kasus Edge

Skenario Tantangan Solusi
Sitekey dalam file JS eksternal Tidak di HTML halaman Parse file JavaScript tertaut untuk pola sitekey
Sitekey dari respons API Dimuat setelah XHR call Monitor network request untuk sitekey dalam respons JSON
Beberapa widget Turnstile Sitekey berbeda di halaman yang sama Cocokkan sitekey dengan form spesifik yang disubmit
Turnstile di shadow DOM Tidak dapat diakses via selector reguler Gunakan shadowRoot.querySelector dalam konteks browser
Sitekey dirender sisi server Tertanam dalam variabel template Periksa tag <script> untuk objek konfigurasi
Turnstile di belakang autentikasi Tidak terlihat di halaman publik Autentikasi dulu, lalu deteksi

Pemecahan Masalah

Gejala Penyebab Solusi
Tag skrip ditemukan tapi tidak ada sitekey JS API dirender dengan konfigurasi dari sumber lain Periksa semua file JS tertaut dan respons XHR
Sitekey salah diekstraksi Beberapa widget CAPTCHA di halaman Cocokkan sitekey dengan elemen form di sekitarnya
Deteksi berhasil tapi solve gagal Parameter action diperlukan untuk validasi Sertakan nilai data-action dalam request solve
Widget tidak ada di HTML awal Pemuatan dinamis setelah interaksi pengguna Gunakan Selenium/Puppeteer untuk rendering halaman penuh
Field cf-turnstile-response kosong Widget belum selesai dimuat Tunggu widget selesai dimuat

Pertanyaan Umum

Bisakah sitekey Turnstile berubah?

Ya. Operator situs dapat merotasi sitekey kapan saja. Selalu ekstrak sitekey baru dari halaman daripada hardcoding.

Apakah saya perlu parameter action?

Hanya jika situs memvalidasinya di sisi server. Jika data-action ada di HTML, sertakan dalam request solve untuk hasil terbaik.

Bagaimana jika sitekey tidak ditemukan?

Sitekey mungkin ada dalam file JavaScript eksternal, respons API, atau dibuat secara dinamis. Gunakan browser DevTools (tab Network) untuk menemukannya, atau gunakan Selenium/Puppeteer untuk mengekstrak setelah rendering halaman penuh.

Apakah metode deteksi mempengaruhi solving?

Tidak. CaptchaAI Turnstile Solver bekerja sama terlepas dari cara widget diimplementasikan. Anda hanya perlu sitekey dan URL halaman.


Ringkasan

Mendeteksi Cloudflare Turnstile memerlukan pemeriksaan tag skrip Turnstile, container cf-turnstile, atribut data-sitekey, dan panggilan turnstile.render(). Gunakan parsing HTML statis untuk integrasi sederhana dan Selenium/Puppeteer untuk widget yang dimuat secara dinamis. Setelah terdeteksi, solve dengan CaptchaAI Turnstile Solver menggunakan sitekey yang diekstraksi — semua mode ditangani secara identik dengan tingkat keberhasilan 100%.

Artikel Terkait

  • Cloudflare Challenge vs Turnstile: Cara Mendeteksi
  • Cloudflare Turnstile 403 Setelah Submit Token — Perbaikan
  • Ekstraksi Sitekey Cloudflare Turnstile
Komentar dinonaktifkan untuk artikel ini.