الشروحات المعمقة

كيفية اكتشاف Cloudflare Turnstile واستخراج مفتاح الموقع

لحلّ أي تحدي Cloudflare Turnstile تحتاج شيئين فقط: مفتاح الموقع وعنوان URL للصفحة. الجزء الصعب هو الأول، فالمواقع تضمّن الأداة بطرق مختلفة — في HTML الظاهر، أو في استدعاء JavaScript، أو بعد تفاعل المستخدم. يوضّح هذا الدليل كيف تلتقط المفتاح بشكل موثوق مهما كانت طريقة التضمين، ثم تُمرّره إلى CaptchaAI لرمز صالح.


طرق تضمين Turnstile الثلاث

تضع المواقع أداة Turnstile بثلاث طرق، لكل منها صعوبة كشف مختلفة:

الطريقة كيف تعمل صعوبة الكشف
HTML مباشر <div class="cf-turnstile" data-sitekey="..."> في مصدر الصفحة سهلة
JavaScript صريحة يُستدعى turnstile.render() داخل سكربت متوسطة
تحميل ديناميكي تُحمّل الأداة بعد إجراء المستخدم أو XHR صعبة

القاعدة: ابدأ بالأبسط. اجلب HTML أولاً؛ إن وجدت cf-turnstile أو data-sitekey فقد انتهيت، وإلا فالمفتاح مخفي في JavaScript ويلزمك الطريقة الثانية أو الثالثة.


الطريقة الأولى: الكشف من HTML الثابت

أبسط تكامل يكتب cf-turnstile وسمة data-sitekey في الصفحة مباشرةً. طلب GET وتعبير نمطي يكفيان لاستخراج المفتاح والوضع والسمة والإجراء:

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://example.com/login")
if info["turnstile_found"]:
    print(f"Sitekey: {info['sitekey']}")
    print(f"Mode: {info['mode']}")

تلتقط الدالة الوضع أيضاً — invisible أو non-interactive أو managed — فتعرف إن كانت الأداة تتطلب تفاعلاً مرئياً.


الطريقة الثانية: الكشف من واجهة JavaScript

بعض المواقع تستدعي turnstile.render() وتمرّر الإعدادات في كائن بدل سمات 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}

تستخرج الدالة أيضاً رد النداء والإجراء والمظهر. مرّر قيمة الإجراء في طلب الحل إن وُجدت، ليطابق ما يتحقق منه الخادم.


الطريقة الثالثة: الكشف عن التحميل الديناميكي (Selenium/Puppeteer)

حين تُحمَّل الأداة بعد تفاعل أو طلب شبكة، لا يكفي طلب GET — تحتاج إلى متصفح حقيقي يُشغّل JavaScript ثم يفحص الـ DOM بعد العرض.

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("--disable-blink-features=AutomationControlled")
    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: ["--disable-blink-features=AutomationControlled"],
  });

  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://example.com/login").then(console.log);

تراقب النسختان ثلاث إشارات: إطار iframe من challenges.cloudflare.com، وحاوية cf-turnstile، وحقل cf-turnstile-response. وجود أيٍّ منها يؤكد أن الأداة نشطة.


فئة كشف موحّدة تجمع الطرق الثلاث

غلّف الطرق الثلاث في فئة واحدة تمرّر لها URL فتُعيد المفتاح والوضع ونوع التضمين — الأنسب لمعالجة مواقع كثيرة دفعة واحدة:

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://example.com/login")
info = detector.detect()

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

تغطّي قائمة SITEKEY_PATTERNS أربع صيغ لكتابة المفتاح، منها TURNSTILE_SITE_KEY، فيصمد الكشف حتى لو تغيّرت تسمية المتغير.


من الكشف إلى الحل عبر CaptchaAI

بعد الحصول على المفتاح، أرسله إلى CaptchaAI مع عنوان الصفحة عبر method=turnstile، ثم استطلع النتيجة حتى يعود الرمز:

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

