مفتاح حلّ اختبار GeeTest v3 المنزلق ليس شريط التمرير نفسه، بل قيمتان نصّيتان تسبقانه: gt وchallenge. الأولى معرّف ثابت للموقع، والثانية رمز جلسة قصير العمر يتغيّر مع كل تحميل للصفحة وينتهي خلال ثوانٍ معدودة. من يلتقط هاتين القيمتين طازجتين ويمرّرهما إلى CaptchaAI قبل انتهاء صلاحية الـ challenge يحصل على حلّ صالح، ومن يتأخّر يحصل على رفض من الموقع. لهذا فإن أصعب جزء في العملية ليس الحل، بل توقيت الاستخراج. يشرح هذا الدليل ثلاث طرق عملية لالتقاط gt وchallenge من أي صفحة، ثم إرسالهما إلى CaptchaAI وحقن الاستجابة في نموذج الصفحة.
المعلمتان الأساسيتان: gt وchallenge
قبل أي سطر برمجي، عليك أن تعرف ما تبحث عنه بالضبط. يعتمد GeeTest v3 على ثلاث قيم، اثنتان منها إلزاميتان:
| المعلمة | الوصف | ثابتة/ديناميكية | مثال |
|---|---|---|---|
gt |
معرف الموقع (32 حرفًا ست عشريًا) | ثابتة لكل موقع | 019924a82c70bb123aae90d483b6a0ed |
challenge |
رمز الجلسة (32 حرفًا ست عشريًا) | ديناميكية لكل تحميل للصفحة | a3f5c8d2e1b04a6789012345678abcdf |
api_server |
النطاق الفرعي لـ GeeTest API (اختياري) | ثابتة لكل موقع | api-na.geetest.com |
القاعدة العملية هنا بسيطة: قيمة gt تُلتقط مرة واحدة وتبقى صالحة لكل الطلبات على الموقع نفسه، أما challenge فتُولَّد من جديد في كل مرة يظهر فيها الاختبار — ومع كل محاولة حل جديدة يجب استخراج قيمة challenge جديدة. أما api_server فاختياري، ولا تحتاج إلى إرساله إلا حين يعتمد الموقع على نطاق GeeTest إقليمي محدد.
الطريقة الأولى: اعتراض طلب التسجيل (register)
معظم تطبيقات GeeTest v3 تجلب التحدي من نقطة نهاية تسجيل تُستدعى عند تحميل الاختبار. تعترض هذه الطريقة الاستجابة مباشرة، وهي الأكثر موثوقية لأنها تلتقط القيم من المصدر ذاته. تبدو الاستجابة كالتالي:
{
"success": 1,
"challenge": "a3f5c8d2e1b04a6789012345678abcdf",
"gt": "019924a82c70bb123aae90d483b6a0ed",
"new_captcha": true
}
Python (مكتبة requests)
عند العمل بدون متصفح، ابحث عن رابط نقطة نهاية التسجيل داخل صفحة HTML ثم اطلبها ضمن الجلسة نفسها لتحصل على القيمتين من الرد JSON:
import requests
import re
session = requests.Session()
html = session.get("https://example.com/login").text
# Find the register endpoint
register_url = re.search(
r'(https?://[^"\']+(?:register|captcha|geetest)[^"\']*)',
html
)
if register_url:
resp = session.get(register_url.group(1)).json()
gt = resp["gt"]
challenge = resp["challenge"]
print(f"gt: {gt}")
print(f"challenge: {challenge}")
JavaScript (اعتراض شبكة Puppeteer)
حين يبني الموقع رابط التسجيل ديناميكيًا عبر JavaScript، يصعب التقاطه بالتعبيرات النمطية وحدها. الأنسب حينئذٍ الإنصات لكل استجابات الشبكة داخل Puppeteer والتقاط أول رد يحمل الحقلين معًا:
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch();
const page = await browser.newPage();
let geetestParams = {};
page.on('response', async (response) => {
const url = response.url();
if (url.includes('register') || url.includes('captcha')) {
try {
const json = await response.json();
if (json.gt && json.challenge) {
geetestParams = {
gt: json.gt,
challenge: json.challenge,
};
console.log('Captured GeeTest params:', geetestParams);
}
} catch (e) {}
}
});
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });
console.log('Final params:', geetestParams);
الطريقة الثانية: القراءة من مصدر الصفحة
بعض المواقع تضمّن القيمتين مباشرة داخل HTML أو في متغيرات JavaScript المضمّنة، فلا حاجة عندها إلى اعتراض أي طلب شبكة. تحاول القطعة التالية أولًا قراءة سمات data- ثم تعود إلى المتغيرات النصية إن لم تجدها:
import re
# From data attributes
gt_match = re.search(r'data-gt=["\']([a-f0-9]{32})', html)
challenge_match = re.search(r'data-challenge=["\']([a-f0-9]{32})', html)
# From JavaScript variables
if not gt_match:
gt_match = re.search(r'gt\s*[=:]\s*["\']([a-f0-9]{32})', html)
if not challenge_match:
challenge_match = re.search(r'challenge\s*[=:]\s*["\']([a-f0-9]{32})', html)
gt = gt_match.group(1) if gt_match else None
challenge = challenge_match.group(1) if challenge_match else None
print(f"gt={gt}, challenge={challenge}")
انتبه إلى أن gt المُضمّن في الصفحة يظل صالحًا، لكن challenge المطبوع في المصدر قد يكون قديمًا إن جرى تخزين الصفحة مؤقتًا (cache). عند الشك، عُد إلى الطريقة الأولى لالتقاط تحدٍّ حي.
الطريقة الثالثة: ربط دالة initGeetest
تُهيّأ أداة GeeTest v3 داخل الصفحة عبر استدعاء initGeetest()، ويمرَّر إليها كائن الإعداد الذي يحتوي على كل المعلمات دفعة واحدة. باعتراض هذا الاستدعاء قبل تشغيل نصوص الصفحة تحصل على القيم كما تراها الأداة نفسها تمامًا:
// Puppeteer: inject before page scripts
await page.evaluateOnNewDocument(() => {
window.__geetestConfig = null;
const origInit = window.initGeetest;
Object.defineProperty(window, 'initGeetest', {
set(fn) {
this._initGeetest = function(config, callback) {
window.__geetestConfig = config;
console.log('GeeTest config:', JSON.stringify(config));
return fn(config, callback);
};
},
get() { return this._initGeetest; }
});
});
await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });
const config = await page.evaluate(() => window.__geetestConfig);
// config = { gt: "019924a...", challenge: "a3f5c8d...", product: "bind", ... }
أي طريقة تناسب حالتك؟
تخيّل فريقًا يبني اختبارات QA آلية لبوابة حجز أو متجر إلكتروني إقليمي في منطقة الخليج يحمي صفحة تسجيل الدخول بـ GeeTest v3. إن كان الفريق يشغّل السكربت بلا متصفح ضمن خط تكامل مستمر، فالطريقة الأولى (اعتراض register) أو الثانية (قراءة المصدر) أخفّ وأسرع. أما إذا كان الموقع يبني الاختبار عبر JavaScript معقّد ولا يكشف نقطة نهاية واضحة، فالطريقة الثالثة (ربط initGeetest) هي الأضمن لأنها تلتقط الإعداد من داخل المتصفح مباشرة. القاعدة العامة: ابدأ بالأخف، وانتقل إلى تشغيل متصفح كامل فقط حين تفشل القراءة النصية. وأيًّا كانت الطريقة، احرص أن تكون قيمة challenge طازجة لحظة الإرسال.
إرسال المعلمات إلى CaptchaAI والحصول على الحل
بعد التقاط gt وchallenge، أرسلهما إلى CaptchaAI باستخدام الأسلوب geetest. الآلية من خطوتين: أرسل المهمة أولًا لتستلم معرّفها، ثم استطلع النتيجة دوريًا حتى تجهز. لا تُطِل الفاصل الزمني بين الاستخراج والإرسال حتى لا ينتهي عمر التحدي.
Python
import requests
import time
API_KEY = "YOUR_API_KEY"
# Submit
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": "https://example.com/login",
"json": "1",
}).json()
if resp["status"] != 1:
raise Exception(f"Submit error: {resp['request']}")
task_id = resp["request"]
# Poll
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, "json": "1"
}).json()
if result["status"] == 1:
solution = result["request"]
print(f"Solution: {solution}")
break
if result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Error: {result['request']}")
JavaScript
يتبع مثال JavaScript المنطق نفسه: إرسال عبر in.php، ثم استطلاع دوري لـ res.php كل خمس ثوانٍ حتى تعود الحالة 1:
const submit = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: 'YOUR_API_KEY',
method: 'geetest',
gt: geetestParams.gt,
challenge: geetestParams.challenge,
pageurl: 'https://example.com/login',
json: 1,
}
});
const taskId = submit.data.request;
let solution = null;
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: 'YOUR_API_KEY', action: 'get', id: taskId, json: 1 }
});
if (poll.data.status === 1) {
solution = poll.data.request;
break;
}
}
console.log('Solution:', solution);
حقن الحل في نموذج الصفحة
الحل الذي تعيده CaptchaAI لـ GeeTest v3 ليس قيمة واحدة، بل ثلاث: challenge وvalidate وseccode. لإكمال تسجيل الدخول أو إرسال النموذج، اكتب هذه القيم في ثلاثة حقول مخفية داخل النموذج قبل إرساله:
# solution is a pipe-delimited string or JSON object
# Parse and inject into the form
import json
sol = json.loads(solution) if isinstance(solution, str) else solution
driver.execute_script("""
const form = document.querySelector('form');
function addHidden(name, value) {
let input = form.querySelector(`input[name="${name}"]`);
if (!input) {
input = document.createElement('input');
input.type = 'hidden';
input.name = name;
form.appendChild(input);
}
input.value = value;
}
addHidden('geetest_challenge', arguments[0]);
addHidden('geetest_validate', arguments[1]);
addHidden('geetest_seccode', arguments[2]);
""", sol["challenge"], sol["validate"], sol["seccode"])
معالجة الأخطاء الشائعة
أغلب حالات الفشل في GeeTest v3 ترجع إلى معلمة ناقصة أو تحدٍّ منتهي الصلاحية، لا إلى الحل نفسه. يلخّص الجدول الأعراض الأكثر تكرارًا وعلاجها:
| المشكلة | السبب | الإصلاح |
|---|---|---|
ERROR_BAD_PARAMETERS |
غياب gt أو challenge |
كلاهما مطلوب؛ استخرج قيمًا طازجة |
| تحدٍّ منتهي الصلاحية | انتهاء عمر رمز challenge |
استخرج تحديًا جديدًا مباشرة قبل الإرسال |
قيمة gt خاطئة |
منقولة من موقع مختلف | gt خاص بكل موقع؛ أعد استخراجه من الهدف |
| رفض الموقع للحل | انتهاء صلاحية التحدي أثناء الحل | قلّل الزمن بين الاستخراج والإرسال |
أسئلة شائعة
ما الفرق بين gt وchallenge، وأيّهما يتغيّر؟
gt معرّف ثابت للموقع تلتقطه مرة واحدة ويبقى صالحًا، بينما challenge رمز جلسة يتجدّد مع كل ظهور للاختبار. عند كل محاولة حل، أعد استخراج challenge واترك gt كما هو.
أي طريقة استخراج من الثلاث أستخدم؟
إن كنت تعمل بلا متصفح فابدأ بالقراءة النصية أو اعتراض register؛ وإن كان الموقع يبني الاختبار عبر JavaScript دون كشف نقطة نهاية واضحة، فربط initGeetest هو الأضمن.
ماذا تُعيد CaptchaAI بعد حلّ GeeTest v3؟
تعيد الخدمة ثلاث قيم: challenge وvalidate وseccode. تُحقن هذه القيم في حقول النموذج المخفية (geetest_challenge وgeetest_validate وgeetest_seccode) لإكمال الإرسال.
وماذا عن GeeTest v4 في CaptchaAI؟
هذا الدليل يخص GeeTest v3 عبر معلمتَي gt وchallenge. أما GeeTest v4 فيعتمد captcha_id وأنواع تحديات متعددة (نقر، مطابقة، شريحة)، ودعمه في CaptchaAI قيد الإطلاق قريبًا وليس متاحًا بعد.
ابدأ بحلّ GeeTest v3 عبر CaptchaAI
أنشئ حسابك واحصل على مفتاح API من captchaai.com، ثم طبّق خطوات هذا الدليل على أول اختبار GeeTest v3 لديك.