المقارنات

شرح reCAPTCHA v2 مقابل reCAPTCHA غير المرئية

ما يكسر الأتمتة بين نسختَي reCAPTCHA v2 ليس الحل بل تسليم الرمز؛ المحرّك واحد في الحالتين. في نسخة مربع الاختيار ينقر الزائر على "أنا لست روبوت"، فيسجّل المتصفح رد النداء نيابةً عنك ويكفي غالباً أن تضع الرمز في الحقل المخفي. أما في النسخة غير المرئية فلا توجد نقرة أصلاً، ويبقى الحقل المخفي معبّأً بلا من يستدعي الدالة التي تُكمل إرسال النموذج.

على مستوى الطلب إلى CaptchaAI الفرق سطر واحد: invisible=1. أما داخل الصفحة فالفرق جوهري، وهو سبب معظم الحالات التي يعود فيها النموذج إلى نقطة البداية رغم وصول الرمز سليماً.

ثلاث علامات تحسم أي نسخة أمامك

  • وجود data-size="invisible" داخل عنصر .g-recaptcha — النسخة غير المرئية.
  • مربع اختيار ظاهر داخل النموذج — النسخة التقليدية.
  • استدعاء grecaptcha.execute() في سكربت الصفحة — تشغيل برمجي بلا widget ظاهر.

شكل كل نسخة داخل شيفرة HTML

النسخة الظاهرة: مربع الاختيار

<!-- Standard checkbox widget -->
<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
     data-callback="onSubmit">
</div>

<!-- Widget renders as: -->
<!-- [✓] I'm not a robot     reCAPTCHA logo -->

الزائر يرى المربع وينقره، وإذا شكّ Google في السلوك ظهرت شبكة الصور أسفل المربع.

النسخة غير المرئية: ثلاثة أنماط تركيب شائعة

<!-- Pattern 1: Invisible widget on a button -->
<button class="g-recaptcha"
        data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
        data-callback="onSubmit"
        data-size="invisible">
  Submit
</button>

<!-- Pattern 2: Invisible div (programmatic trigger) -->
<div class="g-recaptcha"
     data-sitekey="6Le-wvkSAAAAAPBMRTvw..."
     data-size="invisible"
     data-callback="onSubmit">
</div>

<!-- Pattern 3: Programmatic render -->
<script>
  grecaptcha.render('submit-btn', {
    sitekey: '6Le-wvkSAAAAAPBMRTvw...',
    callback: onSubmit,
    size: 'invisible'
  });
</script>

لا شيء يظهر للزائر هنا. يبدأ التحقق عند تفاعل العنصر المرتبط بالـ widget أو عند استدعاء grecaptcha.execute() صراحةً، ولهذا يجب أن تعرف اسم دالة رد النداء قبل أن تكتب أول سطر أتمتة. ومع الشك تظهر شبكة الصور نافذةً منبثقة في زاوية الصفحة.

اكتشف نسخة reCAPTCHA برمجياً قبل إرسال أي طلب

الشكل البصري لا يكفي؛ اقرأ HTML واحسم النسخة من الوسوم. الدوال التالية ترجع مفتاح الموقع واسم رد النداء معاً.

فحص باستخدام Python

import requests
from bs4 import BeautifulSoup
import re

def detect_recaptcha_variant(url):
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, "html.parser")

    # Check for invisible indicators
    invisible_widget = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
    if invisible_widget:
        return {
            "variant": "invisible",
            "sitekey": invisible_widget.get("data-sitekey"),
            "callback": invisible_widget.get("data-callback")
        }

    # Check for programmatic invisible in scripts
    for script in soup.find_all("script"):
        if script.string and "invisible" in str(script.string):
            key_match = re.search(r"sitekey['\"]?\s*[:=]\s*['\"]([^'\"]+)", script.string)
            if key_match:
                return {
                    "variant": "invisible-programmatic",
                    "sitekey": key_match.group(1),
                    "callback": "check grecaptcha.render() call"
                }

    # Check for standard checkbox
    checkbox_widget = soup.find(class_="g-recaptcha")
    if checkbox_widget:
        return {
            "variant": "checkbox",
            "sitekey": checkbox_widget.get("data-sitekey"),
            "callback": checkbox_widget.get("data-callback")
        }

    return None

result = detect_recaptcha_variant("https://example.com/login")
print(result)

فحص باستخدام Node.js

const axios = require("axios");
const cheerio = require("cheerio");

async function detectRecaptchaVariant(url) {
  const { data } = await axios.get(url);
  const $ = cheerio.load(data);

  // Check for invisible
  const invisible = $(".g-recaptcha[data-size='invisible']");
  if (invisible.length) {
    return {
      variant: "invisible",
      sitekey: invisible.attr("data-sitekey"),
      callback: invisible.attr("data-callback"),
    };
  }

  // Check scripts for programmatic invisible
  const scripts = $("script")
    .map((_, el) => $(el).html())
    .get()
    .join("\n");
  if (scripts.includes("invisible")) {
    const keyMatch = scripts.match(/sitekey['"]?\s*[:=]\s*['"]([^'"]+)/);
    if (keyMatch) {
      return {
        variant: "invisible-programmatic",
        sitekey: keyMatch[1],
        callback: "check render call",
      };
    }
  }

  // Check for standard checkbox
  const checkbox = $(".g-recaptcha");
  if (checkbox.length) {
    return {
      variant: "checkbox",
      sitekey: checkbox.attr("data-sitekey"),
      callback: checkbox.attr("data-callback"),
    };
  }

  return null;
}

