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

أتمتة إتمام الشراء متعدد الخطوات وحل اختبارات CAPTCHA

نادراً ما يعرض متجر التجارة الإلكترونية اختبار CAPTCHA واحداً فقط عند الدفع. غالباً يفرض اختباراً مختلفاً في كل خطوة: reCAPTCHA v3 خفياً عند الإضافة إلى السلة، وربما Cloudflare Turnstile عند إدخال عنوان الشحن، ثم reCAPTCHA v2 عند تأكيد الطلب. لأتمتة المسار كاملاً بثبات، تحتاج إلى نمط واحد يفحص كل صفحة ويحلّ ما يظهر عليها قبل الانتقال إلى الخطوة التالية. في هذا الدليل نبني هذا النمط خطوة بخطوة باستخدام Selenium وCaptchaAI في Python، ثم نغطّي الحالات الحرجة التي تكسر سيناريوهات الاختبار الآلي عادةً.


أين تظهر اختبارات CAPTCHA في مسار الشراء

قبل كتابة أي سطر، حدّد النقاط التي قد يظهر عندها اختبار. يختلف التوزيع من متجر لآخر، لكن هذه الخريطة تغطّي الحالات الأكثر شيوعاً في مسار شراء نموذجي:

مرحلة الشراء نوع CAPTCHA المتوقّع الغرض من الاختبار
الإضافة إلى السلة reCAPTCHA v3 (خفي) منع الروبوتات
تسجيل الدخول أو الشراء كضيف reCAPTCHA v2 التحقق من الحساب
إدخال عنوان الشحن Cloudflare Turnstile تحديد معدل الطلبات
صفحة الدفع reCAPTCHA v2/v3 منع الاحتيال
تأكيد الطلب reCAPTCHA v2 تحقق نهائي

تخيّل فريق ضمان جودة في متجر خليجي يجهّز حملة "الجمعة البيضاء": عليه التأكد من أن مسار الشراء كاملاً يعمل تحت ضغط، بما في ذلك كل اختبار CAPTCHA يظهر أثناء الذروة. تشغيل هذا الاختبار يدوياً عشرات المرات غير عملي، لذا تصبح أتمتة المسار مع حلّ آلي للاختبارات هي الطريقة الوحيدة للتحقق قبل إطلاق الحملة. المفتاح أن يكون الاختبار على متجر تملكه أو مصرّح لك باختباره، وفي بيئة sandbox حيثما أمكن.


كيف يبدو سير العمل

الفكرة الأساسية بسيطة: نتنقّل بين خطوات الشراء بالترتيب، وقبل كل إجراء نستدعي دالة واحدة تفحص الصفحة وتحلّ أي اختبار موجود عليها. يمرّ المسار بخمس مراحل، لكل منها نقطة فحص خاصة:

  1. السلة — الإضافة إلى عربة التسوق.
  2. الشحن — إدخال عنوان التسليم.
  3. الدفع — بيانات البطاقة.
  4. المراجعة — تدقيق الطلب قبل الإرسال.
  5. التأكيد — إتمام الشراء نهائياً.

المخطط التالي يوضّح هذه المراحل ونقاط الفحص المرافقة لكل منها:

┌────────┐    ┌───────────┐    ┌──────────┐    ┌─────────┐    ┌──────────┐
│  Cart  │───▶│  Shipping │───▶│ Payment  │───▶│ Review  │───▶│ Confirm  │
│        │    │  Address  │    │  Info    │    │  Order  │    │          │
└────────┘    └───────────┘    └──────────┘    └─────────┘    └──────────┘
     │              │               │               │              │
     ▼              ▼               ▼               ▼              ▼
  [CAPTCHA?]    [CAPTCHA?]     [CAPTCHA?]      [CAPTCHA?]     [CAPTCHA?]

بناء المُشغّل الأساسي

نفصل المسؤوليات إلى فئتين: CaptchaSolver مسؤولة فقط عن إرسال الاختبار إلى CaptchaAI واستطلاع النتيجة عبر نقطتي النهاية in.php وres.php، وCheckoutAutomator تدير المتصفح وخطوات الشراء. هذا الفصل يجعل منطق الحل قابلاً لإعادة الاستخدام في أي مسار آخر لاحقاً، لا في الشراء فقط.

