الدروس التطبيقية

حقن توكن GeeTest في أدوات أتمتة المتصفح

في معظم مشاريع الأتمتة لا يفشل التكامل عند حلّ GeeTest v3، بل بعده مباشرة: تحصل على الحل الصحيح لكن الموقع يرفض الطلب لأن القيم لم تصل إلى المكان الذي يقرأها منه. القاعدة بسيطة — بعد الحل تُرجع GeeTest ثلاث قيم (geetest_challenge وgeetest_validate وgeetest_seccode)، وعليك وضعها جميعاً حيث تتوقعها الصفحة ثم إرسال النموذج بالطريقة التي يستخدمها الموقع فعلاً. هذا الدليل يركّز على الجزء الأصعب: الحقن الصحيح داخل Playwright وPuppeteer وSelenium.

القيم الثلاث التي تُرجعها GeeTest v3

بعد إتمام الحل، تُعيد CaptchaAI نتيجة مكوّنة من ثلاثة حقول:

{
  "geetest_challenge": "a1b2c3d4e5...modified_challenge",
  "geetest_validate": "abc123def456_validate",
  "geetest_seccode": "abc123def456_validate|jordan"
}

لكل قيمة دور مختلف: يتتبّع geetest_challenge جلسة التحقق الحالية، ويُثبت geetest_validate أن التحدي حُلّ فعلاً، بينما يوفّر geetest_seccode بصمة تحقق تكشف أي تلاعب بالقيم. لهذا يجب حقن الثلاث معاً؛ فقدان أي واحدة منها يُبطل عملية التحقق بالكامل حتى لو كانت القيمتان الأخريان صحيحتين.

أين يتوقّع الموقع هذه القيم؟

قبل كتابة أي سطر، حدّد كيف يقرأ الموقع نتيجة GeeTest، لأن مكان الحقن يتغيّر تبعاً لذلك. عملياً ستقابل ثلاثة أنماط:

  • حقول نموذج مخفية: يضع الموقع القيم الثلاث في عناصر input مخفية داخل <form>، وتُرسل مع بقية بيانات النموذج عند الضغط على زر الإرسال.
  • رد نداء JavaScript: يعتمد الموقع على دالة رد نداء يستدعيها كائن GeeTest عند النجاح، فلا وجود لحقول مخفية بل منطق برمجي يلتقط النتيجة.
  • حمولة XHR/‏fetch: تُرسل الصفحة النتيجة ضمن طلب غير متزامن مباشرةً إلى نقطة النهاية، دون المرور بنموذج تقليدي.

افحص شيفرة الصفحة في أدوات المطوّر لتعرف أي نمط يخصّك؛ الخطوات التالية تغطّي الأنماط الثلاثة.

الخطوة 1: استخراج gt وchallenge ثم الحل عبر CaptchaAI

هذه الخطوة مشتركة بين جميع الأدوات. استخرج المعاملين gt وchallenge من الصفحة، ثم أرسلهما إلى CaptchaAI واستطلع النتيجة حتى تجهز:

import requests
import time

def solve_geetest(gt, challenge, page_url):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": "YOUR_API_KEY",
        "method": "geetest",
        "gt": gt,
        "challenge": challenge,
        "pageurl": page_url,
        "json": 1
    })
    task_id = resp.json()["request"]

    for _ in range(60):
        time.sleep(3)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": "YOUR_API_KEY",
            "action": "get",
            "id": task_id,
            "json": 1
        })
        data = result.json()
        if data["status"] == 1:
            return data["request"]  # Returns dict with three values
    raise TimeoutError("GeeTest solve timed out")

استبدل YOUR_API_KEY بمفتاح الـ API من لوحة التحكم. الدالة تُرجع قاموساً يحوي القيم الثلاث التي ستحقنها في الخطوات التالية.

الخطوة 2: الحقن في Playwright عبر Python

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

التقاط المعاملات

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()

    # Capture GeeTest registration response
    geetest_data = {}
    def capture_geetest(response):
        if "register" in response.url and response.status == 200:
            try:
                data = response.json()
                if "gt" in data and "challenge" in data:
                    geetest_data.update(data)
            except Exception:
                pass

    page.on("response", capture_geetest)
    page.goto("https://example.com/login")
    page.wait_for_selector(".geetest_holder")

    gt = geetest_data["gt"]
    challenge = geetest_data["challenge"]

الطريقة الأولى: تعبئة حقول النموذج المخفية

بعد الحصول على الحل، أنشئ حقول input المخفية إن لم تكن موجودة وعبّئها بالقيم الثلاث، ثم أرسل النموذج:

# Solve with CaptchaAI
solution = solve_geetest(gt, challenge, page.url)

# Method 1: Set hidden form fields
page.evaluate(f"""
    const fields = {{
        'geetest_challenge': '{solution["geetest_challenge"]}',
        'geetest_validate': '{solution["geetest_validate"]}',
        'geetest_seccode': '{solution["geetest_seccode"]}'
    }};
    for (const [name, value] of Object.entries(fields)) {{
        let input = document.querySelector(`input[name="${{name}}"]`);
        if (!input) {{
            input = document.createElement('input');
            input.type = 'hidden';
            input.name = name;
            document.querySelector('form').appendChild(input);
        }}
        input.value = value;
    }}
""")

# Submit the form
page.click("#submit-button")

الطريقة الثانية: استدعاء رد نداء GeeTest

تعتمد بعض المواقع على رد نداء JavaScript الخاص بـ GeeTest بدلاً من حقول النموذج. في هذه الحالة مرّر الحل عبر كائن التحقق نفسه وأطلق حدث النجاح:

page.evaluate(f"""
    // Find the GeeTest captcha object
    if (window.captchaObj) {{
        // Simulate a successful solve
        const result = {{
            geetest_challenge: '{solution["geetest_challenge"]}',
            geetest_validate: '{solution["geetest_validate"]}',
            geetest_seccode: '{solution["geetest_seccode"]}'
        }};

        // Override getValidate to return our solution
        window.captchaObj.getValidate = function() {{ return result; }};

        // Trigger the success callback
        const successEvent = new Event('geetest_success');
        document.dispatchEvent(successEvent);
    }}
""")

الخطوة 3: الحقن في Puppeteer عبر JavaScript

المنطق نفسه ينطبق في بيئة Node.js: التقط gt وchallenge من استجابة التسجيل، احصل على الحل، ثم احقن القيم الثلاث في حقول النموذج المخفية:

const puppeteer = require('puppeteer');

async function solveAndInject() {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();

  // Capture GeeTest params
  let gt, challenge;
  page.on('response', async (response) => {
    if (response.url().includes('register') && response.status() === 200) {
      try {
        const data = await response.json();
        if (data.gt && data.challenge) {
          gt = data.gt;
          challenge = data.challenge;
        }
      } catch (e) {}
    }
  });

  await page.goto('https://example.com/login');
  await page.waitForSelector('.geetest_holder');

  // Solve with CaptchaAI (implementation from earlier)
  const solution = await solveCaptcha(gt, challenge, page.url());

  // Inject the three values
  await page.evaluate((sol) => {
    // Set hidden inputs
    const form = document.querySelector('form');
    ['geetest_challenge', 'geetest_validate', 'geetest_seccode'].forEach(name => {
      let input = document.querySelector(`input[name="${name}"]`);
      if (!input) {
        input = document.createElement('input');
        input.type = 'hidden';
        input.name = name;
        form.appendChild(input);
      }
      input.value = sol[name];
    });
  }, solution);

  await page.click('#submit-button');
}

الخطوة 4: الحقن في Selenium عبر Python

في Selenium تُقرأ المعاملات غالباً من سمات data- على عناصر الصفحة، ثم يُنفَّذ الحقن عبر execute_script:

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

driver = webdriver.Chrome()
driver.get("https://example.com/login")

# Wait for GeeTest widget
WebDriverWait(driver, 15).until(
    EC.presence_of_element_located((By.CLASS_NAME, "geetest_holder"))
)

# Extract gt and challenge from the page
gt = driver.execute_script(
    "return document.querySelector('[data-gt]')?.dataset.gt"
)
challenge = driver.execute_script(
    "return document.querySelector('[data-challenge]')?.dataset.challenge"
)

# Solve with CaptchaAI
solution = solve_geetest(gt, challenge, driver.current_url)

# Inject via JavaScript
driver.execute_script(f"""
    var fields = {{
        'geetest_challenge': '{solution["geetest_challenge"]}',
        'geetest_validate': '{solution["geetest_validate"]}',
        'geetest_seccode': '{solution["geetest_seccode"]}'
    }};
    var form = document.querySelector('form');
    for (var name in fields) {{
        var input = document.querySelector('input[name="' + name + '"]');
        if (!input) {{
            input = document.createElement('input');
            input.type = 'hidden';
            input.name = name;
            form.appendChild(input);
        }}
        input.value = fields[name];
    }}
""")

driver.find_element(By.ID, "submit-button").click()

