استخراج بيانات الويب باستخدام Crawlee و TypeScript: دليل شامل من الصفر إلى الإنتاج

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

استخراج بيانات الويب بالطريقة الصحيحة. Crawlee هو إطار عمل TypeScript مفتوح المصدر من Apify يتعامل مع الأجزاء الصعبة — طوابير الطلبات، إعادة المحاولة، تدوير البروكسي، ومكافحة الحظر — حتى تتمكن من التركيز على استخراج البيانات.

ما ستتعلمه

بنهاية هذا الدليل، ستتمكن من:

  • إعداد مشروع Crawlee مع TypeScript من الصفر
  • بناء أدوات استخراج باستخدام PlaywrightCrawler للمواقع التي تعتمد على JavaScript
  • استخدام CheerioCrawler لاستخراج HTML خفيف وسريع
  • إدارة طوابير الطلبات لزحف آلاف الصفحات
  • تخزين البيانات المستخرجة باستخدام نظام Dataset المدمج في Crawlee
  • تطبيق تدوير البروكسي واستراتيجيات مكافحة الحظر
  • التعامل مع الصفحات المتعددة والتمرير اللانهائي والمحتوى الديناميكي
  • نشر أداة الاستخراج في الإنتاج باستخدام Docker

المتطلبات الأساسية

قبل البدء، تأكد من توفر:

  • Node.js 20+ مثبت (node --version)
  • خبرة في TypeScript (الأنواع، async/await، generics)
  • معرفة أساسية بـ HTML/CSS (المحددات، بنية DOM)
  • محرر أكواد — يُنصح بـ VS Code أو Cursor
  • Docker مثبت (اختياري، للنشر)

لماذا Crawlee؟

استخراج بيانات الويب في Node.js غالباً يعني تجميع Puppeteer و Cheerio ومكتبات الطلبات ومنطق إعادة المحاولة وإدارة الطوابير بنفسك. Crawlee يوفر كل هذا في إطار عمل واحد متماسك:

الميزةCrawleeيدوي (Puppeteer + Cheerio)Scrapy (Python)
اللغةTypeScript/JavaScriptJavaScriptPython
دعم المتصفحPlaywright, Puppeteerإعداد يدويSplash/Selenium
طابور الطلباتمدمج مع الحفظتنفيذ يدويمدمج
إعادة المحاولةقابل للتخصيصيدويمدمج
تدوير البروكسيمدمج مع إدارة الجلساتيدويMiddleware
مكافحة الحظربصمات، headersيدويMiddleware
تخزين البياناتDataset + Key-Value Storeيدوي (JSON/DB)Item pipelines
أمان الأنواعTypeScript كاملاختياريلا

Crawlee يمنحك قوة Scrapy مع أمان TypeScript وتجربة مطور حديثة.


الخطوة 1: إعداد المشروع

ابدأ بإنشاء مشروع Crawlee جديد. واجهة الأوامر تُنشئ كل ما تحتاجه:

npx crawlee create my-scraper --template playwright-ts
cd my-scraper

هذا يُنشئ بنية المشروع التالية:

my-scraper/
├── src/
│   ├── main.ts          # نقطة الدخول
│   ├── routes.ts        # معالجات المسارات
│   └── types.ts         # أنواع مخصصة
├── storage/             # يُنشأ تلقائياً للبيانات والطوابير
├── package.json
├── tsconfig.json
└── Dockerfile           # إعداد Docker جاهز للإنتاج

ثبت التبعيات:

npm install

Crawlee يُثبت ثلاث حزم أساسية:

  • crawlee — نواة الإطار مع الزاحفات والطوابير والتخزين
  • playwright — أتمتة المتصفح للصفحات المُقدمة بـ JavaScript
  • @crawlee/playwright — تكامل Playwright مع Crawlee

الخطوة 2: فهم بنية Crawlee

قبل كتابة الكود، دعنا نفهم كيف يعمل Crawlee:

