دروس API

حل Grid Image CAPTCHA باستخدام Node.js وCaptchaAI

في سكربت Node.js يتعامل مع شبكة صور، الوقت الضائع لا يذهب إلى الخدمة: زمن معالجة هذا النوع لدى CaptchaAI أقل من ثانية واحدة، بينما يبتلع تشغيل Puppeteer والوصول إلى إطار التحدي والتقاط الصورة معظم الدورة. لذلك يركّز هذا الدليل على الجزء الذي تتحكم فيه فعلاً: حمولة طلب تحمل الصورة ونص التعليمات معاً بدل الصورة وحدها، حلقة استطلاع تعرف متى تتوقف، ثم إسقاط أرقام المربعات العائدة على عناصر الصفحة دون انزياح في الفهرسة. المسار كامل بـPuppeteer وAxios، مع المعاملات التي تفصل الطلب المقبول عن المرفوض ورموز الخطأ التي تفسّر الرفض.


لماذا تُربك شبكة الصور سكربتات الأتمتة

تحدي الصور الشبكية يختلف عن أي تحدٍ آخر لأنه يحمل معلومتين لا واحدة: صورة مقسّمة إلى 9 أو 16 مربعاً، ونص تعليمات يحدد ما يجب اختياره مثل "حدد جميع المربعات التي تحتوي على إشارات المرور". السكربت الذي يرسل الصورة وحدها يترك المحرك بلا سياق، والسكربت الذي يقرأ التعليمات دون الصورة لا يملك ما يحلله أصلاً.

تزيد بنية الصفحة الأمر تعقيداً: التحدي يعيش داخل إطار iframe منفصل عنوانه ينتهي بـ recaptcha/api2/bframe، وليس داخل الصفحة الرئيسية. لذلك تبدأ كل محاولة ناجحة بالوصول إلى الإطار الصحيح قبل أي التقاط أو نقر. النقطة الثالثة أن بعض التحديات تعيد تحميل المربعات بعد الاختيار، فتحتاج إلى دورة جديدة من الالتقاط والإرسال بدل افتراض جولة واحدة.


ما تحتاجه قبل تشغيل المثال

المتطلب التفاصيل
مفتاح الـ API أنشئ حساباً على captchaai.com وانسخ المفتاح من لوحة التحكم
Node.js الإصدار 14 أو أحدث
الحزم axios وpuppeteer وform-data

احفظ المفتاح في متغير بيئة بدل كتابته داخل الملف، خصوصاً إذا كان السكربت سيُرفع إلى مستودع مشترك.


الخطوة 1: التقط شبكة الصور من إطار التحدي

المهمة هنا مزدوجة: قراءة نص التعليمات من العنصر .rc-imageselect-desc-no-canonical، ثم تصوير حاوية الشبكة .rc-imageselect-target كصورة PNG مستقلة. تجنّب تصوير الصفحة بأكملها ثم قصّها يدوياً، لأن أي هامش زائد يغيّر ترتيب المربعات المستنتج ويعطيك أرقاماً لا تطابق الواقع.

const puppeteer = require('puppeteer');
const fs = require('fs');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/page-with-recaptcha');

// Switch to the reCAPTCHA challenge iframe
const frames = page.frames();
const challengeFrame = frames.find((f) => f.url().includes('recaptcha/api2/bframe'));

// Get the instruction text
const instruction = await challengeFrame.$eval(
  '.rc-imageselect-desc-no-canonical',
  (el) => el.textContent.trim()
);

// Screenshot the grid
const grid = await challengeFrame.$('.rc-imageselect-target');
await grid.screenshot({ path: 'grid.png' });

الخطوة 2: أرسل الصورة والتعليمات إلى CaptchaAI

الإرسال يتم بطلب POST من نوع multipart/form-data إلى نقطة النهاية in.php. كل حقل في حمولة الطلب له دور واضح:

  • method — القيمة post، وهي طريقة الإرسال المشتركة بين صور OCR وصور الشبكة.
  • grid_size — أبعاد الشبكة كما تظهر فعلياً على الشاشة: 3x3 أو 4x4.
  • img_type — القيمة recaptcha لتعريف تخطيط الشبكة المتوقع.
  • instructions — نص التعليمات المقروء من الصفحة؛ إرساله فارغاً يحرم المحرك من السياق.
  • json — القيمة 1 لتصل الاستجابة بصيغة JSON بدل النص الخام.
  • file — الصورة نفسها كتدفق قراءة عبر fs.createReadStream.

عندما تكون قيمة status تساوي 1، يحمل الحقل request معرّف المهمة الذي ستستخدمه في الخطوة التالية.

const axios = require('axios');
const FormData = require('form-data');

const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('grid_size', '3x3');
form.append('img_type', 'recaptcha');
form.append('instructions', instruction);
form.append('json', '1');
form.append('file', fs.createReadStream('grid.png'));

const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
  headers: form.getHeaders(),
});

if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);

الخطوة 3: استطلع النتيجة حتى تصل أرقام المربعات

الاستطلاع الدوري على res.php هو الحلقة التي تنتظر فيها النتيجة. ابدأ بمهلة أولية خمس ثوانٍ قبل أول استفسار، ثم كرّر كل خمس ثوانٍ. الرد CAPCHA_NOT_READY ليس خطأً بل إشارة "ما زال قيد المعالجة"، وأي قيمة أخرى غير ذلك تعني توقفاً حقيقياً يستحق إيقاف الحلقة فوراً بدل الاستمرار في استهلاك المحاولات.

النتيجة تعود كمصفوفة JSON من أرقام المربعات المطلوب النقر عليها، مرقّمة من 1 إلى 9 في شبكة 3×3 ومن 1 إلى 16 في شبكة 4×4، بترتيب القراءة من أعلى اليسار إلى أسفل اليمين كما تظهر الشبكة في المتصفح.