فحص خاطف من وحدة تحكم المتصفح

const el = document.querySelector('[data-size="invisible"]');
console.log(el ? "Invisible reCAPTCHA" : "Checkbox reCAPTCHA");

النتيجة كائن واحد يقود بقية المسار بلا افتراضات.

جدول الفروق بين reCAPTCHA v2 والنسخة غير المرئية

بعد حسم النسخة، هذا ما يتغيّر فعلياً:

العنصر مربع الاختيار غير المرئية
widget ظاهر نعم — مربع "أنا لست روبوت" لا — مخفي تماماً عن الزائر
ما الذي يشغّل التحقق نقرة الزائر على المربع نقرة زر أو إرسال نموذج أو تحميل الصفحة
أثر تجربة الاستخدام احتكاك متوسط احتكاك شبه معدوم
حقل الرمز g-recaptcha-response g-recaptcha-response
التركيب في الصفحة عنصر g-recaptcha داخل النموذج data-callback على الزر أو grecaptcha.execute()
دالة رد النداء اختيارية شبه إلزامية
الطريقة في CaptchaAI method=userrecaptcha method=userrecaptcha + invisible=1
صلاحية الرمز 120 ثانية 120 ثانية
سقف وقت الحل < 60 ثانية < 30 ثانية

إرسال المهمة إلى CaptchaAI

المسار واحد في الحالتين: أرسل إلى in.php، احفظ معرّف المهمة، ثم استطلع res.php حتى تصل استجابة تبدأ بـ OK|.

طلب نسخة مربع الاختيار

import requests
import time

resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": "6Le-wvkSAAAA...",
    "pageurl": "https://example.com/form"
})
task_id = resp.text.split("|")[1]

for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY", "action": "get", "id": task_id
    })
    if result.text.startswith("OK|"):
        token = result.text.split("|")[1]
        break

طلب النسخة غير المرئية

import requests
import time

resp = requests.get("https://ocr.captchaai.com/in.php", params={
    "key": "YOUR_API_KEY",
    "method": "userrecaptcha",
    "googlekey": "6Le-wvkSAAAA...",
    "pageurl": "https://example.com/form",
    "invisible": 1  # Only parameter difference
})
task_id = resp.text.split("|")[1]

for _ in range(60):
    time.sleep(5)
    result = requests.get("https://ocr.captchaai.com/res.php", params={
        "key": "YOUR_API_KEY", "action": "get", "id": task_id
    })
    if result.text.startswith("OK|"):
        token = result.text.split("|")[1]
        break

تسليم الرمز إلى الصفحة: هنا يقع الفرق الحقيقي

المعامل الإضافي كل ما يتغيّر في الطلب؛ ما بعد وصول الرمز يختلف تماماً.

مربع الاختيار: الحقل المخفي يكفي غالباً

# Selenium — inject into hidden field
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# If the page uses a callback, also call it
callback = driver.find_element("css selector", ".g-recaptcha").get_attribute("data-callback")
if callback:
    driver.execute_script(f'{callback}("{token}");')

غير المرئية: استدعاء رد النداء شبه إلزامي

# Selenium — inject AND call the callback
driver.execute_script(
    f'document.getElementById("g-recaptcha-response").value = "{token}";'
)

# CRITICAL: Invisible reCAPTCHA almost always requires calling the callback
callback_name = driver.find_element(
    "css selector", ".g-recaptcha[data-size='invisible']"
).get_attribute("data-callback")

driver.execute_script(f'{callback_name}("{token}");')
// Puppeteer — invisible callback injection
await page.evaluate((tok) => {
  // Set the hidden field
  document.getElementById("g-recaptcha-response").value = tok;

  // Find and call the callback function
  const widget = document.querySelector("[data-size='invisible']");
  const cbName = widget?.getAttribute("data-callback");
  if (cbName && typeof window[cbName] === "function") {
    window[cbName](tok);
  }
}, token);

الخلاصة: مربع الاختيار يكتفي غالباً بالحقل المخفي لأن نقرة الزائر سجّلت رد النداء. النسخة غير المرئية بلا نقرة، فإن لم تستدعِ الدالة بقي النموذج غير مُتحقَّق منه.

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

اجمع الاكتشاف والإرسال والاستطلاع في صنف واحد يقرر بنفسه هل يضيف invisible=1، ويعيد الرمز واسم رد النداء معاً:

import requests
import time
from bs4 import BeautifulSoup