سيناريو عملي من المنطقة: فريق ضمان جودة في متجر خليجي يستعدّ للجمعة البيضاء، وصفحات الدخول وإتمام الشراء محمية بـ Turnstile، ومفاتيح بيئة الاختبار تدور أسبوعياً. بدل ترميز مفتاح ثابت ينكسر مع أول تدوير، يشغّل الفريق TurnstileDetector عند كل تشغيل ليلتقط المفتاح الحالي ويمرّره إلى CaptchaAI، فيواصل اختبار الشراء دون توقف.

يدعم CaptchaAI حلّ Cloudflare Turnstile رسمياً (GA)، والتسعير قائم على عدد الـ Threads المتزامنة مع حلول غير محدودة لكل thread، بدءاً من باقة BASIC بسعر ‎$15‎ شهرياً و5 threads.


حالات خاصة يجب الانتباه لها

لا تسير كل الصفحات وفق المثال. يلخّص الجدول الحالات التي قد تُربك الكشف وطريقة معالجتها:

السيناريو التحدي المعالجة
المفتاح في ملف JS خارجي غير موجود في HTML حلّل ملفات JavaScript المرتبطة
المفتاح من استجابة API يُحمَّل بعد XHR راقب طلبات الشبكة وردود JSON
أدوات Turnstile متعددة مفاتيح مختلفة طابِق كل مفتاح بنموذجه
Shadow DOM يتعذّر الوصول العادي استخدم shadowRoot.querySelector
مفتاح مُولَّد من الخادم داخل متغيرات القالب افحص وسوم <script>
خلف تسجيل الدخول غير مرئية للعامة سجّل الدخول ثم اكشف

استكشاف الأخطاء وإصلاحها

عندما ينجح الكشف نظرياً لكن يفشل عملياً، ابدأ من هذا الجدول قبل السجلات:

العَرَض السبب المحتمل الحل
سكربت بلا مفتاح تكوين JS خارجي افحص ملفات JS وردود XHR
مفتاح خاطئ أدوات CAPTCHA متعددة طابِق كل مفتاح بنموذجه
الحل يفشل رغم الكشف الإجراء مطلوب للتحقق ضمّن data-action
غائبة عن HTML الأولي تحميل ديناميكي استخدم Selenium/Puppeteer
حقل cf-turnstile-response فارغ لم تكتمل الأداة انتظر اكتمالها

الأسئلة المتداولة

كيف أميّز Turnstile عن reCAPTCHA أو hCaptcha على الصفحة نفسها؟

لكل نوع بصمته: يستخدم Turnstile الفئة cf-turnstile وحقل cf-turnstile-response ويحمّل من challenges.cloudflare.com، بينما يظهر reCAPTCHA بحقل g-recaptcha-response. يحلّ CaptchaAI كلاً من Turnstile وreCAPTCHA رسمياً، لكنه لا يدعم hCaptcha حالياً.

هل يتغيّر وضع الكشف باختلاف invisible وmanaged؟

الفحص نفسه: تبحث عن cf-turnstile والسكربت وحقل الاستجابة. لكن invisible قد لا يعرض إطاراً مرئياً، فاعتمد على السكربت وحقل cf-turnstile-response لا على عنصر مرئي.

هل يؤثر أسلوب التضمين في CaptchaAI على نتيجة الحل؟

لا. سواء كُتب المفتاح في HTML أو عبر turnstile.render() أو حُمِّل ديناميكياً، يتعامل CaptchaAI معه بالطريقة نفسها. يلزمه المفتاح الصحيح وعنوان الصفحة، مع الإجراء إن تحقّق منه الموقع.


الخلاصة

يتلخّص اكتشاف Cloudflare Turnstile في البحث عن أربع إشارات: السكربت، وحاوية cf-turnstile، وسمة data-sitekey، واستدعاءات turnstile.render(). استخدم HTML الثابت للتكامل البسيط، وSelenium أو Puppeteer للتحميل الديناميكي. متى استخرجت المفتاح، مرّره إلى حل Turnstile من CaptchaAI — تُعالَج كل الأوضاع بالطريقة نفسها وبمعدل نجاح مرتفع.

مقالات ذات صلة

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.