عندما يعترض اختبار CAPTCHA سكربت الاستخراج الخاص بك، لا تحتاج إلى متصفح كامل في أغلب الحالات — تحتاج فقط إلى إرسال الاختبار إلى خدمة حل خارجية واستقبال الرمز الناتج ثم إرفاقه مع النموذج. هذا الدليل يبني هذا المسار بالكامل في Python باستخدام مكتبة requests وBeautifulSoup وواجهة CaptchaAI، بحيث تبقى نصوصك البرمجية خفيفة وسريعة بدل تحميل صفحة كاملة عبر Selenium.
الفكرة الأساسية بسيطة: مكتبة requests تتولى HTTP بكفاءة، لكنها لا تعرف كيف تحل reCAPTCHA أو اختبار صورة، لذا نمرّر هذه المهمة إلى CaptchaAI ونستعيد النتيجة عبر أربع خطوات — أرسل الاختبار، احفظ معرّف المهمة، استطلع النتيجة، ثم استخدم الرمز.
متى يكفي requests ومتى تحتاج متصفحاً؟
قبل كتابة أي كود، احسم هذا القرار لأنه يحدد بنية السكربت بالكامل:
- استخدم
requests+ CaptchaAI عندما يعمل نموذج الموقع مع طلبات HTTP POST قياسية، ويكون مفتاح الموقع (sitekey) ظاهراً في HTML. هذا المسار أسرع وأقل استهلاكاً للذاكرة. - انتقل إلى متصفح مؤتمت فقط عندما يعتمد الموقع على عرض JavaScript ثقيل، أو يولّد مفتاح الموقع ديناميكياً، أو يربط الجلسة ببصمة المتصفح.
في أغلب مواقع النماذج ومحركات البحث الداخلية، يكفي المسار الأول تماماً.
ملاحظة مهمة: اقصر الاستخراج على البيانات المعلنة علناً التي تملكها أو لديك تصريح صريح بجمعها، والتزم بملف robots.txt وشروط استخدام كل موقع.
المتطلبات
| المتطلب | التفاصيل |
|---|---|
| Python 3.7+ | مع مدير الحزم pip |
requests |
pip install requests |
beautifulsoup4 |
pip install beautifulsoup4 |
| مفتاح CaptchaAI API | من لوحة تحكم captchaai.com |
بناء فئة حلّ قابلة لإعادة الاستخدام
بدل تكرار منطق الإرسال والاستطلاع في كل سكربت، اجمعه في فئة واحدة تستدعيها من كل مشاريعك. الطريقة _submit ترسل الاختبار إلى in.php، والطريقة _poll تستطلع res.php كل خمس ثوانٍ حتى تجهز النتيجة أو تنتهي المهلة:
import requests
import time
class CaptchaSolver:
def __init__(self, api_key):
self.api_key = api_key
self.base = "https://ocr.captchaai.com"
def _submit(self, params):
params["key"] = self.api_key
resp = requests.get(f"{self.base}/in.php", params=params)
if not resp.text.startswith("OK|"):
raise Exception(f"Submit error: {resp.text}")
return resp.text.split("|")[1]
def _poll(self, task_id, timeout=300):
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(5)
resp = requests.get(f"{self.base}/res.php", params={
"key": self.api_key,
"action": "get",
"id": task_id
})
if resp.text == "CAPCHA_NOT_READY":
continue
if resp.text.startswith("OK|"):
return resp.text.split("|")[1]
raise Exception(f"Solve error: {resp.text}")
raise TimeoutError("Solve timed out")
def solve_recaptcha_v2(self, site_key, page_url):
task_id = self._submit({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url
})
return self._poll(task_id)
def solve_recaptcha_v3(self, site_key, page_url, action="verify"):
task_id = self._submit({
"method": "userrecaptcha",
"googlekey": site_key,
"pageurl": page_url,
"version": "v3",
"action": action
})
return self._poll(task_id)
def solve_turnstile(self, site_key, page_url):
task_id = self._submit({
"method": "turnstile",
"sitekey": site_key,
"pageurl": page_url
})
return self._poll(task_id)
def solve_image(self, image_base64):
task_id = self._submit({
"method": "base64",
"body": image_base64
})
return self._poll(task_id)
لاحظ أن الفئة تغطي reCAPTCHA v2 وv3 وCloudflare Turnstile واختبارات الصور من نقطة واحدة، وكلها ضمن أنواع CAPTCHA المدعومة رسمياً في CaptchaAI.
استخراج نموذج محمي بـ reCAPTCHA
الخطوات هنا مرتبة كما يجري السيناريو فعلياً: حمّل الصفحة، استخرج مفتاح الموقع من عنصر g-recaptcha، احصل على الرمز، ثم أرسل النموذج مع حقل g-recaptcha-response:
from bs4 import BeautifulSoup
import requests
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
# Step 1: Load the page
url = "https://example.com/search"
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")
# Step 2: Extract the site key
recaptcha_div = soup.find("div", class_="g-recaptcha")
site_key = recaptcha_div["data-sitekey"]
# Step 3: Solve the CAPTCHA
token = solver.solve_recaptcha_v2(site_key, url)
# Step 4: Submit the form with the token
form_data = {
"q": "search term",
"g-recaptcha-response": token
}
result = session.post(url, data=form_data)
# Step 5: Parse the results
result_soup = BeautifulSoup(result.text, "html.parser")
items = result_soup.find_all("div", class_="result-item")
for item in items:
print(item.text.strip())
استخدام requests.Session() مهم هنا: فهو يحافظ على ملفات تعريف الارتباط بين تحميل الصفحة وإرسال النموذج، وهو ما تتطلبه كثير من المواقع لقبول الرمز.
استخراج صفحات متعددة خلف CAPTCHA
للنتائج المرقّمة التي يحرس كل صفحة منها اختبار CAPTCHA، لُفّ منطق الحل داخل حلقة على أرقام الصفحات، مع تأخير مهذّب بين الطلبات لتخفيف الضغط على الخادم:
def scrape_all_pages(base_url, site_key, max_pages=10):
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
})
all_results = []
for page_num in range(1, max_pages + 1):
page_url = f"{base_url}?page={page_num}"
# Solve CAPTCHA for each page if needed
token = solver.solve_recaptcha_v2(site_key, page_url)
resp = session.get(page_url, params={
"g-recaptcha-response": token,
"page": page_num
})
soup = BeautifulSoup(resp.text, "html.parser")
items = soup.find_all("div", class_="item")
if not items:
break
all_results.extend([item.text.strip() for item in items])
print(f"Page {page_num}: {len(items)} items")
time.sleep(2) # Polite delay
return all_results
هنا تظهر قيمة نموذج التسعير القائم على الـ Threads في CaptchaAI: لأن الفوترة تكون على عدد المهام المتزامنة وليس على كل عملية حل، يمكنك تشغيل عدة صفحات بالتوازي ضمن رصيد خطة واحدة دون رسوم إضافية لكل اختبار.
التعامل مع اختبارات CAPTCHA الصورية
المواقع التي تعرض CAPTCHA نصياً على شكل صورة تحتاج خطوة إضافية: نزّل الصورة، حوّلها إلى Base64، ثم أرسلها إلى الخدمة عبر طريقة solve_image:
import base64
def scrape_with_image_captcha(url):
solver = CaptchaSolver("YOUR_API_KEY")
session = requests.Session()
page = session.get(url)
soup = BeautifulSoup(page.text, "html.parser")
# Find the CAPTCHA image
captcha_img = soup.find("img", {"id": "captcha-image"})
captcha_url = captcha_img["src"]
# Download and encode the image
img_resp = session.get(captcha_url)
img_base64 = base64.b64encode(img_resp.content).decode()
# Solve
captcha_text = solver.solve_image(img_base64)
# Submit
form_data = {
"captcha": captcha_text,
"username": "user"
}
result = session.post(url, data=form_data)
return result.text
إضافة منطق إعادة المحاولة للإنتاج
في البيئات الفعلية ستواجه أحياناً انقطاعات شبكة أو مهلة استجابة. غلّف نداء الحل بحلقة إعادة محاولة قصيرة بدل ترك السكربت يتوقف عند أول خطأ:
def solve_with_retry(solver, site_key, page_url, max_retries=3):
for attempt in range(max_retries):
try:
return solver.solve_recaptcha_v2(site_key, page_url)
except Exception as e:
if attempt == max_retries - 1:
raise
print(f"Attempt {attempt + 1} failed: {e}. Retrying...")
time.sleep(2)
سيناريو تطبيقي من السوق العربي
لنفترض أنك مهندس بيانات في متجر إلكتروني في الرياض أو دبي، ومهمتك جمع قوائم الأسعار المعلنة علناً من واجهات المتاجر التي يملكها فريقك أو التي لديك تصريح باستخراجها لأغراض مراقبة الأسعار الداخلية. بعض هذه الواجهات تحرس صفحة البحث بـ reCAPTCHA v3 لتقليل الطلبات الآلية. باستخدام الفئة أعلاه، تشغّل عملية استطلاع دورية كل صباح تجمع الأسعار عبر عشرات الصفحات، فيحل CaptchaAI الاختبار في كل صفحة وتخزّن النتائج في قاعدة بياناتك.
من ناحية التكلفة، اختر الخطة على أساس مقدار التوازي الذي تحتاجه، لا عدد عمليات الحل، لأن الفوترة تكون على الـ Threads:
- خطة BASIC ($15 شهرياً، 5 Threads) — تكفي لمهمة استطلاع صغيرة تعمل بشكل متسلسل.
- خطة STANDARD ($30 شهرياً، 15 Thread) — توازٍ أوسع عندما ينمو عدد الصفحات المستهدفة.
كل الخطط تشمل عدداً غير محدود من عمليات الحل ضمن الشهر، لذا ابدأ صغيراً وارفع عدد الـ Threads مع نمو الحجم بدل الدفع عن كل عملية على حدة.
أفضل الممارسات لاستخراج مستقر
قبل تحويل السكربت إلى مهمة تعمل يومياً، ثبّت هذه العادات التي تفصل بين كاشط هشّ يتعطّل بعد ساعة وآخر يعمل بلا تدخّل:
- استخدم الرمز فور استلامه؛ رموز reCAPTCHA قصيرة العمر ولا تصلح للتخزين.
- حافظ على
requests.Session()واحدة طوال دورة تحميل الصفحة وإرسال النموذج للحفاظ على ملفات تعريف الارتباط. - أضف تأخيراً متدرّجاً بين الطلبات، وناوب الخوادم الوسيطة إذا زاد الحجم.
- سجّل معرّف كل مهمة والاستجابة النهائية لتسهيل استكشاف الأخطاء لاحقاً.
نصيحة: راقب رصيدك في لوحة التحكم أثناء المهام الكبيرة؛ توقف مفاجئ بسبب
ERROR_ZERO_BALANCEأصعب في التتبّع من خطأ برمجي واضح.
استكشاف الأخطاء وإصلاحها
| المشكلة | السبب | الإجراء |
|---|---|---|
ERROR_WRONG_USER_KEY |
مفتاح API غير صالح | تحقّق من المفتاح في لوحة التحكم |
ERROR_ZERO_BALANCE |
لا يوجد رصيد | اشحن رصيد حسابك |
| إرسال النموذج يعيد صفحة CAPTCHA مجدداً | انتهت صلاحية الرمز أو اسم الحقل خاطئ | استخدم الرمز فوراً وتأكد من أسماء حقول النموذج |
ConnectionError |
مشكلة في الشبكة | أضف منطق إعادة المحاولة مع التراجع الأسي |
| نتائج فارغة بعد الإرسال | الموقع يتطلب ملفات تعريف الارتباط/الجلسة | استخدم requests.Session() للحفاظ على الجلسة |
الأسئلة الشائعة
هل يقبل الموقع الرمز إذا أرسلته من عنوان IP مختلف عن المتصفح؟
في الغالب نعم مع reCAPTCHA، لأن التحقق يتم عبر مفتاح الموقع وليس عنوان IP الخاص بخدمة الحل. لكن بعض المواقع تربط الجلسة بعنوان IP، لذا حافظ على استخدام requests.Session() نفسها وأرسل النموذج مباشرة بعد استلام الرمز.
كم من الوقت يبقى الرمز صالحاً بعد الحل؟
رمز reCAPTCHA قصير العمر — عادة نحو دقيقتين. القاعدة العملية: أرسل النموذج فور استلام الرمز ولا تخزّنه لاستخدام لاحق، وإلا ستعود صفحة الاختبار.
هل أستطيع تشغيل عدة عمليات حل بالتوازي؟
نعم. لأن CaptchaAI يفوتر على أساس الـ Threads المتزامنة، يمكنك إطلاق عدة مهام في الوقت نفسه بقدر ما تسمح به خطتك، مع استطلاع كل مهمة بمعرّفها. استخدم aiohttp لسير عمل غير متزامن كامل — راجع تكامل aiohttp مع CaptchaAI.
كيف أخفّف من تحديد معدل الطلبات أثناء الاستخراج؟
أضف تأخيرات واقعية بين الطلبات (time.sleep(2-5))، وناوب بين الخوادم الوسيطة، واستخدم رؤوس طلب طبيعية. للتفاصيل راجع تناوب البروكسي في استخراج البيانات.
هل يدعم CaptchaAI اختبار reCAPTCHA v3 المعتمد على النقاط؟
نعم، فالطريقة solve_recaptcha_v3 في الفئة أعلاه تمرّر معامِل action وversion المطلوبين لـ reCAPTCHA v3. تذكّر أن هذا النوع يعتمد على درجة سلوكية، لذا يبقى إرسال الرمز بسرعة وضمن جلسة متسقة عاملاً مؤثراً في القبول.