┌─────────────────────────────────────────────┐
│                  Crawlee                      │
│                                               │
│  ┌──────────┐    ┌──────────┐    ┌─────────┐ │
│  │  طابور   │───▶│  الزاحف  │───▶│ مجموعة  │ │
│  │ الطلبات  │    │ (الموجه) │    │ البيانات│ │
│  └──────────┘    └──────────┘    └─────────┘ │
│       │               │                       │
│       │          ┌────┴────┐                  │
│       │          │  مدير   │                  │
│       │          │البروكسي │                  │
│       │          └─────────┘                  │
│       ▼                                       │
│  إعادة محاولة تلقائية عند الفشل              │
│  التحكم في التزامن                            │
│  تحديد المعدل                                 │
└─────────────────────────────────────────────┘

المكونات الرئيسية:

  1. طابور الطلبات — يدير عناوين URL للزحف، يتعامل مع التكرارات، ويحفظ الحالة عبر عمليات إعادة التشغيل
  2. الزاحف — يعالج كل طلب باستخدام دالة المعالجة (Cheerio لـ HTML، Playwright للصفحات المُقدمة بـ JS)
  3. الموجه — يوجه أنماط URL المختلفة إلى دوال معالجة مختلفة
  4. مجموعة البيانات — تخزن البيانات المستخرجة كسطور JSON، قابلة للتصدير إلى CSV أو JSON أو أي تنسيق
  5. مدير البروكسي — يدير تدوير البروكسي والجلسات لتجنب الحظر

الخطوة 3: بناء CheerioCrawler للصفحات الثابتة

لنبدأ بأسرع نوع زاحف — CheerioCrawler. يقوم بتنزيل HTML الخام وتحليله مع Cheerio (واجهة شبيهة بـ jQuery)، بدون تشغيل متصفح. مثالي للمواقع التي لا تتطلب عرض JavaScript.

استبدل محتوى src/main.ts:

import { CheerioCrawler, Dataset, log } from 'crawlee';
 
// تكوين السجلات
log.setLevel(log.LEVELS.INFO);
 
// إنشاء الزاحف
const crawler = new CheerioCrawler({
  // الحد الأقصى لعدد الطلبات المتزامنة
  maxConcurrency: 10,
 
  // الحد الأقصى للطلبات في الدقيقة (تحديد المعدل)
  maxRequestsPerMinute: 60,
 
  // إعادة محاولة الطلبات الفاشلة حتى 3 مرات
  maxRequestRetries: 3,
 
  // معالج لكل صفحة
  async requestHandler({ request, $, enqueueLinks, pushData }) {
    const url = request.url;
    log.info(`جاري الزحف: ${url}`);
 
    // استخراج البيانات من الصفحة باستخدام محددات CSS
    const title = $('h1').first().text().trim();
    const description = $('meta[name="description"]').attr('content') || '';
    const links = $('a[href]')
      .map((_, el) => $(el).attr('href'))
      .get()
      .filter((href) => href.startsWith('http'));
 
    // دفع البيانات المستخرجة إلى مجموعة البيانات
    await pushData({
      url,
      title,
      description,
      linksFound: links.length,
      scrapedAt: new Date().toISOString(),
    });
 
    // تتبع الروابط في الصفحة (زحف عرضي)
    await enqueueLinks({
      globs: ['https://example.com/**'],
      label: 'DETAIL',
    });
  },
 
  // يُستدعى عند فشل الطلب بعد كل المحاولات
  async failedRequestHandler({ request }) {
    log.error(`فشل: ${request.url} — ${request.errorMessages.join(', ')}`);
  },
});
 
// بدء الزاحف بعناوين URL أولية
await crawler.run([
  'https://example.com',
]);
 
// تصدير النتائج
const dataset = await Dataset.open();
await dataset.exportToJSON('results');
log.info('اكتمل الزحف! النتائج محفوظة في storage/datasets/default/');

شغّله:

npx tsx src/main.ts

بياناتك المستخرجة محفوظة في storage/datasets/default/ كملفات JSON فردية.


الخطوة 4: بناء PlaywrightCrawler للمواقع الديناميكية

