الدروس التطبيقية

كيفية اكتشاف اختبارات CAPTCHA المُحمّلة ديناميكيًا والتعامل معها

سكربت الأتمتة يقرأ مصدر الصفحة، لا يجد أي اختبار CAPTCHA، فيمضي في عمله — ثم يُرفض الطلب. السبب في معظم الحالات ليس خطأً في الكود، بل أن الموقع لا يُحمّل الكابتشا إلا بعد تفاعل حقيقي: نقرة زر، أو تمرير حتى قسم معيّن، أو تركيز على حقل. عنصر CAPTCHA ببساطة غير موجود في الـ DOM لحظة التقاطك للصفحة.

الحل ليس قراءة المصدر بشكل أسرع، بل انتظار ظهور الكابتشا فعليًا ثم استخراج مفتاح الموقع (sitekey) منها. في هذا الدليل نغطّي ثلاث طرق عملية لاكتشاف اختبارات CAPTCHA المُحمّلة ديناميكيًا في Puppeteer وSelenium، ثم نربطها بتدفق حلّ كامل عبر CaptchaAI.


متى تظهر الكابتشا؟ محفّزات التحميل الديناميكي الشائعة

قبل أن تكتشف الكابتشا، عليك أن تعرف ما الذي يُظهرها. الجدول التالي يلخّص أكثر المحفّزات شيوعًا وكيفية تشغيل كلٍّ منها من داخل الأتمتة:

المحفّز مثال كيفية التنشيط
نقرة زر زر "إرسال" يضيف reCAPTCHA إلى النموذج انقر الزر أولًا
تركيز الحقل تظهر الكابتشا عند التركيز على الإدخال ركّز على حقل البريد الإلكتروني أو كلمة المرور
موضع التمرير تُحمّل الكابتشا عندما يصبح القسم مرئيًا مرّر حتى النموذج
مؤقّت زمني تُحمّل الكابتشا بعد 3 ثوانٍ انتظر التأخير
شرط JavaScript تُحمّل الكابتشا بعد استجابة AJAX شغّل الطلب المُسبق

سيناريو واقعي: في بوابات الحجز والدفع الإلكتروني الشائعة في المنطقة — كمنصات حجز تذاكر الطيران أو بوابات الخدمات الحكومية — كثيرًا ما لا يظهر اختبار reCAPTCHA إلا بعد اختيار التاريخ أو الضغط على "متابعة". أي سكربت اختبار جودة (QA) يقرأ الصفحة قبل هذه الخطوة سيبلّغ خطأً بأن "لا كابتشا هنا"، بينما المشكلة الحقيقية أنه لم ينتظر المُحفّز.


الطريقة الأولى: مراقبة الـ DOM عبر MutationObserver

الفكرة أن تُراقب شجرة الـ DOM وتنتظر لحظة إضافة عنصر الكابتشا بدلًا من افتراض وجوده مسبقًا. MutationObserver مثالي لهذا: يُطلق دالة كلّما أُضيف عنصر جديد، فتفحص كلّ عنصر بحثًا عن أصناف الكابتشا المعروفة.

Puppeteer

const puppeteer = require('puppeteer');

const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com/login');

// Set up MutationObserver before triggering the CAPTCHA
const captchaInfo = await page.evaluate(() => {
  return new Promise((resolve) => {
    // Check if already present
    const existing = document.querySelector('.g-recaptcha, .cf-turnstile, .h-captcha');
    if (existing) {
      resolve({
        type: existing.className,
        sitekey: existing.getAttribute('data-sitekey'),
      });
      return;
    }

    // Watch for new elements
    const observer = new MutationObserver((mutations) => {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          if (node.nodeType !== 1) continue;

          const captcha = node.matches?.('.g-recaptcha, .cf-turnstile, .h-captcha')
            ? node
            : node.querySelector?.('.g-recaptcha, .cf-turnstile, .h-captcha');

          if (captcha) {
            observer.disconnect();
            resolve({
              type: captcha.className,
              sitekey: captcha.getAttribute('data-sitekey'),
            });
            return;
          }
        }
      }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    // Timeout after 30 seconds
    setTimeout(() => {
      observer.disconnect();
      resolve(null);
    }, 30000);
  });
});

console.log('Detected CAPTCHA:', captchaInfo);

لاحظ أن الكود يتحقق أولًا من وجود العنصر مسبقًا قبل تشغيل المراقب — خطوة تمنع تفويت كابتشا كانت حاضرة من البداية.

تشغيل المُحفّز يدويًا

بعد تجهيز المراقب، شغّل التفاعل الذي يستدعي الكابتشا. اختر السطر المناسب لموقعك:

// Click the submit button to trigger CAPTCHA
await page.click('#submit-btn');

// Or focus the input
await page.focus('#email');

// Or scroll to the form
await page.evaluate(() => {
  document.querySelector('#signup-form').scrollIntoView();
});

