حالات الاستخدام

حل CAPTCHA أثناء كشط البيانات في Node.js

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

لماذا axios و Cheerio بدل متصفح كامل

تتفوق Node.js في المهام كثيفة الإدخال والإخراج، وهو تحديداً ما يفعله الكشط: طلبات HTTP متتالية تنتظر ردّ الخادم. عندما يُرجع الموقع المستهدف صفحة HTML مع نموذج قياسي، فإن ثنائي axios و Cheerio أخفّ وأسرع من متصفح بلا واجهة مثل Puppeteer، لأنه لا يحمّل واجهة رسومية ولا محرك JavaScript كاملاً. الاستثناء الوحيد هو المواقع التي تبني محتواها ديناميكياً داخل المتصفح؛ هناك تحتاج إلى تنفيذ فعلي لـ JavaScript، ونعود إلى ذلك في الأسئلة الشائعة.

الفكرة الأساسية بسيطة: axios يجلب الصفحة ويرسل النماذج، Cheerio يقرأ الـ HTML بصياغة مألوفة لمن اعتاد jQuery، و CaptchaAI يتكفّل بالجزء الوحيد الذي لا يستطيع السكربت حلّه وحده — اختبار CAPTCHA.

المتطلبات

المتطلب التفاصيل
Node.js 16+ مع npm
axios npm install axios
cheerio npm install cheerio
مفتاح CaptchaAI API من captchaai.com

كيف يعمل حل CAPTCHA: أربع خطوات

يتبع كل نوع من أنواع CAPTCHA المدعومة النمط نفسه، وهو ما يجعل وحدة الحل قابلة لإعادة الاستخدام عبر المشاريع:

  1. الإرسال: ترسل نوع التحدي ومفتاح الموقع وعنوان الصفحة إلى نقطة النهاية in.php، فتستقبل معرّف مهمة.
  2. الاستطلاع الدوري: تستفسر عن النتيجة من res.php كل بضع ثوانٍ حتى تجهز، متجاوزاً ردّ CAPCHA_NOT_READY المؤقت.
  3. استلام الرمز: بمجرد اكتمال الحل، تُرجع الخدمة الرمز النصي الجاهز للاستخدام.
  4. الحقن: تضع الرمز في الحقل الذي يتوقعه الموقع — g-recaptcha-response لاختبارات reCAPTCHA — وترسل النموذج كأن مستخدماً حقيقياً أكمله.

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

وحدة حل CaptchaAI في Node.js

تغلّف الفئة التالية الخطوات الأربع في واجهة واحدة. الدالتان الخاصتان _submit و _poll تتوليان الإرسال والاستطلاع، بينما تكشف الدوال العامة طرقاً جاهزة لكل من reCAPTCHA v2 و reCAPTCHA v3 و Cloudflare Turnstile. احفظها في ملف captcha-solver.js:

// captcha-solver.js
const axios = require("axios");

class CaptchaSolver {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = "https://ocr.captchaai.com";
  }

  async _submit(params) {
    params.key = this.apiKey;
    const resp = await axios.get(`${this.baseUrl}/in.php`, { params });
    if (!resp.data.startsWith("OK|")) {
      throw new Error(`Submit error: ${resp.data}`);
    }
    return resp.data.split("|")[1];
  }

  async _poll(taskId, timeout = 300000) {
    const deadline = Date.now() + timeout;
    while (Date.now() < deadline) {
      await new Promise((r) => setTimeout(r, 5000));
      const resp = await axios.get(`${this.baseUrl}/res.php`, {
        params: { key: this.apiKey, action: "get", id: taskId },
      });
      if (resp.data === "CAPCHA_NOT_READY") continue;
      if (resp.data.startsWith("OK|")) return resp.data.split("|")[1];
      throw new Error(`Solve error: ${resp.data}`);
    }
    throw new Error("Solve timed out");
  }

  async solveRecaptchaV2(siteKey, pageUrl) {
    const taskId = await this._submit({
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
    });
    return this._poll(taskId);
  }

  async solveRecaptchaV3(siteKey, pageUrl, action = "verify") {
    const taskId = await this._submit({
      method: "userrecaptcha",
      googlekey: siteKey,
      pageurl: pageUrl,
      version: "v3",
      action,
    });
    return this._poll(taskId);
  }

  async solveTurnstile(siteKey, pageUrl) {
    const taskId = await this._submit({
      method: "turnstile",
      sitekey: siteKey,
      pageurl: pageUrl,
    });
    return this._poll(taskId);
  }
}

