دروس API

حل CAPTCHA الصورية مع Node.js وCaptchaAI

قراءة نص كابتشا الصور من داخل Node.js تحتاج أربع خطوات فقط: احصل على الصورة، حوّلها إلى Base64 أو ارفعها كملف، أرسلها إلى نقطة النهاية in.php، ثم استفسر عن النتيجة من res.php حتى يعود النص جاهزاً. لا تحتاج إلى نموذج تعرّف ضوئي محلي، ولا إلى تدريب أي شيء، ولا إلى مكتبات معالجة صور ثقيلة — CaptchaAI يقرأ الصورة ويعيد الأحرف كما هي في استجابة JSON بسيطة.

هذا النوع من الصور — أحرف مشوّهة فوق خلفية مشوّشة — ما زال حاضراً بقوة في أنظمة الحجز، وبوابات الخدمات المحلية، ولوحات الموردين القديمة المنتشرة في السوق العربية، وهي أنظمة نادراً ما تُحدَّث واجهاتها. إن كنت تكتب اختبارات QA لنموذج تسجيل تملكه، أو تؤتمت إدخال بيانات متكرر داخل نظام شركتك، فالمسار التالي هو ما ستضعه في سكربت الأتمتة لديك.


دورة الحل قبل أن تكتب أي كود

كل ما يحدث لاحقاً في هذا الدليل يتبع الترتيب نفسه، ومن المفيد تثبيته ذهنياً أولاً:

  1. التقاط الصورة — من قرص محلي، أو من لقطة شاشة لعنصر الصورة داخل المتصفح.
  2. الترميز أو الرفع — Base64 من الذاكرة، أو رفع الملف عبر form-data.
  3. الإرسال — طلب واحد إلى in.php يعيد معرّف المهمة taskId.
  4. استطلاع النتيجة — استدعاءات متتابعة لـ res.php بالمعرّف نفسه حتى تصل حالة النجاح ومعها النص.

نوع Image/OCR من الأنواع المدعومة رسمياً في CaptchaAI، وسقف زمن الحل المعلن له <0.5 ثانية بمعدل نجاح مرتفع — أي أن معظم زمن الرحلة في سكربتك سيأتي من الشبكة ومن فترات الانتظار التي تحددها أنت، لا من القراءة نفسها.


ما تحتاجه قبل أول طلب

البند القيمة
مفتاح CaptchaAI API من captchaai.com
Node.js 14+
المكتبات axios، fs
تنسيق الصورة JPG أو PNG أو GIF (100 بايت - 100 كيلو بايت)

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


الخطوة 1: أرسل الصورة بترميز Base64

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

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

// Read and encode the image
const imageB64 = fs.readFileSync('captcha.png').toString('base64');

// Submit to CaptchaAI
const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: API_KEY,
    method: 'base64',
    body: imageB64,
    json: 1,
  },
});

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

هذه أنسب طريقة عندما تكون الصورة موجودة أصلاً في الذاكرة — لقطة شاشة من Puppeteer مثلاً — لأنها توفّر عليك كتابة الملف ثم قراءته مرة أخرى. لاحظ التحقق من status قبل استخدام request: عندما يعود status بقيمة غير 1، فإن حقل request يحمل رمز الخطأ لا معرّف المهمة، وتجاهل هذا الفرق هو أكثر سبب يجعل السكربتات تفشل بصمت بعد ساعات من التشغيل.


بديل الخطوة 1: ارفع الملف مباشرة عبر form-data

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

const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('json', '1');
form.append('file', fs.createReadStream('captcha.png'));

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

const taskId = submitData.request;

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


الخطوة 2: استفسر عن النص من res.php

await sleep(5000);

let captchaText;
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) {
    captchaText = pollData.request;
    console.log(`CAPTCHA text: ${captchaText}`);
    break;
  }
  if (pollData.request !== 'CAPCHA_NOT_READY') {
    throw new Error(pollData.request);
  }
  await sleep(5000);
}

الحلقة أعلاه محافظة عن قصد: انتظار أولي ثم فحص دوري كل 5 ثوانٍ بحد أقصى 30 محاولة. الرمز CAPCHA_NOT_READY — بهذا الإملاء تحديداً في الـ API — يعني «ما زالت قيد المعالجة، أعد السؤال»، وأي رمز آخر يجب أن يوقف الحلقة فوراً بدل أن يستهلك محاولاتك الثلاثين على خطأ ثابت لن يتغيّر.

جهّز أيضاً مسار الفشل: إذا خرجت الحلقة دون نص، لا ترسل قيمة undefined إلى النموذج. سجّل الحالة، والتقط صورة الكابتشا مرة أخرى، وأعد المحاولة من البداية — إعادة المحاولة بصورة جديدة أنجح دائماً من الإصرار على معرّف مهمة قديم.