الطريقة الثانية: انتظار تحميل مكتبة JavaScript

كل نوع كابتشا يحتاج مكتبته الخاصة قبل أن يعمل. بدل مراقبة العناصر، انتظر تعريف الكائن العام للمكتبة في window — فهو مؤشّر أوثق على أن الكابتشا جاهزة للحل لا مجرد موجودة في الصفحة:

// Wait for reCAPTCHA script to load
await page.waitForFunction(() => {
  return typeof window.grecaptcha !== 'undefined' 
    && typeof window.grecaptcha.render === 'function';
}, { timeout: 30000 });

// Now extract parameters
const sitekey = await page.evaluate(() => {
  const el = document.querySelector('.g-recaptcha');
  return el?.getAttribute('data-sitekey');
});

مع Cloudflare Turnstile

المنطق نفسه ينطبق على Turnstile، لكن الكائن العام هنا هو window.turnstile:

await page.waitForFunction(() => {
  return typeof window.turnstile !== 'undefined';
}, { timeout: 30000 });

const sitekey = await page.evaluate(() => {
  const el = document.querySelector('.cf-turnstile');
  return el?.getAttribute('data-sitekey');
});

الطريقة الثالثة: اعتراض استدعاء دالة العرض (render)

الطريقتان السابقتان تنتظران نتيجة؛ هذه الطريقة تلتقط الكابتشا في لحظة إنشائها. نُدخل شيفرة قبل تشغيل سكربتات الصفحة ترتبط بمكتبة الكابتشا وتعترض استدعاء render، فنحصل على مفتاح الموقع ودالة رد النداء (callback) والحاوية مباشرةً دون البحث في الـ DOM:

// Inject before page scripts run
await page.evaluateOnNewDocument(() => {
  window.__captchaDetected = null;

  // Hook grecaptcha.render
  let _grecaptcha;
  Object.defineProperty(window, 'grecaptcha', {
    set(val) {
      _grecaptcha = val;
      const origRender = val.render;
      val.render = function(container, params) {
        window.__captchaDetected = {
          type: 'recaptcha',
          sitekey: params.sitekey,
          callback: params.callback?.name,
          container: typeof container === 'string' ? container : container.id,
        };
        return origRender.apply(this, arguments);
      };
    },
    get() { return _grecaptcha; },
  });
});

await page.goto('https://example.com/signup');

// Trigger the CAPTCHA (click, scroll, etc.)
await page.click('#show-form');

// Wait for detection
await page.waitForFunction(() => window.__captchaDetected !== null, {
  timeout: 30000,
});

const detected = await page.evaluate(() => window.__captchaDetected);
console.log('Detected:', detected);
// { type: 'recaptcha', sitekey: '6Le-wvkS...', callback: 'onCaptcha', container: 'recaptcha-box' }

هذه الطريقة الأدق لأنها تلتقط اسم دالة رد النداء أيضًا — وهو ما ستحتاجه لاحقًا لتشغيل الكابتشا بعد حقن الرمز.


Python وSelenium: انتظار الكابتشا المُحمّلة بتكاسل

إن كان مسار عملك يعتمد على Python، فإن WebDriverWait مع expected_conditions يمنحك الانتظار الصريح نفسه: شغّل المُحفّز، ثم انتظر ظهور أي عنصر كابتشا معروف، وحدّد نوعه من صنف العنصر:

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

driver = webdriver.Chrome()
driver.get("https://example.com/login")

# Trigger the CAPTCHA loading
submit = driver.find_element(By.ID, "submit-btn")
submit.click()

# Wait for CAPTCHA to appear
try:
    captcha_el = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((
            By.CSS_SELECTOR,
            ".g-recaptcha, .cf-turnstile, .h-captcha"
        ))
    )
    sitekey = captcha_el.get_attribute("data-sitekey")
    captcha_class = captcha_el.get_attribute("class")

    if "g-recaptcha" in captcha_class:
        captcha_type = "recaptcha"
    elif "cf-turnstile" in captcha_class:
        captcha_type = "turnstile"
    else:
        captcha_type = "hcaptcha"

    print(f"Type: {captcha_type}, Sitekey: {sitekey}")
except Exception:
    print("No CAPTCHA appeared within 30 seconds")

انتظار إطار iframe الخاص بـ reCAPTCHA

أحيانًا يظهر عنصر div بينما لا يزال السكربت قيد التحميل، فيعود مفتاح الموقع فارغًا. انتظار إطار iframe يضمن أن الكابتشا اكتمل عرضها فعلًا:

# reCAPTCHA loads an iframe even when the div exists but the script is still loading
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((
        By.CSS_SELECTOR,
        "iframe[src*='recaptcha'], iframe[src*='challenges.cloudflare.com']"
    ))
)
print("CAPTCHA iframe loaded")

التدفق الكامل: الاكتشاف ثم الحل عبر CaptchaAI