الدالة المحورية هي _solve_if_captcha، وتعمل وفق التسلسل التالي:

  • تقرأ مصدر الصفحة الحالي وعنوانها.
  • تبحث أولاً عن Turnstile عبر المحدّد .cf-turnstile، فإن وجدته أرسلت sitekey وعنوان الصفحة إلى الخدمة وحقنت الرمز في الحقل cf-turnstile-response.
  • وإلا بحثت عن reCAPTCHA، وميّزت الإصدار الخفي v3 بوجود grecaptcha.execute عن الإصدار v2، ثم حقنت الرمز في g-recaptcha-response.
  • إن لم يظهر أي اختبار، تُعيد None وتتابع الخطوة بلا تأخير.
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class CaptchaSolver:
    BASE = "https://ocr.captchaai.com"

    def __init__(self, api_key):
        self.api_key = api_key

    def solve(self, params, initial_wait=10):
        params["key"] = self.api_key
        params["json"] = 1
        resp = requests.post(f"{self.BASE}/in.php", data=params).json()
        if resp["status"] != 1:
            raise Exception(resp["request"])
        task_id = resp["request"]
        time.sleep(initial_wait)
        for _ in range(60):
            result = requests.get(
                f"{self.BASE}/res.php",
                params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
            ).json()
            if result["request"] == "CAPCHA_NOT_READY":
                time.sleep(5)
                continue
            if result["status"] == 1:
                return result["request"]
            raise Exception(result["request"])
        raise TimeoutError("Timed out")

class CheckoutAutomator:
    def __init__(self, api_key):
        self.solver = CaptchaSolver(api_key)
        self.driver = webdriver.Chrome()
        self.wait = WebDriverWait(self.driver, 15)

    def _find(self, selector):
        return self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))

    def _click(self, selector):
        self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector))).click()

    def _fill(self, selector, value):
        el = self._find(selector)
        el.clear()
        el.send_keys(value)

    def _solve_if_captcha(self):
        """Check for and solve any CAPTCHA on the current page."""
        html = self.driver.page_source
        page_url = self.driver.current_url

        # Turnstile
        turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
        if turnstile:
            sitekey = turnstile[0].get_attribute("data-sitekey")
            token = self.solver.solve({
                "method": "turnstile",
                "sitekey": sitekey,
                "pageurl": page_url,
            })
            self.driver.execute_script(
                f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
            )
            return "turnstile"

        # reCAPTCHA
        recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
        if recaptcha and "recaptcha" in html.lower():
            sitekey = recaptcha[0].get_attribute("data-sitekey")

            # Check if v3 (invisible)
            if "grecaptcha.execute" in html:
                token = self.solver.solve({
                    "method": "userrecaptcha",
                    "googlekey": sitekey,
                    "pageurl": page_url,
                    "version": "v3",
                    "action": "checkout",
                }, initial_wait=20)
            else:
                token = self.solver.solve({
                    "method": "userrecaptcha",
                    "googlekey": sitekey,
                    "pageurl": page_url,
                })

            self.driver.execute_script(
                f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
            )
            return "recaptcha"

        return None

    # --- Checkout Steps ---

    def add_to_cart(self, product_url, quantity=1):
        """Step 1: Navigate to product and add to cart."""
        self.driver.get(product_url)
        time.sleep(2)

        # Set quantity if field exists
        qty_fields = self.driver.find_elements(By.CSS_SELECTOR, "input[name='quantity']")
        if qty_fields:
            qty_fields[0].clear()
            qty_fields[0].send_keys(str(quantity))

        self._solve_if_captcha()
        self._click("[data-action='add-to-cart'], .add-to-cart, #add-to-cart")
        time.sleep(2)
        return True

    def fill_shipping(self, address):
        """Step 2: Fill shipping address."""
        self._solve_if_captcha()

        field_map = {
            "first_name": "#shipping-first-name, [name='firstName']",
            "last_name": "#shipping-last-name, [name='lastName']",
            "address": "#shipping-address, [name='address1']",
            "city": "#shipping-city, [name='city']",
            "state": "#shipping-state, [name='state']",
            "zip": "#shipping-zip, [name='postalCode']",
            "phone": "#shipping-phone, [name='phone']",
        }

        for field, selectors in field_map.items():
            if field in address:
                for selector in selectors.split(", "):
                    elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
                    if elements:
                        elements[0].clear()
                        elements[0].send_keys(address[field])
                        break

        self._solve_if_captcha()
        self._click("[data-step='shipping-submit'], .continue-to-payment")
        time.sleep(2)
        return True

    def fill_payment(self, payment):
        """Step 3: Fill payment information."""
        self._solve_if_captcha()

        # Handle iframe for card fields (common pattern)
        iframes = self.driver.find_elements(By.CSS_SELECTOR, "iframe[name*='card']")
        if iframes:
            self.driver.switch_to.frame(iframes[0])
            self._fill("input[name='cardnumber']", payment["card_number"])
            self.driver.switch_to.default_content()

            if len(iframes) > 1:
                self.driver.switch_to.frame(iframes[1])
                self._fill("input[name='exp-date']", payment["expiry"])
                self.driver.switch_to.default_content()

            if len(iframes) > 2:
                self.driver.switch_to.frame(iframes[2])
                self._fill("input[name='cvc']", payment["cvv"])
                self.driver.switch_to.default_content()
        else:
            # Direct fields
            self._fill("[name='cardNumber'], #card-number", payment["card_number"])
            self._fill("[name='expiry'], #card-expiry", payment["expiry"])
            self._fill("[name='cvv'], #card-cvv", payment["cvv"])

        self._solve_if_captcha()
        self._click("[data-step='payment-submit'], .continue-to-review")
        time.sleep(2)
        return True

    def confirm_order(self):
        """Step 4: Review and confirm order."""
        self._solve_if_captcha()
        self._click("[data-step='confirm'], .place-order, #place-order")
        time.sleep(3)

        # Check for confirmation
        html = self.driver.page_source.lower()
        if "order confirmed" in html or "thank you" in html or "confirmation" in html:
            return True
        return False

    def run_checkout(self, product_url, address, payment, quantity=1):
        """Run complete checkout flow."""
        steps = [
            ("Add to cart", lambda: self.add_to_cart(product_url, quantity)),
            ("Proceed to checkout", lambda: self._click(".checkout-btn, a[href*='checkout']")),
            ("Fill shipping", lambda: self.fill_shipping(address)),
            ("Fill payment", lambda: self.fill_payment(payment)),
            ("Confirm order", lambda: self.confirm_order()),
        ]

        for step_name, step_fn in steps:
            print(f"Step: {step_name}...")
            try:
                result = step_fn()
                print(f"  ✓ {step_name} complete")
            except Exception as e:
                print(f"  ✗ {step_name} failed: {e}")
                return False
        return True

    def close(self):
        self.driver.quit()

