تتوقف معظم عمليات مراقبة الأسعار عند النقطة نفسها: صفحة تعرض صورة نصية مشوّهة وتطلب كتابة الأحرف قبل إظهار بيانات المنتج. معالجة هذه الحالة أربع حركات فقط: اكتشف صفحة التحقق قبل تحليل الصفحة، نزّل الصورة داخل الجلسة نفسها، أرسلها إلى CaptchaAI بصيغة base64، ثم أعد إرسال النموذج بالنص الناتج. يجمع هذا الدليل الحركات الأربع في ثلاث خطوات كود Python قابلة للتشغيل، ثم ينتقل إلى الجزء الذي يوفّر الوقت فعلياً: ضبط إيقاع الزحف حتى لا تظهر الصورة إلا نادراً.
اختبار التجزئة هنا من عائلة الصور/OCR، وليس reCAPTCHA أو Turnstile، ولهذا يجري الحل عبر إرسال بايتات الصورة نفسها إلى نقطة النهاية in.php ثم استطلاع النتيجة من res.php.
سيناريو من السوق الإقليمي: مراقبة أسعار يومية
فريق تجارة إلكترونية في الرياض يتابع 400 صفحة منتج يومياً عبر متاجر إقليمية ودولية، ويعمل من خادم واحد داخل مركز بيانات أوروبي. في المواسم التي تتغيّر فيها الأسعار أكثر من مرة في اليوم — الجمعة البيضاء، عروض رمضان، مواسم العودة إلى المدارس — يرفع الفريق وتيرة الزحف، فتبدأ صور التحقق بالظهور بعد عشرات الطلبات الأولى.
المعالجة هنا شقّان: توزيع 400 طلب على مدار اليوم بحيث يبقى المعدل أقل من طلب واحد في الدقيقة، وربط الزاحف بخدمة حل تعمل تلقائياً عند ظهور الصورة. الفوترة في CaptchaAI قائمة على عدد الـ threads المتزامنة مع عدد حلول غير محدود داخل الشهر، لذا فالرقم الحاسم ليس عدد الصفحات بل كم عملية حل تجري في اللحظة نفسها. خطة BASIC — $15 شهرياً مع 5 threads — تكفي حجماً كهذا بمساحة واسعة، بينما تصبح ADVANCE — $90 شهرياً مع 50 thread — منطقية عند مراقبة عدة أسواق بالتوازي في نافذة زمنية ضيقة.
متى يظهر اختبار CAPTCHA أمام زاحف التجزئة
المتاجر الكبرى لا تعرض صورة التحقق عشوائياً، بل عند اجتماع إشارات تجعل الزيارة تبدو آلية:
| الإشارة | ما الذي يلاحظه المتجر |
|---|---|
| كثافة الطلبات | عدد كبير من الطلبات من عنوان IP واحد داخل نافذة زمنية قصيرة |
| غياب ملفات تعريف الارتباط | جلسة بلا كوكيز سابقة، فكل طلب يبدو كأنه أول زيارة |
| ترويسات غير واقعية | User-Agent يشبه أدوات الأتمتة أو ترويسات ناقصة |
| سمعة عنوان IP | نطاقات معروفة لمراكز البيانات أو لخدمات بروكسي عامة |
عند تحقق الشرط، يُعاد توجيه الطلب إلى صفحة تحتوي صورة نصية مشوّهة وحقل إدخال ونموذج إرسال. لا تُعرض بيانات المنتج قبل وصول النص الصحيح، لذا يُعامل هذا المسار كخطوة عادية داخل سير العمل لا كخطأ يوقف الزاحف.
ما تحتاجه قبل البدء
- مفتاح CaptchaAI API — أنشئ حساباً على captchaai.com وانسخ المفتاح من لوحة التحكم.
- Python 3.7 أو أحدث — مع مكتبتَي requests و beautifulsoup4.
- بروكسي سكني — مستحسن لأي عملية جمع بيانات مستمرة تنطلق من مركز بيانات.
اجعل مفتاح الـ API في متغيّر بيئة لا في ملف الكود، وخصّص خيطاً واحداً للتجربة قبل رفع التزامن.
المسار التشغيلي في ثلاث خطوات
الخطوة 1: اكتشف صفحة التحقق قبل تحليل البيانات
أول خطأ شائع هو تمرير صفحة التحقق إلى محلّل البيانات، فيعود الناتج فارغاً بلا سبب واضح. افحص الاستجابة أولاً، واستخدم جلسة requests.Session واحدة تحتفظ بملفات تعريف الارتباط عبر الطلبات:
import requests
from bs4 import BeautifulSoup
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
})
def is_captcha_page(html):
return "Type the characters you see in this image" in html or \
"captcha" in html.lower()
url = "https://www.amazon.com/dp/B0EXAMPLE"
resp = session.get(url)
if is_captcha_page(resp.text):
print("CAPTCHA detected!")
else:
print("Page loaded successfully")
الخطوة 2: نزّل الصورة وأرسلها إلى CaptchaAI
صورة التحقق مرتبطة بالجلسة، لذا يجب تنزيلها بالجلسة نفسها التي استقبلت الصفحة، ثم ترميزها بصيغة base64 وإرسالها إلى in.php مع المعامل method=base64. يعيد الطلب معرّف المهمة، وبعده يبدأ الاستطلاع الدوري لنقطة res.php حتى تتحول الاستجابة من CAPCHA_NOT_READY إلى النص المحلول:
import base64
API_KEY = "YOUR_API_KEY"
def solve_amazon_captcha(session, captcha_page_html, captcha_page_url):
soup = BeautifulSoup(captcha_page_html, "html.parser")
# Find the CAPTCHA image
img_tag = soup.find("img", src=lambda s: s and "captcha" in s.lower())
if not img_tag:
raise Exception("CAPTCHA image not found")
img_url = img_tag["src"]
# Download the image
img_resp = session.get(img_url)
img_base64 = base64.b64encode(img_resp.content).decode()
# Submit to CaptchaAI
submit_resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "base64",
"body": img_base64
})
task_id = submit_resp.text.split("|")[1]
# Poll for result
import time
for _ in range(30):
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 error: {result.text}")
raise TimeoutError("Solve timed out")
المهلة في المثال أعلاه 30 محاولة بفاصل 5 ثوانٍ. اجعلها مهلة انتهاء واضحة بدل الانتظار المفتوح، وسجّل كل استجابة خطأ لتفرّق لاحقاً بين خطأ في تنزيل الصورة وخطأ في الرصيد أو المفتاح.
الخطوة 3: أعد إرسال النموذج بالنص الناتج
النص المحلول وحده لا يكفي؛ يحتاج المتجر إلى النموذج كاملاً بحقوله المخفية كما وصلت. اجمع كل حقول input من الصفحة، ضع الحل في الحقل المخصص، ثم أرسل النموذج إلى مساره الصحيح بعد تحويل المسار النسبي إلى رابط مطلق:
def submit_captcha_solution(session, captcha_page_html, solution, captcha_page_url):
soup = BeautifulSoup(captcha_page_html, "html.parser")
form = soup.find("form")
# Build form data
form_data = {}
for inp in form.find_all("input"):
name = inp.get("name")
if name:
form_data[name] = inp.get("value", "")
# Set the CAPTCHA answer
form_data["field-keywords"] = solution
# Submit
action = form.get("action", captcha_page_url)
if action.startswith("/"):
from urllib.parse import urljoin
action = urljoin(captcha_page_url, action)
resp = session.post(action, data=form_data)
return resp
مثال متكامل: من رابط المنتج إلى العنوان والسعر
يجمع المثال التالي الخطوات الثلاث في دالة واحدة: تطلب الصفحة، وتعالج التحقق إن ظهر، ثم تستخرج عنوان المنتج وسعره من الاستجابة النهائية.
import requests
import base64
import time
from bs4 import BeautifulSoup
API_KEY = "YOUR_API_KEY"
def scrape_amazon_product(url):
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept-Language": "en-US,en;q=0.9"
})
resp = session.get(url)
# Handle CAPTCHA if present
if "captcha" in resp.text.lower():
soup = BeautifulSoup(resp.text, "html.parser")
img = soup.find("img", src=lambda s: s and "captcha" in s.lower())
if img:
# Download and solve
img_data = session.get(img["src"]).content
img_b64 = base64.b64encode(img_data).decode()
submit = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "base64", "body": img_b64
})
task_id = submit.text.split("|")[1]
for _ in range(30):
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|"):
solution = result.text.split("|")[1]
break
# Submit solution
form = soup.find("form")
form_data = {inp.get("name"): inp.get("value", "")
for inp in form.find_all("input") if inp.get("name")}
form_data["field-keywords"] = solution
action = form.get("action", url)
resp = session.post(action, data=form_data)
# Parse product data
soup = BeautifulSoup(resp.text, "html.parser")
title = soup.find("span", {"id": "productTitle"})
price = soup.find("span", class_="a-price-whole")
return {
"title": title.text.strip() if title else None,
"price": price.text.strip() if price else None
}
product = scrape_amazon_product("https://www.amazon.com/dp/B0EXAMPLE")
print(product)
هذا الهيكل قابل للتوسيع مباشرة: ضع الدالة داخل قائمة انتظار، وامنح كل عامل خيطاً واحداً، واحفظ الناتج في مخزن يحتمل إعادة المحاولة دون تكرار الصفوف.
اضبط الإيقاع لتقليل ظهور CAPTCHA من الأساس
الحل التلقائي يعالج الحالة عند وقوعها، لكن ضبط سلوك الزاحف هو ما يجعلها نادرة:
- استخدم بروكسي سكنياً — عناوين مراكز البيانات تُحجب بسرعة في مواقع التجزئة الكبرى.
- دوّر قيم User-Agent — اعتمد مجموعة من سلاسل المتصفحات الواقعية بدل قيمة ثابتة واحدة.
- حافظ على الجلسة — ملفات تعريف الارتباط المستمرة تجعل الطلبات المتتالية تبدو زيارة واحدة.
- أضف تأخيراً متغيّراً — من 3 إلى 10 ثوانٍ بين الطلبات، مع تفاوت عشوائي بسيط.
- أرسل ترويسات الإعدادات المحلية — قيمة
Accept-Languageمطابقة للسوق المستهدف تقلّل الاشتباه. - اقتصر على الصفحات العامة — صفحات المنتجات متاحة دون تسجيل دخول، ولا داعي لمسارات تتطلب حساباً.
أعطال متكررة وكيف تعالجها
- صورة تحقق مع كل طلب — انتقل إلى بروكسي سكني وخفّض معدل الطلبات.
- رفض النص المُرسل — تأكد من تنزيل الصورة كاملة داخل الجلسة، ثم أعد المحاولة بصورة جديدة.
- دورات إعادة توجيه — راجع التعامل مع ملفات تعريف الارتباط واضبط
allow_redirects=True. - بيانات منتج فارغة — المتجر قد يخدم تصميماً مختلفاً؛ راجع المحددات المستخدمة في التحليل.
الأسئلة الشائعة
وماذا لو استخدم متجر آخر hCaptcha أو FunCaptcha؟
هذان النوعان غير مدعومَين، وGeeTest v4 مدرج ضمن "قريباً" وليس متاحاً بعد. المدعوم فعلياً يشمل reCAPTCHA v2 وv3 بمتغيراتها، وCloudflare Turnstile وCloudflare Challenge، وGeeTest v3، واختبارات الصور والنصوص عبر OCR وتحديات الصور الشبكية وBLS، إضافة إلى CaptchaFox وFriendly Captcha وLemin في نسخة beta.
كم تكلّف مراقبة يومية لبضع مئات من صفحات المنتجات؟
التكلفة مرتبطة بعدد الحلول المتزامنة لا بعدد الصفحات. خطة BASIC — $15 شهرياً مع 5 threads — تغطي زحفاً موزعاً على اليوم بمساحة احتياطية، والانتقال إلى خطة أعلى يصبح مبرراً حين تعمل عدة أسواق في التوقيت نفسه.
لماذا نرسل الصورة بصيغة base64 بدل رابطها؟
لأن رابط الصورة مرتبط بالجلسة وبملفات تعريف الارتباط التي أنشأت الصفحة. تنزيل الصورة داخل الجلسة نفسها ثم ترميزها base64 يضمن وصول البايتات الصحيحة إلى الخدمة، بينما فتح الرابط من سياق آخر قد يعيد صورة مختلفة أو صفحة خطأ.
هل البروكسي السكني ضروري أم يكفي إبطاء الطلبات؟
إبطاء الطلبات وحده يساعد في الأحجام الصغيرة، لكن سمعة عنوان IP تبقى العامل الأقوى في مواقع التجزئة. عند الزحف المستمر من مركز بيانات، يقلّل البروكسي السكني تكرار صور التحقق أكثر بكثير من مجرد إضافة تأخير.