module.exports = CaptchaSolver;

لاحظ المهلة الافتراضية البالغة 300000 ميلي ثانية في _poll: هي سقف الانتظار قبل أن تُطلق الدالة خطأ Solve timed out. ولأن جميع الطرق تعتمد على _submit و _poll نفسيهما، فإن إضافة نوع جديد لاحقاً لا تتطلب سوى دالة قصيرة تمرّر قيمة method الصحيحة.

كشط صفحة محمية بـ reCAPTCHA

الآن نستعمل الوحدة على صفحة حقيقية. يمرّ التدفّق بأربع خطوات معلّمة في التعليقات: تحميل الصفحة، ثم استخراج مفتاح الموقع من عنصر .g-recaptcha، ثم حلّ التحدي، وأخيراً إعادة إرسال النموذج ومعه الرمز في الحقل g-recaptcha-response:

const axios = require("axios");
const cheerio = require("cheerio");
const CaptchaSolver = require("./captcha-solver");

const solver = new CaptchaSolver("YOUR_API_KEY");

async function scrapeProtectedPage(url) {
  // Step 1: Load the page
  const { data: html } = await axios.get(url, {
    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    },
  });

  const $ = cheerio.load(html);

  // Step 2: Extract site key
  const siteKey = $(".g-recaptcha").attr("data-sitekey");
  if (!siteKey) {
    console.log("No CAPTCHA found, page loaded directly");
    return html;
  }

  console.log("Site key found:", siteKey);

  // Step 3: Solve the CAPTCHA
  const token = await solver.solveRecaptchaV2(siteKey, url);
  console.log("Token received:", token.substring(0, 50));

  // Step 4: Submit with the token
  const result = await axios.post(
    url,
    new URLSearchParams({
      "g-recaptcha-response": token,
      q: "search query",
    }),
    {
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
      },
    }
  );

  return result.data;
}

إذا لم يعثر Cheerio على مفتاح الموقع، فهذا يعني غالباً أن الصفحة لم تقدّم اختبار CAPTCHA أصلاً، فنعيد الـ HTML كما هو بدل استدعاء الخدمة بلا داعٍ. رأس User-Agent واقعي مهم هنا: كثير من المواقع ترفض الطلبات التي تصل بلا هوية متصفح معروفة.

الكشط المتزامن وربطه بعدد الـ Threads

تظهر قوة Node.js الحقيقية عند معالجة صفحات كثيرة في آن واحد. يوزّع المثال التالي قائمة العناوين على عدد من العمّال المتوازين، ويتحكم المعامل concurrency في عددهم:

async function scrapePages(urls, siteKey, concurrency = 3) {
  const results = [];
  const queue = [...urls];

  const worker = async () => {
    while (queue.length > 0) {
      const url = queue.shift();
      try {
        const token = await solver.solveRecaptchaV2(siteKey, url);
        const { data } = await axios.post(
          url,
          new URLSearchParams({ "g-recaptcha-response": token }),
          {
            headers: {
              "User-Agent":
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
            },
          }
        );
        results.push({ url, data, success: true });
        console.log(`Scraped: ${url}`);
      } catch (err) {
        results.push({ url, error: err.message, success: false });
        console.error(`Failed: ${url} - ${err.message}`);
      }
    }
  };

  // Run workers concurrently
  const workers = Array(concurrency)
    .fill(null)
    .map(() => worker());
  await Promise.all(workers);

  return results;
}

// Usage
const urls = [
  "https://example.com/page/1",
  "https://example.com/page/2",
  "https://example.com/page/3",
];
const results = await scrapePages(urls, "6Le-wvkS...", 3);

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

الحفاظ على الجلسات وملفات تعريف الارتباط

بعض المواقع تربط اختبار CAPTCHA بجلسة، فترفض الرمز إن وصل من دون ملفات تعريف الارتباط التي زُرعت عند أول تحميل. الحل هو استخدام axios مع حاوية ملفات تعريف ارتباط مستمرة، بحيث يحمل الطلب الذي حلّ التحدي هويته نفسها إلى خطوة الإرسال:

const { wrapper } = require("axios-cookiejar-support");
const { CookieJar } = require("tough-cookie");