بعد اكتشاف الكابتشا واستخراج مفتاح الموقع، تصبح الخطوة التالية إرسالها إلى CaptchaAI عبر in.php، ثم استطلاع النتيجة دوريًا من res.php، وأخيرًا حقن الرمز في الحقل المناسب. الدالة التالية تجمع الخطوات الخمس في تدفق واحد قابل لإعادة الاستخدام:

import requests
import time

def detect_and_solve(driver, api_key, trigger_action=None):
    """Detect a lazy-loaded CAPTCHA, solve it, and inject the token."""

    # 1. Trigger the CAPTCHA
    if trigger_action:
        trigger_action(driver)

    # 2. Wait for it to appear
    captcha_el = WebDriverWait(driver, 30).until(
        EC.presence_of_element_located((
            By.CSS_SELECTOR,
            ".g-recaptcha, .cf-turnstile, .h-captcha"
        ))
    )

    sitekey = captcha_el.get_attribute("data-sitekey")
    page_url = driver.current_url
    captcha_class = captcha_el.get_attribute("class")

    # 3. Determine type and method
    if "g-recaptcha" in captcha_class:
        method, key_param, token_field = "userrecaptcha", "googlekey", "g-recaptcha-response"
    elif "cf-turnstile" in captcha_class:
        method, key_param, token_field = "turnstile", "sitekey", "cf-turnstile-response"
    else:
        method, key_param, token_field = "hcaptcha", "sitekey", "h-captcha-response"

    # 4. Solve with CaptchaAI
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key, "method": method,
        key_param: sitekey, "pageurl": page_url, "json": "1",
    }).json()

    task_id = resp["request"]
    for _ in range(24):
        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["status"] == 1:
            token = result["request"]
            break

    # 5. Inject
    driver.execute_script(f"""
        const el = document.querySelector('textarea[name="{token_field}"], input[name="{token_field}"]');
        if (el) el.value = arguments[0];
    """, token)

    return token

يختار الكود اسم الدالة (method) ومعامل المفتاح وحقل الرمز تلقائيًا حسب نوع الكابتشا المكتشَف — ما يجعله صالحًا لأنواع reCAPTCHA وCloudflare Turnstile المدعومة رسميًا في CaptchaAI دون تعديل يدوي.


استكشاف الأخطاء الشائعة وحلّها

المشكلة السبب المحتمل الحل
الكابتشا لا تظهر إطلاقًا محفّز خاطئ افحص الصفحة لتحديد ما الذي يُشغّل الكابتشا فعلًا
مفتاح الموقع فارغ العنصر موجود لكن السكربت لم يُشغَّل بعد انتظر إطار iframe لا مجرد الـ div
المراقب يفوّت الكابتشا كانت موجودة مسبقًا تحقق من العناصر الحاضرة قبل إعداد المراقب
انتهاء المهلة الكابتشا تُحمّل للمستخدمين الحقيقيين فقط استخدم متصفحًا كاملًا ببصمة واقعية

الأسئلة الشائعة

لماذا يعود مفتاح الموقع (sitekey) فارغًا رغم ظهور عنصر الكابتشا؟

غالبًا لأن عنصر الـ div أُضيف إلى الصفحة قبل أن تكتمل مكتبة الكابتشا. انتظر ظهور إطار iframe أو تعريف الكائن العام (window.grecaptcha أو window.turnstile) بدلًا من الاكتفاء بوجود العنصر، وستحصل على مفتاح صالح.

أيهما أنسب: MutationObserver أم waitForFunction؟

استخدم MutationObserver عندما لا تعرف مسبقًا صنف العنصر أو موضعه الدقيق، فهو يرصد أي إضافة إلى الـ DOM. واستخدم waitForFunction عندما تعرف نوع الكابتشا وتريد التأكد أن مكتبتها جاهزة للحل لا مجرد حاضرة في الصفحة.

كيف أتعامل مع كابتشا لا تظهر إلا بعد استجابة AJAX؟

شغّل الطلب المُسبق الذي تعتمد عليه الكابتشا (مثل اختيار قيمة تُطلق نداء خلفيًا)، ثم انتظر عبر MutationObserver أو الانتظار الصريح. المهلة الافتراضية 30 ثانية في الأمثلة تكفي لأغلب استجابات الشبكة، وارفعها إن كانت البوابة بطيئة.

هل يمكن اكتشاف نوع الكابتشا تلقائيًا قبل إرسالها إلى CaptchaAI؟

نعم. صنف العنصر (g-recaptcha أو cf-turnstile) يكشف النوع، ومنه تختار اسم الدالة (method) الصحيح كما في دالة detect_and_solve أعلاه، فترسل كل نوع بمعاملاته الصحيحة دون تدخّل يدوي.


حلّ أي اختبار CAPTCHA باستخدام CaptchaAI

أنشئ حسابك واحصل على مفتاح API من captchaai.com، ثم مرّر مفتاح الموقع المكتشَف إلى تدفق الحل مباشرةً.


أدلة ذات صلة

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