العديد من المواقع الحديثة تعرض المحتوى باستخدام JavaScript. لهذه المواقع، تحتاج PlaywrightCrawler الذي يُشغل متصفحاً حقيقياً:

import { PlaywrightCrawler, Dataset, log } from 'crawlee';
 
const crawler = new PlaywrightCrawler({
  // استخدام Chromium بدون واجهة
  launchContext: {
    launchOptions: {
      headless: true,
    },
  },
 
  // صفحات المتصفح مكلفة — حدد التزامن
  maxConcurrency: 5,
 
  // مهلة لكل صفحة (30 ثانية)
  requestHandlerTimeoutSecs: 30,
 
  async requestHandler({ request, page, enqueueLinks, pushData }) {
    const url = request.url;
    log.info(`جاري الزحف (متصفح): ${url}`);
 
    // انتظار عرض المحتوى الرئيسي
    await page.waitForSelector('.product-card', { timeout: 10000 });
 
    // استخراج بيانات المنتجات
    const products = await page.$$eval('.product-card', (cards) =>
      cards.map((card) => ({
        name: card.querySelector('.product-name')?.textContent?.trim() || '',
        price: card.querySelector('.product-price')?.textContent?.trim() || '',
        rating: card.querySelector('.product-rating')?.textContent?.trim() || '',
        image: card.querySelector('img')?.getAttribute('src') || '',
      }))
    );
 
    // دفع كل منتج إلى مجموعة البيانات
    for (const product of products) {
      await pushData({
        ...product,
        sourceUrl: url,
        scrapedAt: new Date().toISOString(),
      });
    }
 
    // تتبع روابط الصفحات التالية
    await enqueueLinks({
      selector: 'a.pagination-next',
      label: 'LISTING',
    });
  },
});
 
await crawler.run([
  'https://example-shop.com/products?page=1',
]);

متى تستخدم أي زاحف

السيناريوالزاحفالسبب
مواقع HTML ثابتةCheerioCrawlerأسرع 10 مرات، بدون حمل المتصفح
محتوى مُقدم بـ JavaScriptPlaywrightCrawlerينفذ JS، ينتظر العرض
تطبيقات الصفحة الواحدة (SPAs)PlaywrightCrawlerيتعامل مع التوجيه من جانب العميل
APIs تُرجع HTMLCheerioCrawlerيحتاج فقط لتحليل HTML
مواقع تتطلب تسجيل الدخولPlaywrightCrawlerيمكنه ملء النماذج

الخطوة 5: استخدام Router للأنماط المتعددة

أدوات الاستخراج الحقيقية تحتاج للتعامل مع أنواع صفحات مختلفة — صفحات القوائم، صفحات التفاصيل، نتائج البحث. Router في Crawlee يجعل هذا نظيفاً:

أنشئ src/routes.ts:

import { createPlaywrightRouter, Dataset } from 'crawlee';
 
export const router = createPlaywrightRouter();
 
// المعالج الافتراضي — صفحات القوائم
router.addDefaultHandler(async ({ request, page, enqueueLinks, log }) => {
  log.info(`معالجة القائمة: ${request.url}`);
 
  // استخراج روابط العناصر الفردية
  await enqueueLinks({
    selector: 'a.item-link',
    label: 'DETAIL',
  });
 
  // التعامل مع الصفحات المتعددة
  const nextButton = await page.$('a.next-page');
  if (nextButton) {
    await enqueueLinks({
      selector: 'a.next-page',
      label: 'LISTING',
    });
  }
});
 
// معالج صفحة التفاصيل
router.addHandler('DETAIL', async ({ request, page, pushData, log }) => {
  log.info(`معالجة التفاصيل: ${request.url}`);
 
  await page.waitForSelector('.article-content', { timeout: 10000 });
 
  const data = await page.evaluate(() => {
    const title = document.querySelector('h1')?.textContent?.trim() || '';
    const author = document.querySelector('.author-name')?.textContent?.trim() || '';
    const date = document.querySelector('time')?.getAttribute('datetime') || '';
    const content = document.querySelector('.article-content')?.textContent?.trim() || '';
    const tags = Array.from(document.querySelectorAll('.tag'))
      .map((tag) => tag.textContent?.trim() || '');
 
    return { title, author, date, content, tags };
  });
 
  await pushData({
    ...data,
    url: request.url,
    scrapedAt: new Date().toISOString(),
  });
});