const jar = new CookieJar();
const client = wrapper(
  axios.create({
    jar,
    headers: {
      "User-Agent":
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    },
  })
);

async function scrapeWithSession(url, siteKey) {
  // Initial page load sets cookies
  await client.get(url);

  // Solve CAPTCHA
  const token = await solver.solveRecaptchaV2(siteKey, url);

  // Submit with maintained cookies
  const result = await client.post(
    url,
    new URLSearchParams({ "g-recaptcha-response": token })
  );

  return result.data;
}

هذا النمط ضروري تحديداً في تدفّقات تسجيل الدخول أو إتمام الشراء، حيث تُبنى الحماية على تسلسل من الطلبات المترابطة لا على طلب منفرد.

استخراج النتائج باستخدام Cheerio

بعد اجتياز الحماية يتحوّل العمل إلى قراءة البيانات. يوفّر Cheerio محدّدات مألوفة لمن اعتاد jQuery، فتستطيع المرور على العناصر واستخلاص العنوان والرابط والوصف في بنية نظيفة:

function parseResults(html) {
  const $ = cheerio.load(html);
  const items = [];

  $(".result-item").each((_, el) => {
    items.push({
      title: $(el).find(".title").text().trim(),
      url: $(el).find("a").attr("href"),
      description: $(el).find(".description").text().trim(),
    });
  });

  return items;
}

افصل دائماً منطق التحليل عن منطق الكشط: هكذا إذا غيّر الموقع المستهدف تخطيطه، تعدّل دالة parseResults وحدها دون المساس ببقية السكربت.

التعامل مع Cloudflare و Turnstile

إذا كان الموقع محمياً بـ Cloudflare Turnstile بدل reCAPTCHA، فالمسار نفسه ينطبق مع استبدال الدالة بـ solver.solveTurnstile()، مع الانتباه إلى أن الرمز يُحقن في الحقل cf-turnstile-response. أما صفحات تحدي Cloudflare الكاملة فتحتاج إلى مسار مختلف يُرجع ملفات تعريف الارتباط cf_clearance، وقد فصّلنا ذلك في دليل حل تحدي Cloudflare عبر الـ API.

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

المشكلة السبب المرجّح الإجراء
تكرار CAPCHA_NOT_READY بلا نهاية مفتاح موقع خاطئ أو حل بطيء تحقّق من مفتاح الموقع وارفع قيمة المهلة
403 Forbidden عند إرسال النموذج ملفات تعريف ارتباط أو رؤوس ناقصة استخدم جلسة بملفات تعريف الارتباط وأضف رأس Referer
Cheerio لا يجد العناصر محتوى يُبنى ديناميكياً انتقل إلى Puppeteer للمواقع المعتمدة على JavaScript
ECONNREFUSED تحديد لمعدل الطلبات من الموقع المستهدف أضف فواصل زمنية وبدّل الخوادم الوسيطة

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

كم عملية كشط متزامنة يمكنني تشغيلها؟

يحدّها عدد الـ Threads في خطتك لا الكود. اجعل قيمة concurrency مساوية لعدد الـ Threads أو أقل؛ فرفعها فوق ذلك يجعل الطلبات الزائدة تنتظر في قائمة الانتظار دون تسريع فعلي.

هل يمكنني الانتقال من 2Captcha دون إعادة كتابة الكود؟

في الغالب نعم. تستخدم CaptchaAI نمط in.php و res.php نفسه المتوافق مع سكربتات 2Captcha، فيكفي عادةً تغيير عنوان النقطة الأساسية ومفتاح الـ API مع الإبقاء على منطقك الحالي كما هو.

ماذا لو كان الموقع محمياً بـ hCaptcha؟

لا يدعم CaptchaAI حل hCaptcha حالياً، لذا لن تنجح معه هذه الوحدة. تحقّق من نوع الحماية قبل بناء سكربتك وتأكد أنه ضمن الأنواع المدعومة مثل reCAPTCHA أو Cloudflare Turnstile.

متى أستخدم Puppeteer بدل axios و Cheerio؟

عندما يتطلب الموقع تنفيذ JavaScript أو تفاعلات مستخدم معقّدة أو عرضاً ديناميكياً للمحتوى. أما إذا كان يُرجع HTML ثابتاً مع نماذج قياسية، فإن axios و Cheerio أسرع وأقل استهلاكاً للموارد.

أدلة ذات صلة

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