عندما ينجح حلّ reCAPTCHA v2 عبر الـ API ويعود برمز صالح، لكن الموقع يرفض الرمز نفسه داخل المتصفح، فالسبب في أغلب الحالات ليس الرمز بل طريقة حقنه: نصّ الاستجابة لم يُملأ في المكان الصحيح، أو دالة رد النداء (callback) لم تُطلَق، أو انتهت صلاحية الرمز قبل الإرسال. هذا الدليل يمشي معك على نقاط الفشل الخمس بالترتيب — من الأكثر شيوعًا إلى الأندر — ويعطيك كود Python جاهزًا لكل حالة.
سيناريو مألوف: فريق تطوير في القاهرة يؤتمت تعبئة نموذج تسجيل على بوابة خدمات إقليمية. الحل ينجح عبر الـ API ويعود برمز صالح في كل مرة، لكن النموذج في المتصفح يظل يرفضه. عند الفحص يتبيّن أن زر الإرسال مرتبط بدالة رد نداء لم تُنفَّذ — وهي الحالة الثانية في هذا الدليل بالضبط.
أين يفشل الرمز فعليًا: خريطة نقاط الفشل
قبل أي كود، حدِّد أيّ الأعراض تطابق حالتك. الجدول يربط كل عَرَض بسببه الجذري ليختصر عليك التشخيص:
| المشكلة | العَرَض | السبب الجذري |
|---|---|---|
| النموذج يُرسَل بلا رمز | لا يصل أي رمز مع الطلب | محدِّد g-recaptcha-response خاطئ |
| الموقع يعيد عرض الكابتشا | يظهر التحدي من جديد | تأخّر طويل بين الحل والإرسال |
| زر الإرسال يبقى معطّلاً | لا يمكن إرسال النموذج | دالة رد النداء لم تُطلَق |
| الرمز غير صالح لهذا النطاق | رفض فوري بعد الإرسال | عدم تطابق pageurl |
| الحظر بعد قبول الرمز | خطأ 403 رغم قبول الرمز | بصمة المتصفح واكتشاف الأتمتة |
الحل 1: احقن رمز reCAPTCHA v2 في المكان الصحيح
أكثر الأسباب شيوعًا هو أبسطها: الرمز يوضَع في المكان الخطأ. ثلاثة عوائق تجعل مجرد إسناد القيمة غير كافٍ:
- حقل الاستجابة
g-recaptcha-responseقد يكون مخفيًا في الصفحة. - قد توجد أكثر من نسخة من الحقل نفسه على نفس الصفحة.
- إسناد القيمة وحده لا يفعّل النموذج؛ يجب أيضًا إطلاق رد النداء الذي تنتظره الصفحة.
الدالة التالية تملأ كل نسخ الحقل، ثم تبحث في بنية grecaptcha عن دالة رد النداء وتستدعيها:
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
def inject_recaptcha_token(driver, token):
"""Properly inject reCAPTCHA v2 token in browser."""
# Step 1: Make the response textarea visible and set the value
driver.execute_script("""
// Find all response textareas (may be multiple on page)
var textareas = document.querySelectorAll('[name="g-recaptcha-response"]');
textareas.forEach(function(ta) {
ta.style.display = 'block';
ta.value = arguments[0];
});
// Also set via ID if present
var byId = document.getElementById('g-recaptcha-response');
if (byId) {
byId.style.display = 'block';
byId.value = arguments[0];
}
""", token)
# Step 2: Trigger the callback
driver.execute_script("""
// Try standard callback
if (typeof ___grecaptcha_cfg !== 'undefined') {
var clients = ___grecaptcha_cfg.clients;
for (var key in clients) {
var client = clients[key];
// Navigate nested structure to find callback
for (var prop in client) {
var val = client[prop];
if (val && typeof val === 'object') {
for (var subprop in val) {
var subval = val[subprop];
if (subval && typeof subval === 'object' && subval.callback) {
subval.callback(arguments[0]);
return;
}
}
}
}
}
}
// Fallback: try common callback names
if (typeof onCaptchaSuccess === 'function') onCaptchaSuccess(arguments[0]);
else if (typeof captchaCallback === 'function') captchaCallback(arguments[0]);
""", token)
# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/form")
# ... solve CAPTCHA via CaptchaAI ...
inject_recaptcha_token(driver, token)
time.sleep(1)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
الحل 2: النماذج التي تنتظر رد النداء (callback)
بعض النماذج تُبقي زر الإرسال معطّلاً حتى تتأكد من أن رد النداء نُفِّذ فعلًا. هنا لا يكفي ملء الحقل؛ عليك العثور على اسم الدالة — سواء من السمة data-callback أو من إعدادات grecaptcha الداخلية — ثم استدعاؤها بالرمز:
def find_and_trigger_callback(driver, token):
"""Find the reCAPTCHA callback and trigger it."""
# Method 1: Extract callback from data attribute
callback_name = driver.execute_script("""
var widget = document.querySelector('.g-recaptcha');
if (widget) {
return widget.getAttribute('data-callback');
}
return null;
""")
if callback_name:
driver.execute_script(f"window['{callback_name}'](arguments[0]);", token)
return True
# Method 2: Extract from grecaptcha config
triggered = driver.execute_script("""
try {
var widgetId = 0;
var callback = ___grecaptcha_cfg.clients[widgetId].aa.l.callback;
if (typeof callback === 'function') {
callback(arguments[0]);
return true;
}
} catch(e) {}
return false;
""", token)
return triggered
الحل 3: التوقيت — احقن الرمز فور استلامه
الرمز ليس دائم الصلاحية. رمز reCAPTCHA v2 يبقى صالحًا لفترة قصيرة (نحو دقيقتين)، وكل ثانية تمرّ بين استلامه وإرساله تقرّبه من الانتهاء. اتبع هذا الترتيب دون فواصل زمنية:
- استطلِع النتيجة حتى يعود الرمز من الخدمة.
- احقن الرمز في الصفحة فور وصوله مباشرة.
- أرسِل النموذج خلال ثوانٍ من الحقن.
القاعدة: احقن وأرسِل فورًا، ولا تخزّن الرمز لاستخدام لاحق.
المسار الكامل من الإرسال إلى الحقن:
import requests
import time
def solve_and_inject_fast(driver, api_key, sitekey, page_url):
"""Solve CAPTCHA and inject token with minimal delay."""
# Submit solve request
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": api_key,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"json": 1,
}, timeout=30)
task_id = resp.json()["request"]
# Poll for result
time.sleep(15)
for _ in range(24):
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": api_key, "action": "get",
"id": task_id, "json": 1,
}, timeout=15)
data = resp.json()
if data.get("status") == 1:
token = data["request"]
# IMMEDIATELY inject — don't wait
inject_recaptcha_token(driver, token)
# Submit form within 5 seconds
time.sleep(0.5)
return True
if data["request"] != "CAPCHA_NOT_READY":
raise RuntimeError(data["request"])
time.sleep(5)
raise TimeoutError("Solve timeout")
الحل 4: عنوان الصفحة الصحيح في تطبيقات SPA وإطارات iframe
في تطبيقات الصفحة الواحدة (SPA) قد يتغيّر عنوان الصفحة بعد التنقّل أو إعادة التوجيه، فيصبح الـ pageurl الذي أرسلته للـ API مختلفًا عن العنوان الذي تراه الكابتشا فعليًا — والنتيجة رمز غير صالح لهذا النطاق. الحل: استخدم دائمًا driver.current_url لحظة الحل، لا العنوان الذي بدأت منه، وتحقّق مما إذا كانت الكابتشا داخل إطار iframe:
def get_correct_pageurl(driver):
"""Get the URL that reCAPTCHA actually sees."""
# Check if CAPTCHA is in an iframe
iframes = driver.find_elements(By.TAG_NAME, "iframe")
for iframe in iframes:
src = iframe.get_attribute("src") or ""
if "recaptcha" in src or "anchor" in src:
# CAPTCHA is in iframe — use parent page URL
return driver.current_url
# For SPAs, use current URL (may differ from initial load)
return driver.current_url
# WRONG — using the URL you navigated to
pageurl = "https://example.com/form" # May have redirected
# CORRECT — using the URL the browser is actually on
pageurl = get_correct_pageurl(driver)
الحل 5: الحظر بعد قبول الرمز — مؤشرات الأتمتة
أحيانًا يُقبل الرمز ثم يأتي الحظر (403) بعده مباشرة. هنا المشكلة ليست الكابتشا، بل مؤشرات الأتمتة الأخرى التي يرصدها الموقع:
- خاصية
navigator.webdriverالظاهرة للصفحة. - أعلام التشغيل الآلي في المتصفح.
- حجم النافذة غير الواقعي.
تقليل هذه المؤشرات يجعل الجلسة أقرب إلى متصفح بشري عادي، ويرفع موثوقية سير العمل دون أي التفاف على الحماية:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
def create_stealth_driver():
"""Create a browser that avoids basic bot detection."""
options = Options()
# Remove automation indicators
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
# Set realistic window size
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
# Override navigator.webdriver
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
})
return driver
driver = create_stealth_driver()
الحل 6: حقن رمز Cloudflare Turnstile
Cloudflare Turnstile يخزّن استجابته في حقل مختلف اسمه cf-turnstile-response، ويتطلّب إطلاق حدثَي change وinput حتى تلتقط الصفحة القيمة. لذلك تختلف آلية حقنه قليلًا عن reCAPTCHA v2:
def inject_turnstile_token(driver, token):
"""Inject Turnstile token into the page."""
driver.execute_script("""
// Find Turnstile response input
var inputs = document.querySelectorAll(
'input[name="cf-turnstile-response"], ' +
'input[name="g-recaptcha-response"]'
);
inputs.forEach(function(input) {
input.value = arguments[0];
});
// Trigger change event
inputs.forEach(function(input) {
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('input', { bubbles: true }));
});
// Try Turnstile callback
if (window.turnstile) {
var widgets = document.querySelectorAll('[data-callback]');
widgets.forEach(function(w) {
var cb = w.getAttribute('data-callback');
if (typeof window[cb] === 'function') {
window[cb](arguments[0]);
}
});
}
""", token)
قائمة فحص سريعة قبل أن تستسلم
قبل أن تفترض أن العيب في الرمز نفسه، اطبع حالة الصفحة وتحقّق من هذه المؤشرات:
- هل حقل الاستجابة موجود في DOM؟
- هل امتلأ الحقل بالقيمة فعلًا؟
- هل جرى تحميل grecaptcha؟
- ما اسم دالة رد النداء؟
- ما عنوان الصفحة الحالي؟
الدالة التالية تجمع هذه المؤشرات كلها في مكان واحد لتقرأ السبب بنظرة واحدة:
def debug_captcha_injection(driver, token):
"""Print debug info to diagnose injection failures."""
info = driver.execute_script("""
var result = {};
// Check textarea exists
var ta = document.querySelector('[name="g-recaptcha-response"]');
result.textarea_found = !!ta;
result.textarea_value_set = ta ? ta.value.length > 0 : false;
// Check reCAPTCHA loaded
result.grecaptcha_loaded = typeof grecaptcha !== 'undefined';
// Check for callback
var widget = document.querySelector('.g-recaptcha');
result.has_data_callback = widget ? !!widget.getAttribute('data-callback') : false;
result.callback_name = widget ? widget.getAttribute('data-callback') : null;
// Current URL
result.current_url = window.location.href;
// Check for errors in console (basic)
result.has_submit_button = !!document.querySelector('button[type=submit], input[type=submit]');
return result;
""")
for key, value in info.items():
print(f" {key}: {value}")
return info
جدول التشخيص السريع
بعد أن تقرأ حالة الصفحة، طابِق العَرَض مع الإصلاح المناسب مباشرة:
| العَرَض | السبب | الإصلاح |
|---|---|---|
| الرمز مضبوط لكن النموذج يرفضه | لم تُطلَق دالة رد النداء | ابحث عن الدالة واستدعِها بالرمز |
| زر الإرسال يبقى معطّلاً | رد النداء هو ما يفعّله | أطلِق رد النداء بعد حقن الرمز |
| «كابتشا غير صالحة» بعد الإرسال | انتهت صلاحية الرمز | قلّل الوقت بين الحل والإرسال |
| خطأ 403 بعد إرسال النموذج | اكتشاف الأتمتة لا الكابتشا | استخدم خيارات تقلّل مؤشرات الأتمتة |
| ينجح عبر الطلبات ويفشل في المتصفح | اختلاف عنوان الصفحة | استخدم driver.current_url كـ pageurl |
الأسئلة الشائعة
كم من الوقت يبقى رمز reCAPTCHA v2 صالحًا بعد الحل؟
رمز reCAPTCHA v2 صالح لفترة قصيرة (نحو دقيقتين). إذا تأخّر إرسال النموذج بعد حقن الرمز، فقد ينتهي ويعرض الموقع الكابتشا من جديد. القاعدة: احقن وأرسِل مباشرة، ولا تخزّن الرمز لوقت لاحق.
كيف أفرّق بين فشل بسبب رد النداء وفشل بسبب انتهاء الصلاحية؟
افحص حالة الصفحة أولًا. إن كان الحقل ممتلئًا وزر الإرسال معطّلاً، فالمشكلة رد النداء. أما إن قَبِل الموقع الإرسال ثم قال «كابتشا غير صالحة»، فالسبب على الأرجح انتهاء الصلاحية أو عدم تطابق pageurl.
هل يختلف حقن رمز Turnstile عن reCAPTCHA v2؟
نعم. يستخدم Cloudflare Turnstile حقل cf-turnstile-response بدل g-recaptcha-response، ويحتاج إلى إطلاق حدثَي change وinput لتلتقط الصفحة القيمة. راجع الحل 6 أعلاه.
يستمر ظهور 403 رغم قبول الرمز — ما الحل؟
خطأ 403 بعد قبول الرمز يعني أن مصدر الحظر هو اكتشاف الأتمتة لا الكابتشا. قلّل مؤشرات الأتمتة كما في الحل 5، واستخدم عنوان IP نظيفًا عبر خادم وسيط موثوق لتحسين سمعة عنوان IP.