حدّث src/main.ts لاستخدام الموجه:

import { PlaywrightCrawler } from 'crawlee';
import { router } from './routes.js';
 
const crawler = new PlaywrightCrawler({
  requestHandler: router,
  maxConcurrency: 5,
  maxRequestsPerMinute: 30,
});
 
await crawler.run([
  { url: 'https://example-blog.com/articles', label: 'LISTING' },
  { url: 'https://example-blog.com/search?q=typescript', label: 'SEARCH' },
]);

الخطوة 6: التعامل مع الصفحات المتعددة والتمرير اللانهائي

الصفحات التقليدية

للمواقع التي تحتوي على أزرار "التالي" أو صفحات مرقمة:

router.addHandler('LISTING', async ({ page, enqueueLinks, request, log }) => {
  const items = await page.$$eval('.item', (els) =>
    els.map((el) => ({
      title: el.querySelector('h2')?.textContent?.trim(),
      url: el.querySelector('a')?.href,
    }))
  );
 
  log.info(`الصفحة ${request.userData.page || 1}: وجدنا ${items.length} عنصر`);
 
  for (const item of items) {
    if (item.url) {
      await enqueueLinks({
        urls: [item.url],
        label: 'DETAIL',
      });
    }
  }
 
  const nextUrl = await page.$eval('a.next', (el) => el.href).catch(() => null);
  if (nextUrl) {
    await enqueueLinks({
      urls: [nextUrl],
      label: 'LISTING',
      userData: { page: (request.userData.page || 1) + 1 },
    });
  }
});

التمرير اللانهائي

للمواقع التي تُحمّل المزيد من المحتوى أثناء التمرير:

router.addHandler('INFINITE', async ({ page, pushData, log }) => {
  let previousHeight = 0;
  let scrollAttempts = 0;
  const maxScrolls = 20;
 
  while (scrollAttempts < maxScrolls) {
    await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
    await page.waitForTimeout(2000);
 
    const currentHeight = await page.evaluate(() => document.body.scrollHeight);
    if (currentHeight === previousHeight) {
      log.info('لا يوجد محتوى إضافي للتحميل');
      break;
    }
 
    previousHeight = currentHeight;
    scrollAttempts++;
    log.info(`التمرير ${scrollAttempts}/${maxScrolls} — الارتفاع: ${currentHeight}`);
  }
 
  const allItems = await page.$$eval('.feed-item', (items) =>
    items.map((item) => ({
      text: item.querySelector('.content')?.textContent?.trim() || '',
      author: item.querySelector('.author')?.textContent?.trim() || '',
      timestamp: item.querySelector('time')?.getAttribute('datetime') || '',
    }))
  );
 
  log.info(`تم استخراج ${allItems.length} عنصر إجمالاً`);
 
  for (const item of allItems) {
    await pushData(item);
  }
});

الخطوة 7: تدوير البروكسي ومكافحة الحظر

الحظر هو أكبر تحدٍ في استخراج بيانات الويب. Crawlee يحتوي على أدوات مدمجة للمساعدة:

تدوير البروكسي الأساسي

import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
 
const proxyConfiguration = new ProxyConfiguration({
  proxyUrls: [
    'http://user:pass@proxy1.example.com:8080',
    'http://user:pass@proxy2.example.com:8080',
    'http://user:pass@proxy3.example.com:8080',
  ],
});
 
const crawler = new PlaywrightCrawler({
  proxyConfiguration,
  requestHandler: router,
  useSessionPool: true,
  sessionPoolOptions: {
    maxPoolSize: 100,
    sessionOptions: {
      maxUsageCount: 50,
    },
  },
});

أفضل ممارسات مكافحة الحظر