class RecaptchaV2UniversalSolver:
    def __init__(self, api_key):
        self.api_key = api_key

    def detect_and_solve(self, page_url, page_html=None):
        if not page_html:
            page_html = requests.get(page_url).text

        soup = BeautifulSoup(page_html, "html.parser")

        # Detect variant
        invisible = soup.find(attrs={"data-size": "invisible", "class": "g-recaptcha"})
        widget = invisible or soup.find(class_="g-recaptcha")

        if not widget:
            raise Exception("No reCAPTCHA widget found")

        sitekey = widget.get("data-sitekey")
        is_invisible = invisible is not None
        callback = widget.get("data-callback")

        params = {
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": page_url
        }
        if is_invisible:
            params["invisible"] = 1

        resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
        if not resp.text.startswith("OK|"):
            raise Exception(f"Submit failed: {resp.text}")

        task_id = resp.text.split("|")[1]
        for _ in range(60):
            time.sleep(5)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get", "id": task_id
            })
            if result.text.startswith("OK|"):
                return {
                    "token": result.text.split("|")[1],
                    "variant": "invisible" if is_invisible else "checkbox",
                    "callback": callback,
                    "sitekey": sitekey
                }
            if result.text != "CAPCHA_NOT_READY":
                raise Exception(f"Solve error: {result.text}")

        raise Exception("Timed out")

# Usage
solver = RecaptchaV2UniversalSolver("YOUR_API_KEY")
result = solver.detect_and_solve("https://example.com/login")
print(f"Variant: {result['variant']}, Callback: {result['callback']}")

سيناريو من السوق العربي: بوابة حجوزات خليجية

فريق ضمان جودة في شركة سفر بالرياض يفحص مسار الحجز قبل كل إصدار. النموذج نفسه يسلك سلوكين: مربع اختيار على سطح المكتب، وتحقق صامت على واجهة الجوال عند الضغط على "تأكيد الحجز". السكربت الذي افترض وجود مربع اختيار كان ينجح في فحص واحد ويفشل في الآخر بلا رسالة خطأ.

العلاج كان في الترتيب لا في الخدمة:

  1. اكتشاف النسخة في بداية كل جلسة بدل افتراضها من بيئة التشغيل.
  2. تمرير invisible=1 تلقائياً عند اكتشاف النسخة غير المرئية.
  3. قراءة data-callback واستدعاؤه فور وصول الرمز وقبل انتهاء عمره.

أما تشغيلياً فخطط CaptchaAI محسوبة بعدد الـ threads المتزامنة لا بعدد الحلول، مع حلول غير محدودة لكل thread: خطة BASIC بسعر 15 دولاراً شهرياً وخمسة threads تكفي فحصاً ليلياً لمسار واحد، بينما تناسب ADVANCE بسعر 90 دولاراً وخمسين thread فريقاً يختبر عدة بوابات معاً.

أخطاء متكررة أثناء التنفيذ

العَرَض في نسخة مربع الاختيار في النسخة غير المرئية
الرمز مرفوض تأكد من كتابته في حقل g-recaptcha-response احقن الرمز ثم استدعِ دالة data-callback
لا يُعثر على الـ widget ابحث عن الفئة .g-recaptcha ابحث عن data-size="invisible" أو استدعاء render
النموذج يُرسل ثم يفشل تحقق هل تنتظر الصفحة رد نداء استخرج اسم رد النداء ونفّذه
إعادة تحميل الصفحة بعد الحقن تحقق من JavaScript الخاص بالنموذج استدعِ رد النداء قبل تشغيل الإرسال التلقائي

أسئلة شائعة

لماذا يُقبل الرمز في نسخة مربع الاختيار ويُرفض في النسخة غير المرئية؟

لأن الرفض غالباً ليس رفضاً للرمز. النموذج ينتظر إشارة من دالة رد النداء، وهي تأتي عادةً من نقرة الزائر. في النسخة غير المرئية لا نقرة هناك، فيبقى منتظراً حتى تستدعي الدالة بنفسك.

كم تبقى صلاحية الرمز، وماذا أفعل إذا انتهت المهلة؟

عمر الرمز 120 ثانية في النسختين. نفّذ الحقن والإرسال مباشرة بعد وصول الاستجابة، ولا تخزّن الرموز لاستخدام لاحق. وإذا انتهت المهلة، اطلب رمزاً جديداً من in.php.

هل أحتاج خطة أعلى للتعامل مع النسخة غير المرئية؟

لا. النسختان تستخدمان method=userrecaptcha نفسها، ولا رسوم إضافية بحسب النوع. ما يحدد الخطة هو عدد الطلبات المتزامنة أي عدد الـ threads. ابدأ من دليل البدء السريع مع CaptchaAI.

هل يتغيّر نوع الاختبار بين الجوال وسطح المكتب؟

نعم، وهو شائع. مفتاح الموقع يبقى واحداً بينما يختلف التركيب، فقد يظهر مربع اختيار على سطح المكتب وتعمل النسخة غير المرئية على الجوال. لذلك يبقى الاكتشاف الديناميكي أأمن من أي افتراض ثابت.

اقرأ أيضاً

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