تشغيل المسار كاملاً

تتبع دوال الخطوات التسلسل الطبيعي للشراء:

  1. add_to_cart — الإضافة إلى السلة.
  2. الانتقال إلى صفحة الدفع.
  3. fill_shipping — عنوان الشحن.
  4. fill_payment — بيانات البطاقة.
  5. confirm_order — تأكيد الطلب.

كل دالة تستدعي _solve_if_captcha قبل الإجراء وبعد ملء الحقول، لأن بعض المتاجر تُظهر الاختبار فقط بعد أن يبدأ المستخدم الفعلي بالتفاعل مع النموذج. أما run_checkout فتجمع هذه الخطوات في قائمة مرتّبة وتطبع حالة كل واحدة، ما يسهّل تحديد أين يتوقف المسار عند الفشل.

لتشغيل السيناريو كاملاً، مرّر رابط المنتج وعنوان الشحن وبيانات بطاقة الاختبار. استخدم دائماً أرقام بطاقات وضع الاختبار من منصتك، لا بيانات حقيقية:

automator = CheckoutAutomator("YOUR_API_KEY")

try:
    success = automator.run_checkout(
        product_url="https://store.example.com/product/widget-pro",
        address={
            "first_name": "Jane",
            "last_name": "Smith",
            "address": "123 Test Street",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94102",
            "phone": "415-555-0100",
        },
        payment={
            "card_number": "4111111111111111",
            "expiry": "12/26",
            "cvv": "123",
        },
        quantity=1,
    )
    print(f"Checkout {'succeeded' if success else 'failed'}")
finally:
    automator.close()

التعامل مع الحالات الحرجة

المسار المثالي نادر في الواقع. ثلاث حالات هي الأكثر تسبباً في فشل الاختبارات الآلية أثناء الشراء:

  • ظهور اختبار CAPTCHA بعد رسالة خطأ في التحقق.
  • اختبارات مرتبطة بالجلسة تظهر مرة واحدة فقط.
  • حقول تُحمّل ديناميكياً بعد اكتمال تحميل الصفحة.

لكل حالة معالجة واضحة نستعرضها فيما يلي.

ظهور اختبار CAPTCHA بعد رسالة خطأ في التحقق