const crawler = new PlaywrightCrawler({
  maxRequestsPerMinute: 20,
 
  browserPoolOptions: {
    useFingerprints: true,
    fingerprintOptions: {
      fingerprintGeneratorOptions: {
        browsers: ['chrome', 'firefox'],
        operatingSystems: ['windows', 'macos', 'linux'],
        locales: ['en-US', 'en-GB'],
      },
    },
  },
 
  preNavigationHooks: [
    async ({ page }) => {
      const width = 1280 + Math.floor(Math.random() * 200);
      const height = 800 + Math.floor(Math.random() * 200);
      await page.setViewportSize({ width, height });
 
      await page.setExtraHTTPHeaders({
        'Accept-Language': 'en-US,en;q=0.9',
        'Accept-Encoding': 'gzip, deflate, br',
      });
    },
  ],
 
  requestHandler: router,
});

الخطوة 8: تخزين البيانات والتصدير

Crawlee يوفر نظامين للتخزين:

Dataset — للبيانات الجدولية

import { Dataset } from 'crawlee';
 
// دفع البيانات أثناء الزحف
await pushData({
  name: 'TypeScript Handbook',
  price: 29.99,
  category: 'Programming',
});
 
// بعد الزحف، التصدير بتنسيقات مختلفة
const dataset = await Dataset.open();
 
// تصدير كـ JSON
await dataset.exportToJSON('output');
 
// تصدير كـ CSV
await dataset.exportToCSV('output');
 
// الحصول على كل البيانات مرة واحدة
const { items } = await dataset.getData();
console.log(`إجمالي العناصر: ${items.length}`);

Key-Value Store — للبيانات المتنوعة

import { KeyValueStore } from 'crawlee';
 
const store = await KeyValueStore.open();
 
// حفظ لقطات الشاشة
await store.setValue('homepage-screenshot', await page.screenshot(), {
  contentType: 'image/png',
});
 
// حفظ الإعدادات أو الحالة
await store.setValue('scraper-config', {
  lastRun: new Date().toISOString(),
  totalPages: 1500,
  errors: 12,
});

الخطوة 9: معالجة الأخطاء والمرونة

أدوات الاستخراج في الإنتاج يجب أن تتعامل مع الفشل بلطف:

import { PlaywrightCrawler, log } from 'crawlee';
 
const crawler = new PlaywrightCrawler({
  maxRequestRetries: 3,
  requestHandlerTimeoutSecs: 60,
 
  async requestHandler({ request, page, pushData, session }) {
    try {
      const pageTitle = await page.title();
 
      // كشف الحظر الناعم
      if (
        pageTitle.toLowerCase().includes('captcha') ||
        pageTitle.toLowerCase().includes('verify') ||
        pageTitle.toLowerCase().includes('access denied')
      ) {
        session?.retire();
        throw new Error(`تم كشف حظر في ${request.url}`);
      }
 
      // انتظار المحتوى مع بديل
      try {
        await page.waitForSelector('.main-content', { timeout: 10000 });
      } catch {
        log.warning(`محدد المحتوى غير موجود في ${request.url}، جاري تجربة البديل`);
        await page.waitForSelector('body', { timeout: 5000 });
      }
 
      const data = await page.evaluate(() => ({
        title: document.querySelector('h1')?.textContent?.trim() || 'بدون عنوان',
        content: document.querySelector('.main-content')?.textContent?.trim() || '',
      }));
 
      await pushData({
        ...data,
        url: request.url,
        retryCount: request.retryCount,
      });
    } catch (error) {
      log.error(`خطأ في زحف ${request.url}`, {
        error: (error as Error).message,
        retryCount: request.retryCount,
      });
      throw error;
    }
  },
 
  async failedRequestHandler({ request, log }) {
    log.error(`فشل نهائي: ${request.url}`, {
      errors: request.errorMessages,
      retries: request.retryCount,
    });
 
    const dataset = await Dataset.open('failed-requests');
    await dataset.pushData({
      url: request.url,
      errors: request.errorMessages,
      failedAt: new Date().toISOString(),
    });
  },
});

الخطوة 10: مثال واقعي — زحف لوحة الوظائف