التعامل مع الإرسال عبر XHR

حين لا يمرّ الموقع عبر نموذج بل يرسل نتيجة GeeTest ضمن طلب XHR أو fetch، اعترض الطلب في Playwright وأضف القيم الثلاث إلى حمولته قبل متابعته:

# Playwright: Intercept the XHR and inject values
def handle_route(route):
    if "login" in route.request.url and route.request.method == "POST":
        # Modify the POST data to include our solution
        post_data = route.request.post_data
        # Add GeeTest values to the request
        route.continue_(post_data=modified_data)
    else:
        route.continue_()

page.route("**/api/login**", handle_route)

سيناريو عملي من السوق

تخيّل فريق ضمان جودة في متجر إلكتروني إقليمي يضع GeeTest v3 على صفحة تسجيل الدخول لحماية الحسابات. يحتاج الفريق إلى اختبار تلقائي ليلي (nightly regression) يتحقق من أن مسار الدخول يعمل بعد كل إصدار. هنا يظهر لماذا يهمّ الحقن الصحيح: لو التقط السكربت القيم لكنه أرسل النموذج قبل تعبئة الحقول المخفية، سيسجّل الاختبار فشلاً زائفاً كل ليلة رغم أن الحل نفسه سليم.

ولأن CaptchaAI يُسعّر حسب عدد الـ Threads المتزامنة لا حسب كل عملية حل، يستطيع الفريق رفع درجة التوازي في خط CI دون قلق من فاتورة لكل طلب؛ فخطة BASIC تبدأ من $15 شهرياً مع 5 Threads، وتتوسّع الخطط الأعلى مع زيادة الحاجة إلى تشغيل عدة اختبارات في الوقت نفسه. النصيحة العملية: افصل خطوة الحل عن خطوة الحقن في السكربت، وسجّل مخرجات كل مرحلة على حدة كي تعرف بسرعة أين انكسر المسار.

استكشاف الأخطاء الشائعة

المشكلة السبب المرجّح الإجراء
رسالة "GeeTest validation failed" إحدى القيم الثلاث لم تُحقَن تأكّد من حقن challenge وvalidate وseccode جميعاً قبل الإرسال
التحدي ينتهي قبل الحقن مرّ وقت طويل بين الاستخراج والحل استخرج القيم وحُلّها بتتابع سريع وأعد الالتقاط عند الحاجة
النموذج يُرسل بلا القيم أسماء الحقول أو محدّد النموذج خاطئ افحص النموذج الفعلي لتحديد الأسماء الصحيحة للحقول
رد النداء لا يعمل الموقع يستخدم اسم رد نداء مخصّص ابحث عن الاسم داخل خيارات initGeetest
القيم مُحقَنة لكن الأداة ما زالت ظاهرة حالة الأداة لم تُحدَّث أطلق رد نداء النجاح برمجياً بعد الحقن
ينجح محلياً ويفشل في CI حلقة مفقودة بين الحل والطلب النهائي تحقّق من المسار الكامل من أداة الحل حتى الطلب المرسل للموقع

الأسئلة الشائعة

هل قيم GeeTest صالحة لأكثر من إرسال واحد؟

لا تعامِلها كذلك. القيم مرتبطة بجلسة التحقق التي التقطتها منها، فأعد التقاط gt وchallenge وحُلّهما من جديد لكل محاولة تحميل للصفحة بدل إعادة استخدام حل قديم.

كيف أتعامل مع موقع يستخدم اسم رد نداء مخصّص؟

ابحث في شيفرة الصفحة عن استدعاء initGeetest واقرأ خيار رد النداء المُعرَّف فيه (مثل onSuccess)، ثم استدعِ هذا الاسم تحديداً بعد الحقن بدلاً من الاعتماد على حدث geetest_success العام.

أي طريقة أختار: حقول النموذج أم رد النداء؟

ابدأ بفحص الصفحة: إن رأيت حقول input مخفية باسم geetest_* فاستخدم تعبئة الحقول، وإن كان المنطق يعتمد على دالة رد نداء فاستدعِها مباشرةً. عند الشك جرّب تعبئة الحقول أولاً لأنها الأكثر شيوعاً.

هل يحلّ CaptchaAI الإصدار GeeTest v4؟

الدعم قيد الإعداد ولم يُطرح بعد؛ حالياً يغطّي CaptchaAI الإصدار GeeTest v3 بشكل كامل. تابع صفحة الأنواع المدعومة لمعرفة موعد توفّر v4 رسمياً.


الخطوات التالية

أدلة ذات صلة

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