اضبط معلمات الدقة لتقليل الأخطاء

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

// Digits only, 4-6 characters
const { data } = await axios.post('https://ocr.captchaai.com/in.php', null, {
  params: {
    key: API_KEY,
    method: 'base64',
    body: imageB64,
    numeric: 1,      // digits only
    min_len: 4,       // minimum length
    max_len: 6,       // maximum length
    json: 1,
  },
});
المعلمة القيمة الغرض
numeric 1 = أرقام، 2 = حروف حدود الأحرف
min_len / max_len عدد صحيح قيود الطول
calc 1 يحسب التعبير الرياضي
regsense 1 حساسة لحالة الأحرف

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


سيناريو كامل: من لقطة الشاشة إلى إرسال النموذج

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

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

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

async function solveImageCaptcha() {
  // 1. Load page and screenshot CAPTCHA
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com/register');

  const captchaEl = await page.$('#captcha-image');
  await captchaEl.screenshot({ path: 'captcha.png' });

  // 2. Encode and submit
  const imageB64 = fs.readFileSync('captcha.png').toString('base64');
  const { data: submit } = await axios.post('https://ocr.captchaai.com/in.php', null, {
    params: { key: API_KEY, method: 'base64', body: imageB64, json: 1 },
  });
  const taskId = submit.request;

  // 3. Poll for text
  await sleep(5000);
  let text;
  for (let i = 0; i < 30; i++) {
    const { data: poll } = await axios.get('https://ocr.captchaai.com/res.php', {
      params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
    });
    if (poll.status === 1) { text = poll.request; break; }
    if (poll.request !== 'CAPCHA_NOT_READY') throw new Error(poll.request);
    await sleep(5000);
  }

  // 4. Type and submit
  await page.type('#captcha-input', text);
  await page.click('form [type="submit"]');
  console.log(`Solved: ${text}`);
  await browser.close();
}

solveImageCaptcha().catch(console.error);

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

Solved: ABC123

الأهم هنا ليس الكود بل ترتيبه: الالتقاط والإرسال والاستطلاع والكتابة أربع مسؤوليات منفصلة. افصل كل خطوة في وظيفة مستقلة لتعيد تشغيل الخطوة الفاشلة وحدها بدل الدورة كاملة.


رموز الخطأ وطريقة معالجتها

خطأ السبب إصلاح
ERROR_WRONG_FILE_EXTENSION تنسيق غير معتمد استخدم JPG أو PNG أو GIF
ERROR_TOO_BIG_CAPTCHA_FILESIZE حجم الصورة أكبر من 100 كيلو بايت اضغط الصورة قبل الإرسال
ERROR_ZERO_CAPTCHA_FILESIZE حجم الصورة أقل من 100 بايت تحقّق من نجاح الالتقاط
CAPCHA_NOT_READY المهمة ما زالت قيد المعالجة أعد الاستطلاع كل 5 ثوانٍ

أخطاء حجم الملف هي الأكثر تكراراً في الإنتاج، ومصدرها غالباً لقطة شاشة لعنصر فارغ لم يُحمَّل بعد. انتظر ظهور عنصر الصورة فعلياً قبل التقاطها، وتحقق من حجم الملف الناتج قبل الإرسال — سطر واحد من fs.statSync يوفّر عليك طلبات فاشلة كثيرة. أما أخطاء الرصيد أو المفتاح فمكانها لوحة التحكم لا الكود.


التكلفة والتشغيل المتوازي

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

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


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

كم يستغرق حل صورة واحدة، ولماذا ينتظر الكود 5 ثوانٍ؟

سقف زمن الحل المعلن لنوع Image/OCR هو <0.5 ثانية. فترة الانتظار البالغة 5 ثوانٍ في المثال هامش أمان محافظ للشبكة وطابور الطلبات، ويمكنك تقليلها في بيئتك بعد قياس زمن الرحلة الفعلي.

هل تؤثر جودة الصورة على صحة النص العائد؟

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

ماذا أفعل إذا عاد النص خاطئاً؟

أبلغ عن النتيجة عبر استدعاء https://ocr.captchaai.com/res.php?key=KEY&action=reportbad&id=TASK_ID بمعرّف المهمة نفسه، ثم أعد الالتقاط والإرسال بصورة جديدة بدلاً من إعادة استخدام المعرّف القديم.

كم طلباً يمكنني تشغيله في الوقت نفسه؟

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


اقرأ أيضاً


أنشئ حسابك واقرأ نص أول صورة كابتشا اليوم ←

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