لنبني أداة استخراج كاملة تجمع قوائم الوظائف. هذا المثال يوضح كل المفاهيم معاً:

import { PlaywrightCrawler, Dataset, log } from 'crawlee';
 
interface JobListing {
  title: string;
  company: string;
  location: string;
  salary: string;
  description: string;
  tags: string[];
  postedAt: string;
  url: string;
  scrapedAt: string;
}
 
const crawler = new PlaywrightCrawler({
  maxConcurrency: 3,
  maxRequestsPerMinute: 15,
  maxRequestRetries: 3,
  requestHandlerTimeoutSecs: 45,
 
  browserPoolOptions: {
    useFingerprints: true,
  },
 
  preNavigationHooks: [
    async ({ page }) => {
      await page.route('**/*.{png,jpg,jpeg,gif,webp,woff,woff2}', (route) =>
        route.abort()
      );
    },
  ],
 
  async requestHandler({ request, page, enqueueLinks, pushData, log }) {
    const label = request.label || 'LISTING';
 
    if (label === 'LISTING') {
      log.info(`زحف صفحة القوائم: ${request.url}`);
      await page.waitForSelector('.job-card', { timeout: 15000 });
 
      await enqueueLinks({
        selector: '.job-card a.job-title-link',
        label: 'JOB_DETAIL',
      });
 
      const hasNextPage = await page.$('a[aria-label="Next page"]');
      if (hasNextPage) {
        await enqueueLinks({
          selector: 'a[aria-label="Next page"]',
          label: 'LISTING',
        });
      }
    }
 
    if (label === 'JOB_DETAIL') {
      log.info(`زحف تفاصيل الوظيفة: ${request.url}`);
      await page.waitForSelector('.job-detail', { timeout: 15000 });
 
      const job: JobListing = await page.evaluate(() => {
        const getText = (selector: string): string =>
          document.querySelector(selector)?.textContent?.trim() || '';
 
        return {
          title: getText('h1.job-title'),
          company: getText('.company-name'),
          location: getText('.job-location'),
          salary: getText('.salary-range'),
          description: getText('.job-description'),
          tags: Array.from(document.querySelectorAll('.skill-tag')).map(
            (tag) => tag.textContent?.trim() || ''
          ),
          postedAt: getText('.posted-date'),
          url: window.location.href,
          scrapedAt: new Date().toISOString(),
        };
      });
 
      if (job.title && job.company) {
        await pushData(job);
        log.info(`تم الحفظ: ${job.title} في ${job.company}`);
      }
    }
  },
});
 
log.info('بدء أداة زحف لوحة الوظائف...');
await crawler.run([
  { url: 'https://example-jobs.com/jobs?q=typescript', label: 'LISTING' },
]);
 
const dataset = await Dataset.open();
const { items } = await dataset.getData();
log.info(`اكتمل الزحف! تم استخراج ${items.length} قائمة وظائف.`);
await dataset.exportToJSON('jobs');

الخطوة 11: النشر باستخدام Docker

مشاريع Crawlee تأتي مع Dockerfile جاهز للإنتاج:

FROM node:20-slim AS builder
 
RUN npx playwright install-deps chromium
 
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
 
FROM node:20-slim
 
RUN npx playwright install chromium
RUN npx playwright install-deps chromium
 
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
 
RUN mkdir -p storage
 
ENV NODE_ENV=production
ENV CRAWLEE_STORAGE_DIR=./storage
 
CMD ["node", "dist/main.js"]

البناء والتشغيل:

docker build -t my-scraper .
docker run -v $(pwd)/output:/app/storage my-scraper

الخطوة 12: أنماط متقدمة

الزحف القابل للاستئناف

Crawlee يحفظ طابور الطلبات على القرص. إذا تعطلت أداة الاستخراج، فقط أعد تشغيلها — ستستأنف من حيث توقفت:

const crawler = new PlaywrightCrawler({
  requestHandler: router,
});
 
// التشغيل الأول: يعالج كل عناوين URL
await crawler.run(['https://example.com/page1', 'https://example.com/page2']);
 
