حالات الاستخدام

جمع بيانات موقع البيع بالتجزئة من خلال معالجة اختبار CAPTCHA

تتوقف معظم عمليات مراقبة الأسعار عند النقطة نفسها: صفحة تعرض صورة نصية مشوّهة وتطلب كتابة الأحرف قبل إظهار بيانات المنتج. معالجة هذه الحالة أربع حركات فقط: اكتشف صفحة التحقق قبل تحليل الصفحة، نزّل الصورة داخل الجلسة نفسها، أرسلها إلى 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 تبقى العامل الأقوى في مواقع التجزئة. عند الزحف المستمر من مركز بيانات، يقلّل البروكسي السكني تكرار صور التحقق أكثر بكثير من مجرد إضافة تأخير.

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.