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

استخراج بيانات الويب بالطريقة الصحيحة. 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/JavaScript | JavaScript | Python |
| دعم المتصفح | 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 installCrawlee يُثبت ثلاث حزم أساسية:
crawlee— نواة الإطار مع الزاحفات والطوابير والتخزينplaywright— أتمتة المتصفح للصفحات المُقدمة بـ JavaScript@crawlee/playwright— تكامل Playwright مع Crawlee
الخطوة 2: فهم بنية Crawlee
قبل كتابة الكود، دعنا نفهم كيف يعمل Crawlee:
┌─────────────────────────────────────────────┐
│ Crawlee │
│ │
│ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ طابور │───▶│ الزاحف │───▶│ مجموعة │ │
│ │ الطلبات │ │ (الموجه) │ │ البيانات│ │
│ └──────────┘ └──────────┘ └─────────┘ │
│ │ │ │
│ │ ┌────┴────┐ │
│ │ │ مدير │ │
│ │ │البروكسي │ │
│ │ └─────────┘ │
│ ▼ │
│ إعادة محاولة تلقائية عند الفشل │
│ التحكم في التزامن │
│ تحديد المعدل │
└─────────────────────────────────────────────┘
المكونات الرئيسية:
- طابور الطلبات — يدير عناوين URL للزحف، يتعامل مع التكرارات، ويحفظ الحالة عبر عمليات إعادة التشغيل
- الزاحف — يعالج كل طلب باستخدام دالة المعالجة (Cheerio لـ HTML، Playwright للصفحات المُقدمة بـ JS)
- الموجه — يوجه أنماط URL المختلفة إلى دوال معالجة مختلفة
- مجموعة البيانات — تخزن البيانات المستخرجة كسطور JSON، قابلة للتصدير إلى CSV أو JSON أو أي تنسيق
- مدير البروكسي — يدير تدوير البروكسي والجلسات لتجنب الحظر
الخطوة 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 مرات، بدون حمل المتصفح |
| محتوى مُقدم بـ JavaScript | PlaywrightCrawler | ينفذ JS، ينتظر العرض |
| تطبيقات الصفحة الواحدة (SPAs) | PlaywrightCrawler | يتعامل مع التوجيه من جانب العميل |
| APIs تُرجع HTML | CheerioCrawler | يحتاج فقط لتحليل 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 عاملة، إليك بعض الطرق لتوسيعها:
- أضف قاعدة بيانات — خزّن النتائج في PostgreSQL باستخدام Drizzle ORM (اطلع على دليل Drizzle)
- ابنِ API — قدّم البيانات المستخرجة عبر REST API مع Hono (اطلع على دليل Hono)
- جدولة التشغيل — استخدم GitHub Actions (اطلع على دليل CI/CD)
- أضف استخراج بالذكاء الاصطناعي — ادمج مع Claude لتحليل البيانات الذكي (اطلع على دليل AI scraper)
الخلاصة
لقد بنيت أداة استخراج بيانات ويب احترافية باستخدام Crawlee و TypeScript. الآن تعرف كيف:
- تختار بين
CheerioCrawlerوPlaywrightCrawlerحسب الموقع المستهدف - تستخدم الموجهات للتعامل مع أنواع الصفحات المختلفة بنظافة
- تنفذ التعامل مع الصفحات المتعددة والتمرير اللانهائي
- تدير تدوير البروكسي والجلسات لتجنب الحظر
- تخزن وتصدر البيانات بتنسيقات متعددة
- تنشر أداة الاستخراج باستخدام Docker للإنتاج
Crawlee يتعامل مع الأجزاء الصعبة — إدارة الطوابير، إعادة المحاولة، تدوير البروكسي، بصمات المتصفح — حتى تتمكن من التركيز على كتابة منطق الاستخراج المهم.
استخراج سعيد!
ناقش مشروعك معنا
نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.
دعنا نجد أفضل الحلول لاحتياجاتك.
مقالات ذات صلة

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

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
تعلم كيف تبني وكيل ذكاء اصطناعي يقرر بشكل مستقل متى وكيف يسترجع المعلومات من قواعد البيانات المتجهية. دليل عملي شامل باستخدام Vercel AI SDK و Next.js مع أمثلة قابلة للتنفيذ.

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