مفتاح الموقع وأداة reCAPTCHA غير موجودين في مصدر صفحة تطبيق الصفحة الواحدة لحظة فتحها؛ إطار العمل هو من يحقنهما بعد تركيب المكوّن أو بعد تغيير المسار. الحل عمليًا هو ترتيب السكربت حول لحظة الظهور هذه: انتظار الأداة، ثم التقاط مفتاح الموقع من موضعه الحقيقي، ثم إرسال المهمة إلى CaptchaAI، وأخيرًا إعادة الرمز إلى المسار الذي يقرأه التطبيق قبل الطلب النهائي.
- انتظر ظهور الـ iframe أو عنصر
.g-recaptchaبدل قراءة HTML الأولي مرة واحدة. - استخرج مفتاح الموقع من
srcالخاص بالـ iframe أو من السمةdata-sitekey. - أرسل المهمة إلى
in.phpثم استطلع النتيجة منres.php. - احقن الرمز في حقل
g-recaptcha-responseأو مرّره إلى دالة رد النداء.
ما الذي يكسر السكربت التقليدي مع تطبيقات SPA؟
الفرق ليس في reCAPTCHA نفسها، بل في توقيت ظهورها. الصفحة التقليدية تسلّم كل شيء دفعة واحدة، أما تطبيق الصفحة الواحدة فيبني الواجهة تدريجيًا داخل المتصفح:
| صفحة تقليدية | تطبيق صفحة واحدة — SPA |
|---|---|
| سكربت reCAPTCHA داخل HTML الأولي | يُحقن السكربت بعد تغيير المسار |
| الأداة تُرسم عند تحميل الصفحة | الأداة تُرسم عند تركيب المكوّن |
| مفتاح الموقع ظاهر في مصدر الصفحة | مفتاح الموقع داخل حزمة JavaScript |
| النموذج يُرسل عبر POST | النموذج يُرسل عبر XHR أو fetch |
أي سكربت يقرأ مصدر الصفحة مرة واحدة سيعود بلا مفتاح موقع ثم يفشل بصمت. تعامل مع الصفحة كحالة متغيّرة، لا كملف ثابت.
الخطوة 1: انتظر ظهور أداة reCAPTCHA بدل قراءة المصدر
في كثير من الواجهات لا تظهر reCAPTCHA حتى ينفّذ المستخدم فعلاً محددًا: فتح نموذج الدخول، أو الانتقال إلى مسار الدفع. حاكِ ذلك الفعل أولاً، ثم انتظر عنصر الـ iframe بمهلة معقولة.
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com/login")
# SPA may need a click or navigation to trigger reCAPTCHA
page.click("#show-login-form")
# Wait for reCAPTCHA iframe to appear
recaptcha_frame = page.wait_for_selector(
"iframe[src*='recaptcha']",
timeout=15000
)
print("reCAPTCHA detected:", recaptcha_frame.get_attribute("src"))
مهلة 15 ثانية تستوعب تحميل حزمة JavaScript على اتصال متوسط دون تجميد السكربت أمام صفحة بلا reCAPTCHA. سجّل قيمة src عند أول ظهور — ستحتاجها في الخطوة التالية.
الخطوة 2: استخرج مفتاح الموقع من مواضعه الثلاثة المحتملة
مفتاح الموقع قد يكون في أي من ثلاثة مواضع، ولا يوجد ترتيب واحد يصلح لكل المشاريع:
- داخل معامل
k=في رابط الـ iframe بعد رسم الأداة. - في السمة
data-sitekeyعلى العنصر الذي يرسمه إطار العمل. - داخل حزمة JavaScript نفسها، وهو الملاذ الأخير.
# Method 1: From the iframe src
iframe_src = recaptcha_frame.get_attribute("src")
# src contains: ...?k=6LcR_RsTAAAAADge...
import re
match = re.search(r'[?&]k=([^&]+)', iframe_src)
site_key = match.group(1) if match else None
# Method 2: From data-sitekey attribute
site_key = page.eval_on_selector(
"[data-sitekey]",
"el => el.getAttribute('data-sitekey')"
)
# Method 3: From JavaScript bundle (last resort)
site_key = page.evaluate("""
() => {
// Check for grecaptcha render calls
const scripts = document.querySelectorAll('script');
for (const s of scripts) {
const match = s.textContent.match(/sitekey['":\s]+(['"])(6L[^'"]+)\\1/);
if (match) return match[2];
}
return null;
}
""")
جرّب المواضع بهذا الترتيب وتوقّف عند أول نتيجة صالحة. مفاتيح reCAPTCHA تبدأ عادةً بالبادئة 6L، وهو فحص سريع يمنع إرسال مفتاح مشوّه.
الخطوة 3: أرسل المهمة إلى CaptchaAI واستطلع النتيجة
بعد أن يصبح مفتاح الموقع بين يديك ينتهي دور إطار العمل: الخدمة تحتاج المفتاح وعنوان الصفحة فقط.
import requests
import time
def solve_recaptcha(site_key, page_url):
# Submit task
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": "YOUR_API_KEY",
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"json": 1
})
task_id = resp.json()["request"]
# Poll for result
for _ in range(60):
time.sleep(3)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": "YOUR_API_KEY",
"action": "get",
"id": task_id,
"json": 1
})
data = result.json()
if data["status"] == 1:
return data["request"]
raise TimeoutError("Solve timed out")
الفاصل الزمني بين محاولات الاستطلاع ثابت عند ثلاث ثوانٍ، وتقليله لا يسرّع الحل. وانتهاء المهلة يُرفع كخطأ مستقل يفصل المهمة البطيئة عن الخطأ الحقيقي في المعاملات.
الخطوة 4: أعد الرمز إلى المسار الذي ينتظره التطبيق
هنا تفشل أغلب المحاولات: تطبيقات الصفحة الواحدة لا ترسل نماذج HTML تقليدية، فقد لا تكفي كتابة الرمز في حقل مخفي. أمامك ثلاثة أساليب.
الأسلوب الأول: كتابة الرمز في الحقل المخفي
يصلح مع الواجهات التي تقرأ قيمة g-recaptcha-response مباشرة عند الإرسال.
token = solve_recaptcha(site_key, "https://example.com/login")
# Inject into the g-recaptcha-response textarea
page.evaluate(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
""")
# Submit the form
page.click("#login-button")
إن ظهرت رسالة "أكمل التحقق" رغم امتلاء الحقل، فالواجهة تعتمد على رد النداء لا على قيمة الحقل.
الأسلوب الثاني: استدعاء دالة رد النداء
معظم عمليات الدمج الحديثة تسجّل دالة في السمة data-callback وتنتظر استدعاءها بالرمز.
# Find the callback function name
callback = page.evaluate("""
() => {
const widget = document.querySelector('.g-recaptcha');
return widget?.getAttribute('data-callback') || null;
}
""")
# Call it with the token
if callback:
page.evaluate(f"window['{callback}']('{token}')")
else:
# Try the default grecaptcha callback
page.evaluate(f"""
if (window.grecaptcha) {{
// Set the response and trigger verification
document.getElementById('g-recaptcha-response').value = '{token}';
// Find and call any registered callbacks
const form = document.querySelector('form');
if (form) form.dispatchEvent(new Event('submit'));
}}
""")
استدعاء رد النداء أكثر متانة: يسلك المسار نفسه ويحدّث حالة React أو Vue بدل تعديل عنصر قد يُعاد رسمه.
الأسلوب الثالث: اعتراض طلب XHR باستخدام Puppeteer
حين يرسل التطبيق بياناته إلى نقطة نهاية REST بدل نموذج، راقب الطلب للتأكد من وصول الرمز ضمن حمولة الطلب.
const puppeteer = require('puppeteer');
async function solveRecaptchaInSPA() {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Intercept the API call and inject the token
await page.setRequestInterception(true);
page.on('request', request => {
if (request.url().includes('/api/login')) {
const postData = JSON.parse(request.postData() || '{}');
// Token already set via page.evaluate — let it pass
request.continue();
} else {
request.continue();
}
});
await page.goto('https://example.com/login');
await page.waitForSelector('[data-sitekey]');
const siteKey = await page.$eval(
'[data-sitekey]',
el => el.getAttribute('data-sitekey')
);
// Solve with CaptchaAI (implementation omitted — same as Python)
const token = await solveCaptcha(siteKey, page.url());
// Inject token and trigger callback
await page.evaluate((t) => {
document.getElementById('g-recaptcha-response').value = t;
const callback = document.querySelector('.g-recaptcha')
?.getAttribute('data-callback');
if (callback && window[callback]) {
window[callback](t);
}
}, token);
}
أنماط جاهزة حسب إطار العمل
React مع react-google-recaptcha
تستخدم تطبيقات React عادةً حزمة react-google-recaptcha، والمكوّن يُرسم بشكل غير متزامن بعد اكتمال دورة التركيب:
# Wait for React to mount the component
page.wait_for_selector(".g-recaptcha", state="attached")
# The sitekey is in the rendered div's data attribute
site_key = page.eval_on_selector(
".g-recaptcha", "el => el.dataset.sitekey"
)
Vue مع vue-recaptcha
في Vue يكون الشرط v-if هو ما يؤخّر الرسم غالبًا، لذا نفّذ التفاعل الذي يحقق الشرط أولاً:
# Vue may use v-if to conditionally render
# Navigate or interact to trigger the condition
page.click("#proceed-to-checkout")
page.wait_for_selector("iframe[src*='recaptcha']")
Angular والتحميل الكسول للوحدات
تحمّل تطبيقات Angular وحدة reCAPTCHA بشكل كسول أحيانًا، فانتظر محدد المكوّن لا الـ iframe وحده:
# Angular apps may lazy-load reCAPTCHA modules
# Wait for the specific Angular component
page.wait_for_selector("re-captcha, app-recaptcha, [data-sitekey]")
التنقل بين مسارات تطبيق SPA دون إعادة تحميل الصفحة
يتغيّر العنوان في شريط المتصفح دون إعادة تهيئة الصفحة، فأي مرجع التقطته قبل التنقل قد يصبح قديمًا:
# Listen for reCAPTCHA appearing after SPA navigation
page.goto("https://example.com")
# Navigate within the SPA
page.click("a[href='/login']")
# The URL changed but no page reload happened
# Wait for the CAPTCHA to render in the new "page"
page.wait_for_selector("iframe[src*='recaptcha']", timeout=10000)
القاعدة العملية: أعد التقاط مفتاح الموقع والعنوان بعد كل تغيير مسار، ولا تعِد استخدام رمز من مسار آخر.
مثال من السوق العربي: اختبار مسار الدفع قبل الجمعة البيضاء
فريق جودة في متجر إلكتروني خليجي يشغّل واجهة Vue، ويريد التأكد قبل موسم الجمعة البيضاء من صمود مسار إتمام الشراء تحت الضغط. الأداة لا تظهر إلا بعد النقر على "تأكيد الطلب"، فبُني السيناريو المعتمد كالتالي:
- تشغيل الاختبار على بيئة التجربة الخاصة بالمتجر نفسه، لا على مواقع طرف ثالث.
- الانتقال من صفحة السلة إلى مسار الدفع، ثم انتظار ظهور الأداة.
- استخراج مفتاح الموقع من
data-sitekey، وإرسال المهمة، واستدعاء رد النداء بالرمز. - تسجيل زمن كل دورة كاملة في سجل الاختبار الليلي لمقارنة النتائج أسبوعيًا.
النتيجة العملية: الأداة تُعاد تهيئتها عند العودة للخلف من صفحة الملخص، وهو عطل لم يكن ليظهر في اختبار يدوي.
كم Thread تحتاج لهذا النوع من التشغيل؟
الـ threads في CaptchaAI هي سقف الطلبات المتزامنة، لا سقف عمليات الحل. خطة BASIC بسعر $15 شهريًا تمنحك 5 threads، وهي كافية لسكربت اختبار واحد. ومع متصفحات متوازية داخل خط تكامل مستمر تقترب خطة STANDARD بسعر $30 شهريًا مع 15 thread من الواقع، بينما تناسب ADVANCE بسعر $90 شهريًا مع 50 thread الاختبار الليلي. كل خطة تتيح عمليات حل غير محدودة داخل الـ thread الواحد، فالتكلفة مرتبطة بالتزامن لا بالحجم.
أعطال شائعة وكيفية معالجتها
| العطل | السبب الأرجح | المعالجة |
|---|---|---|
العنصر g-recaptcha-response غير موجود |
الواجهة لم ترسم الأداة بعد | انتظر المحدد بمهلة كافية بدل القراءة الفورية |
| الرمز يُكتب لكن النموذج لا يُرسل | الواجهة تنتظر رد النداء | استخرج data-callback واستدعِ الدالة بالرمز |
| الرمز يعمل مرة ثم يفشل | إعادة رسم المكوّن مسحت القيمة | احقن الرمز مباشرة قبل الإرسال |
| الجهة المستهدفة ترفض الرمز | مفتاح الموقع أو العنوان لا يطابق ما أُرسل | أعد التقاط المعاملات واستخدم الرمز في الجلسة نفسها |
أسئلة شائعة
كم يبقى الرمز صالحًا بعد استلامه؟
رمز reCAPTCHA v2 قصير العمر — دقيقتان تقريبًا وفق توثيق Google. اجعل الحقن والإرسال النهائي متتاليين، ولا تخزّن الرمز لدورة لاحقة.
لماذا يختفي الرمز بعد إعادة رسم المكوّن؟
لأن React وVue يديران قيمة العنصر عبر حالتهما الداخلية، فأي كتابة مباشرة على الـ DOM تُمسح عند أول إعادة رسم. استدعاء رد النداء يعالج ذلك لأنه يحدّث الحالة نفسها.
هل أحتاج إلى خادم وسيط لتشغيل هذا السيناريو؟
ليس شرطًا. إن كنت تختبر بيئتك أو بيئة عميل تملك إذنًا مكتوبًا بها، فعنوان IP ثابت يكفي. البروكسي يفيد عند محاكاة مواقع جغرافية مختلفة ضمن اختبارات مصرّح بها.
هل تختلف الطريقة مع reCAPTCHA v2 Invisible؟
التسلسل نفسه، لكن لا يوجد مربع ظاهر تنتظره. راقب بدلاً منه حقن سكربت reCAPTCHA أو ظهور الـ iframe الخفي، ثم أكمل بالخطوات ذاتها.
هل يؤثر إطار العمل على نتيجة الحل؟
لا. تستقبل CaptchaAI مفتاح الموقع وعنوان الصفحة فقط؛ طريقة رسم الأداة تحدد كيفية التقاط المعاملات وحقن الرمز لا أكثر.
الخطوات التالية
- البدء السريع مع CaptchaAI: حلّ أول كابتشا في 5 دقائق
- كيفية حلّ reCAPTCHA v2 عبر الـ API: دليل خطوة بخطوة
- كيفية حل Cloudflare Turnstile باستخدام واجهة API
- كيفية حل GeeTest v3 باستخدام API