داخل المتصفح لا يجري سوى إجراءين: قراءة سمة data-sitekey من الصفحة، وكتابة قيمة الرمز في الحقل المخفي g-recaptcha-response. أما الاختبار نفسه فيُحلّ خارج الصفحة عبر طلبين إلى CaptchaAI API، ولذلك لا ينقر المتصفح على عنصر واجهة CAPTCHA في أي لحظة، ولا يحتاج سكربت Python إلى تحليل صور أو محاكاة حركة مؤشر.
ولأن العبء الحقيقي يقع خارج المتصفح، يعمل السكربت نفسه في الوضع بلا واجهة رسومية، وداخل حاويات Docker، وعلى خوادم التكامل المستمر، دون تعديل يُذكر في الشيفرة.
أين يقع CaptchaAI في مسار التنفيذ
الفكرة هنا توزيع للأدوار: المتصفح يتولى الصفحة، والخدمة تتولى الاختبار، ولا يتداخل الطرفان.
- يحمّل Selenium الصفحة المستهدفة وينتظر ظهور عنصر CAPTCHA.
- يقرأ السكربت مفتاح الموقع sitekey من الـ DOM.
- يحل CaptchaAI الاختبار اعتماداً على sitekey وعنوان الصفحة.
- يحقن السكربت الرمز الناتج في الحقل المخصص ثم يرسل النموذج.
النقطة التي تُهمل غالباً هي عنوان الصفحة: يجب أن تكون قيمة pageurl مطابقة تماماً للعنوان الذي ظهر فيه الاختبار، بما في ذلك النطاق الفرعي والمسار. أي اختلاف هنا ينتج رمزاً يرفضه الخادم لاحقاً رغم أن عملية الحل نفسها انتهت بنجاح.
ما تحتاجه قبل تشغيل السكربت
القائمة قصيرة، ولا تتطلب مكتبات إضافية للتعامل مع المتصفح:
| المتطلب | التفاصيل |
|---|---|
| Python 3.7+ | مع أداة pip |
| Selenium | pip install selenium |
| Chrome + ChromeDriver | إصداران متطابقان |
| requests | pip install requests |
| مفتاح CaptchaAI API | من captchaai.com |
وداخل الحاويات، ثبّت Chrome وChromeDriver بالإصدار نفسه؛ أكثر أعطال البدء شيوعاً سببه فارق الإصدار بينهما لا الشيفرة.
الخطوة 1: تهيئة Selenium وجلسة Chrome
ابدأ بجلسة Chrome عادية مع خيارين يقلّلان الفروق بين جلسة الأتمتة وجلسة المستخدم:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
driver = webdriver.Chrome(options=options)
هذه الخيارات تزيل بعض العلامات التي يضيفها Chrome عند تشغيله عبر أداة أتمتة، وهي إعدادات معتادة في بيئات الاختبار المملوكة. ولتهيئة أدق أضف:
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
الخطوة 2: بناء دالة الحل عبر CaptchaAI API
الدالة التالية تنفذ الدورة كاملة: إرسال المهمة إلى in.php، ثم استطلاع النتيجة من res.php حتى تعود الاستجابة النهائية.
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(site_key, page_url):
"""Solve reCAPTCHA v2 using CaptchaAI API."""
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
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": API_KEY,
"action": "get",
"id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("CAPTCHA solve timed out")
ثلاث ملاحظات تشغيلية على هذه الدالة. الاستطلاع الدوري كل 5 ثوانٍ لستين محاولة يمنح مهلة تقارب خمس دقائق، وهي أوسع مما تحتاجه فعلياً؛ إذ يُنجز reCAPTCHA v2 عادةً في أقل من 60 ثانية، وCloudflare Turnstile في أقل من 10 ثوانٍ. والاستجابة CAPCHA_NOT_READY ليست خطأ بل إشارة إلى أن المهمة قيد التنفيذ، فلا تُنهِ الحلقة عندها. وأخيراً اقرأ قيمة YOUR_API_KEY من متغير بيئة بدل كتابتها داخل الملف، خصوصاً قبل رفع الشيفرة إلى GitHub.
الخطوة 3: التقاط sitekey من الصفحة ثم طلب الحل
الآن اربط المتصفح بالدالة: انتظر ظهور عنصر g-recaptcha، اقرأ منه السمة data-sitekey، ثم مرّر القيمة مع عنوان الصفحة الحالي.
# Navigate to the target page
driver.get("https://example.com/login")
# Wait for the reCAPTCHA to load
wait = WebDriverWait(driver, 10)
recaptcha = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)
# Extract the site key
site_key = recaptcha.get_attribute("data-sitekey")
page_url = driver.current_url
print(f"Site key: {site_key}")
print(f"Page URL: {page_url}")
# Solve the CAPTCHA
token = solve_recaptcha_v2(site_key, page_url)
print(f"Token received: {token[:50]}...")
كثير من المواقع تحمّل عنصر CAPTCHA بعد اكتمال بقية الصفحة، لذلك تعيد القراءة المباشرة دون WebDriverWait قيمة فارغة يفشل معها الإرسال.
الخطوة 4: حقن الرمز في النموذج وإرساله
الرمز العائد نص طويل يوضع داخل الحقل المخفي g-recaptcha-response، ثم يُرسل النموذج بالطريقة التي تتوقعها الصفحة:
# Inject the token into the reCAPTCHA response field
driver.execute_script(f"""
document.getElementById('g-recaptcha-response').innerHTML = '{token}';
document.getElementById('g-recaptcha-response').style.display = '';
""")
# If the form uses a callback function, trigger it
driver.execute_script(f"""
if (typeof ___grecaptcha_cfg !== 'undefined') {{
Object.keys(___grecaptcha_cfg.clients).forEach(function(key) {{
var client = ___grecaptcha_cfg.clients[key];
if (client.callback) client.callback('{token}');
}});
}}
""")
# Submit the form
driver.find_element(By.CSS_SELECTOR, "form").submit()
# Wait for navigation
wait.until(EC.url_changes(page_url))
print(f"Success! Now on: {driver.current_url}")
إذا كان النموذج بلا زر إرسال تقليدي واعتمد على دالة رد نداء، فشغّل الدالة مباشرة كما في المقطع الثاني أعلاه؛ التفاصيل الكاملة في دليل حل reCAPTCHA v2 المعتمد على Callback. واحرص على أن تمر خطوتا الحقن والإرسال خلال 120 ثانية من استلام الرمز، لأن صلاحيته محدودة زمنياً.
سكربت Selenium كامل لحل CAPTCHA
تجمع النسخة التالية الخطوات الأربع في ملف واحد، مع إغلاق المتصفح داخل finally حتى لا تتراكم عمليات ChromeDriver عند فشل أي خطوة:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import requests
import time
API_KEY = "YOUR_API_KEY"
def solve_recaptcha_v2(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
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": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY":
continue
if result.text.startswith("OK|"):
return result.text.split("|")[1]
raise Exception(f"Solve failed: {result.text}")
raise TimeoutError("Timed out")
def main():
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com/login")
wait = WebDriverWait(driver, 10)
# Extract site key
recaptcha = wait.until(
EC.presence_of_element_located((By.CLASS_NAME, "g-recaptcha"))
)
site_key = recaptcha.get_attribute("data-sitekey")
# Solve
token = solve_recaptcha_v2(site_key, driver.current_url)
# Inject and submit
driver.execute_script(
f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';"
)
driver.find_element(By.CSS_SELECTOR, "form").submit()
wait.until(EC.url_changes(driver.current_url))
print("Login successful!")
finally:
driver.quit()
if __name__ == "__main__":
main()
توسيع النمط إلى أنواع أخرى من CAPTCHA
البنية نفسها تخدم بقية الأنواع؛ ما يتغير هو قيمة method والمعاملات المرافقة لها، بينما تبقى حلقة الاستطلاع كما هي.
reCAPTCHA v3
def solve_recaptcha_v3(site_key, page_url, action="verify"):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action
})
task_id = resp.text.split("|")[1]
# ... same polling logic
يحتاج الإصدار الثالث إلى تمرير اسم الإجراء action مطابقاً لما تنتظره الصفحة، وإلا عاد رمز سليم شكلاً لكنه مرفوض عند التحقق من جانب الخادم.
Cloudflare Turnstile
def solve_turnstile(site_key, page_url):
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url
})
task_id = resp.text.split("|")[1]
# ... same polling logic
وقبل أن تبني خط أتمتة كاملاً على هذا النمط، تأكد من نوع الاختبار الذي تتعامل معه: لا يدعم CaptchaAI حالياً hCaptcha ولا FunCaptcha، وGeeTest v4 مدرج ضمن الدعم القادم وليس متاحاً اليوم، بينما CaptchaFox وFriendly Captcha وLemin متاحة في مرحلة beta.
سيناريو تشغيلي: اختبارات ليلية قبل موسم التخفيضات
خذ فريق اختبار في متجر تجزئة إلكتروني بالسوق الخليجي يستعد لموسم الجمعة البيضاء. قبل الموسم يشغّل الفريق كل ليلة مجموعة اختبارات على بيئة المتجر المملوكة له: تسجيل دخول، إضافة إلى السلة، ثم إتمام الشراء. صفحة الدخول محمية بـ reCAPTCHA v2، وصفحة الدفع خلف Cloudflare Turnstile، أي أن كل تشغيل كامل يمر باختبارين على الأقل.
مع أربعين حالة اختبار تعمل بالتوازي، السؤال العملي ليس كلفة الحل الواحد بل عدد العمليات المتزامنة. تُحسب خطط CaptchaAI على أساس عدد الـ threads — أي عدد اختبارات CAPTCHA الجارية في اللحظة نفسها — مع عدد حلول غير محدود لكل thread خلال الشهر. خطة BASIC عند $15 شهرياً تمنح 5 threads وتكفي سكربتاً فردياً أثناء التطوير، بينما خطة ADVANCE عند $90 شهرياً تمنح 50 thread وتستوعب مجموعة الاختبارات الليلية كاملة دون طابور انتظار.
القاعدة العملية هنا: اجعل عدد الـ threads مساوياً لعدد جلسات Selenium المتزامنة، لا لعدد حالات الاختبار الكلي.
أخطاء متكررة أثناء التشغيل
| العرَض | السبب المرجّح | المعالجة |
|---|---|---|
| رفض الرمز بعد إرسال النموذج | انتهت صلاحية الرمز قبل وصوله | نفّذ الحقن والإرسال خلال 120 ثانية |
| لم يُعثر على قيمة sitekey | العنصر يُحمّل ديناميكياً بعد الصفحة | وسّع مهلة WebDriverWait وانتظر العنصر لا الصفحة |
NoSuchElementException |
محدد CSS لا يطابق بنية الصفحة الفعلية | افحص الصفحة وحدّث المحدد |
| تعارض في إصدار ChromeDriver | تحديث Chrome تلقائياً | نزّل ChromeDriver المطابق وثبّت الإصدار في البيئة |
| الطلب مرفوض رغم صحة الرمز | طبقة حماية إضافية خارج CAPTCHA | راجع سمعة عنوان IP وثبات الجلسة والترويسات المرسلة |
أسئلة شائعة
كم يجب أن تكون المهلة داخل سكربت Selenium؟
اضبطها على أساس نوع الاختبار: reCAPTCHA v2 يُنجز عادةً في أقل من 60 ثانية، وCloudflare Turnstile في أقل من 10 ثوانٍ. أما مهلة WebDriverWait فتخص ظهور العنصر داخل الصفحة، و10 ثوانٍ تكفي غالباً، بينما تُدار مهلة الحل داخل حلقة الاستطلاع.
كم عدد الـ threads الذي أحتاجه لتشغيل عدة متصفحات معاً؟
بعدد الجلسات المتزامنة فقط. خمس جلسات Selenium متوازية تحتاج 5 threads، وهو ما توفره خطة BASIC عند $15 شهرياً. أما مجموعات الاختبار الأكبر فتنتقل إلى STANDARD عند $30 شهرياً مع 15 thread، أو ADVANCE عند $90 شهرياً مع 50 thread.
هل ينتقل النمط نفسه إلى أدوات أتمتة أخرى؟
نعم. الجزء المرتبط بـ Selenium هو قراءة sitekey وحقن الرمز فقط، أما استدعاء API فمستقل تماماً عن أداة المتصفح. لذلك ينتقل المنطق نفسه إلى Puppeteer أو Playwright بتغيير سطري التنفيذ داخل الصفحة فحسب.
هل أحتاج إلى خادم وسيط لكل جلسة؟
ليس شرطاً في معظم الحالات. إذا كان تدفق الصفحة يربط جلسة التحقق بعنوان IP محدد، فاحرص على أن يخرج المتصفح والطلبات من العنوان نفسه حتى يبقى الاتساق قائماً؛ خلاف ذلك يعمل السكربت دون خادم وسيط دون مشكلة.