ما يكسر الأتمتة بين نسختَي 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']}")
سيناريو من السوق العربي: بوابة حجوزات خليجية
فريق ضمان جودة في شركة سفر بالرياض يفحص مسار الحجز قبل كل إصدار. النموذج نفسه يسلك سلوكين: مربع اختيار على سطح المكتب، وتحقق صامت على واجهة الجوال عند الضغط على "تأكيد الحجز". السكربت الذي افترض وجود مربع اختيار كان ينجح في فحص واحد ويفشل في الآخر بلا رسالة خطأ.
العلاج كان في الترتيب لا في الخدمة:
- اكتشاف النسخة في بداية كل جلسة بدل افتراضها من بيئة التشغيل.
- تمرير
invisible=1تلقائياً عند اكتشاف النسخة غير المرئية. - قراءة
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.
هل يتغيّر نوع الاختبار بين الجوال وسطح المكتب؟
نعم، وهو شائع. مفتاح الموقع يبقى واحداً بينما يختلف التركيب، فقد يظهر مربع اختيار على سطح المكتب وتعمل النسخة غير المرئية على الجوال. لذلك يبقى الاكتشاف الديناميكي أأمن من أي افتراض ثابت.