// إذا أعدت التشغيل، يتم تخطي عناوين URL المكتملة تلقائياً

اعتراض طلبات الشبكة

مراقبة وتعديل حركة الشبكة أثناء الزحف:

const crawler = new PlaywrightCrawler({
  preNavigationHooks: [
    async ({ page }) => {
      page.on('response', async (response) => {
        const url = response.url();
        if (url.includes('/api/products')) {
          try {
            const data = await response.json();
            const store = await KeyValueStore.open();
            await store.setValue(`api-response-${Date.now()}`, data);
          } catch {
            // الاستجابة قد لا تكون JSON
          }
        }
      });
 
      await page.route('**/*', (route) => {
        const type = route.request().resourceType();
        if (['image', 'font', 'stylesheet'].includes(type)) {
          return route.abort();
        }
        return route.continue();
      });
    },
  ],
 
  async requestHandler({ page, pushData }) {
    await page.waitForSelector('.content');
    const title = await page.title();
    await pushData({ title });
  },
});

استكشاف الأخطاء وإصلاحها

مشاكل شائعة

فشل تشغيل المتصفح في Docker: تأكد من تثبيت تبعيات Playwright:

npx playwright install-deps chromium

الحظر المتكرر:

  • قلل maxConcurrency و maxRequestsPerMinute
  • فعّل تدوير البروكسي
  • استخدم useFingerprints: true
  • أضف تأخيرات عشوائية بين الطلبات

مشاكل الذاكرة مع عمليات الزحف الكبيرة:

  • استخدم CheerioCrawler بدلاً من PlaywrightCrawler عند الإمكان
  • حدد maxConcurrency لتقليل استخدام الذاكرة
  • اضبط maxRequestsPerCrawl للمعالجة على دفعات

الخطوات التالية

الآن بعد أن لديك أداة استخراج Crawlee عاملة، إليك بعض الطرق لتوسيعها:


الخلاصة

لقد بنيت أداة استخراج بيانات ويب احترافية باستخدام Crawlee و TypeScript. الآن تعرف كيف:

  • تختار بين CheerioCrawler و PlaywrightCrawler حسب الموقع المستهدف
  • تستخدم الموجهات للتعامل مع أنواع الصفحات المختلفة بنظافة
  • تنفذ التعامل مع الصفحات المتعددة والتمرير اللانهائي
  • تدير تدوير البروكسي والجلسات لتجنب الحظر
  • تخزن وتصدر البيانات بتنسيقات متعددة
  • تنشر أداة الاستخراج باستخدام Docker للإنتاج

Crawlee يتعامل مع الأجزاء الصعبة — إدارة الطوابير، إعادة المحاولة، تدوير البروكسي، بصمات المتصفح — حتى تتمكن من التركيز على كتابة منطق الاستخراج المهم.

استخراج سعيد!


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على ElysiaJS + Bun: بناء واجهة برمجة تطبيقات REST آمنة الأنواع من طرف إلى طرف.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

بناء وكلاء ذكاء اصطناعي جاهزين للإنتاج باستخدام Claude Agent SDK و TypeScript

تعلم كيف تبني وكلاء ذكاء اصطناعي مستقلين باستخدام Claude Agent SDK من Anthropic بلغة TypeScript. يغطي هذا الدرس العملي حلقة الوكيل، الأدوات المدمجة، أدوات MCP المخصصة، الوكلاء الفرعيين، أوضاع الصلاحيات، وأنماط النشر للإنتاج.

35 د قراءة·

بناء وكلاء الذكاء الاصطناعي من الصفر باستخدام TypeScript: إتقان نمط ReAct مع Vercel AI SDK

تعلّم كيفية بناء وكلاء الذكاء الاصطناعي من الأساس باستخدام TypeScript. يغطي هذا الدليل التعليمي نمط ReAct، واستدعاء الأدوات، والاستدلال متعدد الخطوات، وحلقات الوكلاء الجاهزة للإنتاج مع Vercel AI SDK.

35 د قراءة·