على المواقع الصينية شكلان من التحقق يتكرّران: صور الأحرف الصينية تُرسَل إلى Image/OCR بعد تفعيل مجموعة الأحرف الصينية عبر language=2، وألغاز الشريحة من نوع GeeTest v3 تُرسَل بمعاملَي gt وchallenge. ما عدا ذلك — الترميز، وملفات تعريف الارتباط، والخادم الوسيط — تفاصيل تشغيلية هي التي تقرّر نجاح الطلب أو فشله.
الموضوع أقرب إلى فرق البيانات العربية مما يبدو: أي شركة استيراد أو تخليص جمركي تتعامل مع موردين صينيين تصطدم عاجلاً بصفحة تحقق مكتوبة بالماندرين. والفرق بين سكربت يتوقف كل ساعة وآخر يعمل طوال الليل سطران في الإعداد، لا خوارزمية جديدة.
لماذا تختلف صفحات التحقق الصينية عن نظيراتها الغربية
ثلاثة فروق عملية تفسّر لماذا يفشل السكربت الذي اعتدت عليه في أوروبا أو أمريكا الشمالية عند أول بوابة صينية:
- مجموعة الأحرف. محرك OCR المضبوط على الأحرف اللاتينية يقرأ الحرف الصيني ضوضاءً، فلا بد من تمرير
language=2صراحةً في حمولة الطلب. - الجلسة قصيرة العمر. كثير من البوابات الحكومية يربط صورة التحقق بملف تعريف ارتباط للجلسة، ويُبطلها خلال ثوانٍ من أي تحديث للصفحة.
- طبقة CDN محلية. شبكات التوزيع الصينية تطبّق تحديد معدل الطلبات جغرافياً، فتظهر تحديات إضافية أمام عناوين IP من خارج البر الرئيسي.
خريطة الأنواع الصينية وما يغطيه CaptchaAI فعلياً
| نوع التحقق | حالة الدعم في CaptchaAI | أين يظهر عادةً |
|---|---|---|
| صورة بأحرف صينية | ✅ Image/OCR مع language=2 |
البوابات الحكومية وقواعد البيانات الأكاديمية |
| مسألة حسابية بالماندرين | ✅ Image/OCR | نماذج التسجيل وإنشاء الحسابات |
| GeeTest v3 — لغز الشريحة | ✅ مدعوم عبر method=geetest |
بايدو، بيليبيلي، ومنصات كبرى أخرى |
| النقر على الأحرف بالترتيب | ✅ Image/OCR في وضع الإحداثيات | صفحات تسجيل الدخول والاستعلام |
| reCAPTCHA v2 | ✅ مدعوم | الواجهات الدولية للشركات الصينية |
| GeeTest v4 | ❌ غير متاح بعد — الدعم مُعلن بصيغة "قريباً" | منصات صينية حديثة |
| Tencent CAPTCHA | ❌ غير مدرج ضمن الأنواع المدعومة | خدمات تابعة لمنظومة Tencent |
الخلاصة العملية: أغلب ما ستقابله على بوابة حكومية أو قاعدة بيانات أكاديمية صينية يقع ضمن النوعين الأولين.
سيناريو من السوق: متابعة موردين صينيين من مكتب في الخليج
تخيّل فريق تخليص جمركي في دبي يتابع يومياً حالة شحنات لدى ثلاث بوابات صينية: بوابة تتبع لدى الشركة الناقلة، وسجل مواصفات على منصة B2B، وصفحة استعلام حكومية عن شهادات المطابقة. الصفحتان الأوليان تطلبان صورة تحقق بأحرف صينية عند كل استعلام، والثالثة تعرض لغز شريحة GeeTest v3 عند تسجيل الدخول.
الترتيب الذي ينجح هنا بسيط: جلسة واحدة لكل بوابة، تحميل صورة التحقق داخل الجلسة نفسها، إرسالها إلى CaptchaAI، ثم إعادة النص المُستخرَج مع بيانات النموذج مباشرةً. ثلاث بوابات × نحو 40 استعلاماً صباحياً عبء لا يستدعي خطة كبيرة، لأن الفوترة على الطلبات المتزامنة لا على عدد الحلول.
تجهيز البيئة قبل أول طلب
- أنشئ حساباً على captchaai.com وانسخ مفتاح الـ API من لوحة التحكم.
- اضبط ترميز UTF-8 في الطلب والاستجابة معاً، وإلا وصلك النص الصيني مشوّهاً بعد حلّ صحيح تماماً.
- ثبّت جلسة واحدة تحمل ملفات تعريف الارتباط من لحظة فتح الصفحة حتى إرسال النموذج.
- جهّز خادماً وسيطاً بعنوان IP صيني إن كانت البوابة تفرض قيوداً جغرافية على الاستعلام.
- سجّل وقت الحل ورمز الخطأ لكل طلب، فهذا ما ستعود إليه عند أي تراجع في معدل الحل.
خطط CaptchaAI قائمة على الـ threads لا على عدد الحلول: BASIC ($15 شهرياً، 5 threads) يكفي لسكربت رصد واحد، وSTANDARD ($30 شهرياً، 15 thread) يناسب متابعة عدة بوابات بالتوازي، وADVANCE ($90 شهرياً، 50 thread) موجّه لفرق تشغّل زحفاً ليلياً واسعاً. عدد الحلول داخل الخطة غير محدود، والسقف الحقيقي هو عدد الطلبات المتزامنة.
Python: قراءة صورة التحقق بالأحرف الصينية
تعرض هذه الصور عبارات مثل 请输入验证码 — أي "يُرجى إدخال رمز التحقق" — وتحتاج تعرفاً ضوئياً مضبوطاً على النص الصيني. الدالتان التاليتان تغطيان الحالتين الشائعتين: صورة محفوظة على القرص، وأخرى تُحمَّل من رابط داخل جلسة قائمة، والدالة الثالثة تتكفّل بـ GeeTest v3.
import requests
import base64
import time
API_KEY = "YOUR_API_KEY"
SUBMIT_URL = "https://ocr.captchaai.com/in.php"
RESULT_URL = "https://ocr.captchaai.com/res.php"
def solve_chinese_image_captcha(image_path: str) -> str:
"""Solve a Chinese character image CAPTCHA."""
with open(image_path, "rb") as f:
image_b64 = base64.b64encode(f.read()).decode()
resp = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"language": 2, # 2 = Chinese characters supported
"json": 1,
}, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit failed: {resp.get('request')}")
task_id = resp["request"]
start = time.monotonic()
while time.monotonic() - start < 120:
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get",
"id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve failed: {poll.get('request')}")
raise RuntimeError("Timeout")
def solve_chinese_captcha_from_url(captcha_url: str, cookies: dict = None) -> str:
"""Download and solve a Chinese CAPTCHA from a URL."""
session = requests.Session()
if cookies:
session.cookies.update(cookies)
resp = session.get(captcha_url, timeout=15)
image_b64 = base64.b64encode(resp.content).decode()
submit = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "base64",
"body": image_b64,
"language": 2,
"json": 1,
}, timeout=30).json()
if submit.get("status") != 1:
raise RuntimeError(f"Submit: {submit.get('request')}")
task_id = submit["request"]
for _ in range(24):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
return poll["request"]
raise RuntimeError(f"Solve: {poll.get('request')}")
raise RuntimeError("Timeout")
# --- GeeTest on Chinese platforms ---
def solve_geetest_chinese(gt: str, challenge: str, pageurl: str) -> dict:
"""Solve GeeTest v3 commonly found on Baidu, Bilibili, etc."""
resp = requests.post(SUBMIT_URL, data={
"key": API_KEY,
"method": "geetest",
"gt": gt,
"challenge": challenge,
"pageurl": pageurl,
"json": 1,
}, timeout=30).json()
if resp.get("status") != 1:
raise RuntimeError(f"Submit: {resp.get('request')}")
task_id = resp["request"]
for _ in range(36):
time.sleep(5)
poll = requests.get(RESULT_URL, params={
"key": API_KEY, "action": "get", "id": task_id, "json": 1,
}, timeout=15).json()
if poll.get("request") == "CAPCHA_NOT_READY":
continue
if poll.get("status") == 1:
# GeeTest returns challenge, validate, seccode
return poll["request"]
raise RuntimeError(f"Solve: {poll.get('request')}")
raise RuntimeError("Timeout")
# Usage — Chinese government portal
text = solve_chinese_image_captcha("chinese_captcha.png")
print(f"Chinese CAPTCHA text: {text}")
# GeeTest on a Chinese platform
geetest_result = solve_geetest_chinese(
gt="b46d1900d0a894591f1561f8c35670a7",
challenge="dynamic_challenge_string",
pageurl="https://www.example.cn/login",
)
JavaScript: التدفق نفسه داخل Node.js
إن كانت أدوات الزحف لديك مبنية على Node.js، فالمنطق لا يتغيّر: بناء URLSearchParams، إرسال إلى in.php، ثم استطلاع دوري لـ res.php حتى تختفي حالة CAPCHA_NOT_READY. لاحظ أن language تُمرَّر هنا كنص "2" لا كرقم، وهي أكثر نقطة يسهو عنها المطورون عند النقل من Python.
const API_KEY = "YOUR_API_KEY";
const SUBMIT_URL = "https://ocr.captchaai.com/in.php";
const RESULT_URL = "https://ocr.captchaai.com/res.php";
const fs = require("fs");
async function solveChineseImageCaptcha(imagePath) {
const imageB64 = fs.readFileSync(imagePath, "base64");
const body = new URLSearchParams({
key: API_KEY,
method: "base64",
body: imageB64,
language: "2",
json: "1",
});
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 24; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
async function solveGeeTest(gt, challenge, pageurl) {
const body = new URLSearchParams({
key: API_KEY,
method: "geetest",
gt,
challenge,
pageurl,
json: "1",
});
const resp = await (await fetch(SUBMIT_URL, { method: "POST", body })).json();
if (resp.status !== 1) throw new Error(`Submit: ${resp.request}`);
const taskId = resp.request;
for (let i = 0; i < 36; i++) {
await new Promise((r) => setTimeout(r, 5000));
const url = `${RESULT_URL}?key=${API_KEY}&action=get&id=${taskId}&json=1`;
const poll = await (await fetch(url)).json();
if (poll.request === "CAPCHA_NOT_READY") continue;
if (poll.status === 1) return poll.request;
throw new Error(`Solve: ${poll.request}`);
}
throw new Error("Timeout");
}
// Usage
const text = await solveChineseImageCaptcha("chinese_captcha.png");
console.log(`Chinese text: ${text}`);
ضبط الجلسة والترميز والخادم الوسيط
خمسة مواضع تستهلك وقت الفريق في أول أسبوع تشغيل:
| ما يحدث | ما ينبغي فعله |
|---|---|
| الأحرف الصينية تظهر مشوّهة في المخرجات | اضبط UTF-8 صراحةً في الطلب وفي فك ترميز الاستجابة معاً |
| البوابة ترفض النموذج رغم صحة النص | احضر صورة التحقق وأرسل النموذج ضمن الجلسة نفسها بلا انقطاع |
| معاملات GeeTest غير ظاهرة في HTML | استخرج gt وchallenge من سكربتات الصفحة أو من استدعاء الـ API الذي يولّدهما |
| تحديد معدل الطلبات من شبكات CDN الصينية | وزّع الطلبات عبر خادم وسيط بعناوين IP صينية وباعد بين الاستعلامات |
| الصورة تتغيّر مع كل تحميل للصفحة | نزّل الصورة مرة واحدة، وحلّها، ثم أرسل النتيجة دون إعادة تحميل الصفحة |
معالجة الأعطال الشائعة
| المشكلة | السبب المرجّح | الإجراء |
|---|---|---|
| عدد التحديات يقفز فجأة خلال فترة قصيرة | إيقاع الجمع أو مستوى التوازي أعلى مما يحتمله المصدر | خفّض التكرار، وثبّت الجلسات، وراجع جودة الخادم الوسيط |
| البيانات ناقصة رغم نجاح الحل | أداة الاستخراج تقرأ نسخة قديمة من الصفحة | استخرج البيانات بعد تطبيق الرمز داخل الجلسة نفسها |
challenge منتهي الصلاحية في GeeTest |
عمر المعامل قصير جداً | استخرجه وأرسله خلال ثوانٍ من تحميل الصفحة |
| النص المُعاد يخلط حروفاً لاتينية وصينية | مجموعة الأحرف غير مضبوطة | مرّر language=2 في كل طلب OCR صيني |
| التكلفة ترتفع أسرع من المتوقع | إعادة محاولات زائدة أو صفحات لا تحتاج حلاً أصلاً | اقصر الحل على الخطوات الحرجة وتتبّع إعادة المحاولات لكل مصدر |
أسئلة شائعة
ما الفرق بين language=2 وlanguage=chi_sim؟
language=2 هو المعامل الذي يفهمه in.php ويفعّل التعرف على الأحرف الصينية. أما chi_sim فتسمية مجموعة أحرف مألوفة في محركات OCR المحلية، ولا تُمرَّر بهذا الشكل في الطلب.
هل يجب تشغيل السكربت من خادم داخل الصين؟
لا. الحل يتم عند CaptchaAI، ويمكن تشغيل السكربت من الرياض أو فرانكفورت أو أي منطقة أخرى. ما يهمّ هو عنوان IP الذي تراه البوابة: إن فرضت قيوداً جغرافية، مرّر الطلبات عبر خادم وسيط بعنوان صيني.
كم عدد الـ threads الذي أحتاجه لمعالجة آلاف الصفحات ليلياً؟
احسبها بعدد الطلبات المتزامنة لا بعدد الصفحات. إذا فتح الزاحف 10 اتصالات في اللحظة نفسها وواحدة من كل خمس صفحات تعرض تحدياً، فأنت في حدود ثلاثة threads نشطة — وSTANDARD ($30 شهرياً، 15 thread) يغطيها بهامش مريح. الانتقال إلى ADVANCE ($90 شهرياً، 50 thread) يصبح منطقياً حين تزيد قائمة الانتظار على عشرات الطلبات المتزامنة.
ماذا عن GeeTest v4 وhCaptcha على المنصات الصينية؟
GeeTest v4 غير متاح في CaptchaAI حتى الآن، والدعم مُعلن بصيغة "قريباً" فقط، ولا يشمل CaptchaAI حلّ hCaptcha أو FunCaptcha. عملياً لا يعطّل هذا معظم العمل مع البوابات الصينية لأن الغالب فيها صور الأحرف وGeeTest v3، لكن تحقّق من نوع التحدي قبل بناء المسار حوله.
الخطوات التالية
ابدأ بالمسار الأقصر: مفتاح API، ثم أول طلب OCR صيني، ثم GeeTest v3.