كثير من المتاجر لا تعرض اختباراً في المحاولة الأولى، لكنها تُظهره فور فشل تحقق ما (بريد غير صالح، أو رمز خصم منتهٍ). غلّف الخطوة بمنطق إعادة محاولة يستدعي الحل ثم يكرّر الإجراء:

def retry_step_with_captcha(self, step_fn, max_retries=2):
    for attempt in range(max_retries + 1):
        try:
            return step_fn()
        except Exception:
            if attempt < max_retries:
                self._solve_if_captcha()
                continue
            raise

اختبارات مرتبطة بالجلسة

تعرض بعض المواقع الاختبار عند الطلب الأول في الجلسة فقط، ثم تثق ببقية الطلبات. للتعامل مع ذلك:

  • حافظ على ملفات تعريف الارتباط (cookies) الخاصة بالمتصفح عبر كل خطوات المسار.
  • أبقِ جلسة واحدة متصلة من السلة حتى التأكيد بدلاً من فتح جلسة لكل خطوة.
  • بذلك يقلّ عدد الاختبارات التي تحتاج إلى حلّها، ويقترب سلوك الأتمتة من سلوك مستخدم حقيقي.

الحقول التي تُحمّل ديناميكياً

تُبنى صفحات الشراء الحديثة عبر JavaScript، وقد لا تكون الحقول جاهزة لحظة تحميل الصفحة. انتظر ظهور عنصر دليلي يؤكّد اكتمال الخطوة قبل التعبئة، بدلاً من الاعتماد على مهل ثابتة:

def wait_for_step(self, indicator_selector, timeout=15):
    """Wait for a step to fully load before proceeding."""
    WebDriverWait(self.driver, timeout).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, indicator_selector))
    )

حل المشكلات الشائعة

عند تعثّر المسار، ابدأ من هذا الجدول قبل الغوص في السجلّات:

العرض السبب المحتمل الحل
رُفض رمز CAPTCHA عند التأكيد انتهت صلاحية الرمز قبل الإرسال احلل الاختبار مباشرة قبل الضغط على زر الإرسال
حقول البطاقة لا تُملأ الحقول داخل iframe (مثل Stripe أو Braintree) انتقل إلى الـ iframe قبل التعبئة
المسار يعيد التوجيه إلى تسجيل الدخول انتهت صلاحية الجلسة أضف خطوة تسجيل دخول قبل بدء الشراء
فشل الطلب دون رسالة واضحة حقول مطلوبة غير مكتملة افحص رسائل الخطأ بعد كل خطوة

أسئلة شائعة

لماذا يُرفض الرمز أحياناً رغم حلّه بنجاح؟

رموز reCAPTCHA وTurnstile قصيرة العمر. إن حللت الاختبار مبكراً ثم ملأت حقولاً كثيرة قبل الإرسال، قد تنتهي صلاحية الرمز. احرص على استدعاء الحل في آخر لحظة قبل النقر على زر الإرسال في كل خطوة.

هل أستطيع تشغيل أكثر من مسار شراء في وقت واحد؟

نعم. يعتمد تسعير CaptchaAI على عدد الـ threads المتزامنة لا على عدد عمليات الحل، وكل خطة تشمل حلولاً غير محدودة لكل thread. لتشغيل عدة اختبارات متوازية، اختر خطة توفّر خيوطاً كافية، مثل STANDARD ($30 شهرياً، 15 thread) لفرق الاختبار الصغيرة.

كم يكلّف حلّ الاختبارات أثناء اختبار متجر كامل؟

تدفع اشتراكاً شهرياً ثابتاً حسب عدد الخيوط، لا رسماً لكل اختبار. تبدأ الخطط من BASIC ($15 شهرياً، 5 threads) مع حلول غير محدودة، ما يجعل تكلفة تشغيل دورة اختبار كاملة قابلة للتنبؤ بها بغضّ النظر عن عدد الاختبارات التي تظهر.

ماذا لو ظهر hCaptcha في صفحة الدفع؟

لا يدعم CaptchaAI حالياً حلّ hCaptcha أو FunCaptcha. لكنه يغطّي أنواع الاختبارات الأكثر شيوعاً في مسارات الشراء — reCAPTCHA v2 وv3 وCloudflare Turnstile وChallenge وGeeTest v3 والاختبارات الصورية — وهي التي ستقابلها في معظم متاجر التجارة الإلكترونية.


أدلة ذات صلة


أتمتة اختبار الشراء — تعامل مع اختبارات CAPTCHA عبر CaptchaAI.

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