await sleep(5000);

let cellsToClick;
for (let i = 0; i < 30; i++) {
  const { data: pollData } = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
  });

  if (pollData.status === 1) {
    cellsToClick = JSON.parse(pollData.request);
    console.log('Click cells:', cellsToClick);
    break;
  }
  if (pollData.request !== 'CAPCHA_NOT_READY') {
    throw new Error(pollData.request);
  }
  await sleep(5000);
}

الخطوة 4: انقر المربعات ثم أكّد التحقق

المصفوفة العائدة تبدأ من الرقم 1 بينما فهرسة العناصر في JavaScript تبدأ من الصفر، ولهذا يظهر cellNum - 1 في الحلقة. أبقِ فاصلاً زمنياً قصيراً بين النقرات كي تلتقط الصفحة كل حدث نقر بشكل مستقل، ثم اضغط زر التحقق وأغلق المتصفح.

const tiles = await challengeFrame.$$('.rc-imageselect-tile');

for (const cellNum of cellsToClick) {
  await tiles[cellNum - 1].click();
  await sleep(300);
}

// Click verify
await challengeFrame.click('#recaptcha-verify-button');
console.log(`Solved: clicked tiles ${JSON.stringify(cellsToClick)}`);
await browser.close();

الناتج المتوقع:

Click cells: [1, 3, 6, 9]
Solved: clicked tiles [1,3,6,9]

سيناريو تشغيلي: بوابة حجز مواعيد في السعودية أو مصر

تخيّل فريق ضمان جودة في الرياض مسؤول عن مراقبة توافر المواعيد في بوابة خدمات حكومية، أو فريق تجارة إلكترونية في القاهرة يختبر مسار إتمام الشراء قبل موسم رمضان. في الحالتين يعمل سكربت مراقبة كل بضع دقائق على مدار اليوم، وتظهر شبكة الصور فجأة عند تكرار الطلبات من نفس عنوان IP فيتوقف السيناريو بالكامل حتى يتدخل شخص يدوياً.

بدمج الخطوات الأربع أعلاه في مهمة المراقبة، تستمر الدورة دون تدخل. الحساب هنا بسيط: الفوترة في CaptchaAI قائمة على الـ threads لا على عدد عمليات الحل، وكل خطة تمنح حلولاً غير محدودة ضمن عدد الـ threads المتاح. خطة BASIC بسعر $15 شهرياً تتيح 5 threads متزامنة وتكفي سكربت مراقبة واحداً أو اثنين، بينما خطة ADVANCE بسعر $90 شهرياً تتيح 50 thread لفريق يشغّل عشرات المتصفحات بالتوازي. راجع صفحة الأسعار الرسمية قبل الترقية لأن الحد العملي هو عدد الطلبات المتزامنة، لا حجم الاستهلاك الشهري.


أخطاء شائعة أثناء التشغيل وكيف تعالجها

  • ERROR_ZERO_BALANCE — انتهى الرصيد أو الاشتراك؛ تحقق من لوحة التحكم قبل إعادة تشغيل الحلقة.
  • ERROR_WRONG_USER_KEY — المفتاح منسوخ ناقصاً أو يحمل مسافة زائدة في نهايته.
  • ERROR_ZERO_CAPTCHA_FILESIZE — لقطة الشاشة فارغة لأن العنصر لم يُحمّل بعد؛ انتظر ظهور .rc-imageselect-target قبل التصوير.
  • ERROR_WRONG_FILE_EXTENSION — الملف المرسل ليس بصيغة صورة مدعومة؛ التزم بـ PNG أو JPG.
  • ERROR_CAPTCHA_UNSOLVABLE — الصورة غير واضحة أو التعليمات مفقودة؛ أعد الالتقاط والإرسال بدل إعادة استخدام نفس الملف.
  • شبكة تتغير بعد النقر — بعض التحديات تحمّل مربعات جديدة، فاجعل السكربت يعيد الدورة عند بقاء زر التحقق نشطاً.

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

كم يستغرق حل شبكة الصور عادةً؟

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

هل تتغير الخطوات إذا كانت الشبكة 4×4 بدل 3×3؟

الخطوات نفسها بلا تغيير، والفرق الوحيد هو ضبط grid_size على 4x4 وتوقّع مصفوفة أرقام تمتد حتى 16. لا تخمّن القيمة: اقرأ عدد المربعات المعروضة فعلياً قبل بناء حمولة الطلب.

هل أحتاج إلى خادم وسيط مع هذا السكربت؟

ليس شرطاً لتشغيل المثال، لكنه مفيد عندما يعمل السكربت لساعات طويلة من عنوان واحد. البروكسي يقلل تكرار ظهور التحدي أصلاً، أما دور CaptchaAI فيبدأ بعد ظهوره.

كيف أختار عدد الـ threads المناسب لحجم عملي؟

احسب عدد عمليات الحل المتزامنة في ذروة اليوم لا مجموعها الشهري، لأن الفوترة قائمة على الـ threads مع حلول غير محدودة داخل كل thread. متصفح واحد يعمل بشكل متسلسل يحتاج thread واحداً، وعشرة متصفحات متوازية تحتاج عشرة.

هل يصلح نفس المسار لأنواع الصور الأخرى؟

نعم، فحقل method بقيمة post يخدم صور OCR العادية وصور الشبكة معاً. ما يتغير هو المعاملات المرافقة: صورة OCR عادية لا تحتاج grid_size ولا img_type، بينما شبكة الصور تحتاجهما مع نص التعليمات.


أدلة ذات صلة


ابدأ حل شبكات الصور الآن مع CaptchaAI ←

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