أتمتة المتصفح بالذكاء الاصطناعي بدون BrowserBase: شو بنينا بدالها
كيف تبني أتمتة متصفح بالذكاء الاصطناعي مع Playwright و LLMs بدل الأدوات المدفوعة. Instance Pooling، إدارة الجلسات، فهم الصفحات بالـ LLM، ومقارنة التكاليف.
سوق أتمتة المتصفح المدفوعة
BrowserBase و Browserless وخدمات مشابهة تحاسبك بالدقيقة أو بالجلسة لمتصفحات Headless مُدارة. لأنظمة الذكاء الاصطناعي اللي تحتاج تتفاعل مع صفحات الويب (تعبئة نماذج، استخراج بيانات منظمة، التنقل في عمليات متعددة الخطوات)، هالخدمات تتكفل بالبنية التحتية: مثيلات المتصفح، مكافحة الكشف، البروكسيات، وإدارة الجلسات.
التكاليف تتراكم بسرعة. بسعر $0.10-0.50 لكل دقيقة جلسة، workflow يعالج 1,000 صفحة يومياً بدقيقتين لكل صفحة يكلف $200-1,000 يومياً. لنظام ذكاء اصطناعي يشتغل باستمرار، هذا $6,000-30,000 شهرياً بس للبنية التحتية للمتصفح.
بنينا بديل مستضاف ذاتياً باستخدام Playwright + LLM لفهم الصفحات. يغطي 90% من حالات الاستخدام بجزء بسيط من التكلفة. هالمقال يغطي الهندسة المعمارية. لكيف نبني أنظمة سير عمل الذكاء الاصطناعي والذكاء الاصطناعي الوكيل بشكل عام، هالأدلة تغطي الأنماط عالية المستوى.
الهندسة المعمارية
┌─────────────────────────────────────────────────────────┐
│ AI Browser Engine │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task Queue │ │ Instance │ │ Session │ │
│ │ (BullMQ) │ │ Pool │ │ Manager │ │
│ │ │ │ (Playwright │ │ (cookies, │ │
│ │ Prioritized │ │ browsers) │ │ localStorage│ │
│ │ Retry logic │ │ │ │ auth state) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Page Interaction Layer │ │
│ │ │ │
│ │ 1. الانتقال للرابط │ │
│ │ 2. انتظار تحميل الصفحة │ │
│ │ 3. استخراج بنية الصفحة (شجرة الوصول) │ │
│ │ 4. إرسال البنية للـ LLM للفهم │ │
│ │ 5. الـ LLM يرجع خطة أفعال (click, type, select) │ │
│ │ 6. تنفيذ الأفعال عبر Playwright │ │
│ │ 7. استخراج البيانات المنظمة من النتيجة │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Instance Pooling
تشغيل متصفح جديد لكل مهمة مكلف (Cold Start: 1-3 ثواني، ذاكرة: 200-400 ميجابايت لكل مثيل). الـ Pool يعيد استخدام مثيلات المتصفح عبر المهام.
class BrowserPool {
private available: Browser[] = [];
private inUse = new Map<string, Browser>();
private maxInstances: number;
constructor(options: { maxInstances: number }) {
this.maxInstances = options.maxInstances;
}
async acquire(): Promise<{ browser: Browser; id: string }> {
// إعادة استخدام مثيل متاح
if (this.available.length > 0) {
const browser = this.available.pop()!;
const id = crypto.randomUUID();
this.inUse.set(id, browser);
return { browser, id };
}
// إنشاء مثيل جديد إذا تحت الحد الأقصى
if (this.inUse.size < this.maxInstances) {
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--single-process',
],
});
const id = crypto.randomUUID();
this.inUse.set(id, browser);
return { browser, id };
}
// الـ Pool ممتلئ: انتظار تحرير مثيل
return new Promise((resolve) => {
this.waitQueue.push(resolve);
});
}
async release(id: string): Promise<void> {
const browser = this.inUse.get(id);
if (!browser) return;
this.inUse.delete(id);
// تنظيف الحالة بين المهام
const pages = browser.contexts();
for (const context of pages) {
await context.close();
}
// إذا أحد ينتظر، أعطيه هالمثيل
if (this.waitQueue.length > 0) {
const resolve = this.waitQueue.shift()!;
const newId = crypto.randomUUID();
this.inUse.set(newId, browser);
resolve({ browser, id: newId });
} else {
this.available.push(browser);
}
}
}
تحديد حجم الـ Pool
| حجم العمل | حجم الـ Pool | الذاكرة المطلوبة |
|---|---|---|
| خفيف (< 100 صفحة/ساعة) | 2-3 مثيلات | 1-2 GB |
| متوسط (100-500 صفحة/ساعة) | 5-10 مثيلات | 3-5 GB |
| ثقيل (500+ صفحة/ساعة) | 10-20 مثيل | 5-10 GB |
كل مثيل Chromium يستخدم 200-400 ميجابايت RAM. حجم الـ Pool يحدد سقف الإنتاجية ومتطلبات الذاكرة. ابدأ صغير وكبّر بناءً على الحمل الفعلي.
إدارة الجلسات
كثير من الـ workflows تحتاج تحافظ على حالة تسجيل الدخول عبر تفاعلات متعددة مع الصفحات. مدير الجلسات يحفظ الكوكيز و localStorage وتوكنات المصادقة بين المهام.
class SessionManager {
private sessions = new Map<string, SessionState>();
async createSession(id: string, options: SessionOptions): Promise<BrowserContext> {
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
userAgent: options.userAgent || this.getRandomUserAgent(),
locale: options.locale || 'en-US',
timezoneId: options.timezone || 'Europe/Berlin',
});
// استعادة حالة الجلسة السابقة إذا موجودة
const existing = this.sessions.get(id);
if (existing) {
await context.addCookies(existing.cookies);
// localStorage يتم استعادته عبر page.evaluate بعد التنقل
}
return context;
}
async saveSession(id: string, context: BrowserContext): Promise<void> {
const cookies = await context.cookies();
const pages = context.pages();
let localStorage = {};
if (pages.length > 0) {
localStorage = await pages[0].evaluate(() => {
const data: Record<string, string> = {};
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
if (key) data[key] = window.localStorage.getItem(key) || '';
}
return data;
});
}
this.sessions.set(id, {
cookies,
localStorage,
lastUsed: Date.now(),
});
}
}
فهم الصفحات بالـ LLM
الابتكار الرئيسي: بدل ما تكتب CSS selectors أو XPath لكل صفحة، ترسل شجرة الوصول (Accessibility Tree) للصفحة للـ LLM وتخليه يقرر مع أي عناصر يتفاعل.
async function extractPageStructure(page: Page): Promise<string> {
// الحصول على شجرة الوصول (تمثيل منظم ومختصر)
const tree = await page.accessibility.snapshot();
// تحويل لصيغة نصية يفهمها الـ LLM
return formatAccessibilityTree(tree, {
maxDepth: 5,
includeRoles: ['button', 'link', 'textbox', 'combobox', 'checkbox', 'heading'],
includeText: true,
includeLabels: true,
});
}
function formatAccessibilityTree(node: any, options: any, depth = 0): string {
if (depth > options.maxDepth) return '';
if (!options.includeRoles.includes(node.role) && depth > 1) {
// تخطي العناصر غير التفاعلية، لكن التكرار في العناصر الفرعية
return (node.children || []).map(c => formatAccessibilityTree(c, options, depth + 1)).join('');
}
const indent = ' '.repeat(depth);
let result = `${indent}[${node.role}] ${node.name || ''}`;
if (node.value) result += ` value="${node.value}"`;
result += '\n';
for (const child of node.children || []) {
result += formatAccessibilityTree(child, options, depth + 1);
}
return result;
}
تخطيط الأفعال بالـ LLM
ترسل بنية الصفحة مع وصف المهمة للـ LLM. الـ LLM يرجع تسلسل أفعال:
async function planActions(pageStructure: string, task: string): Promise<Action[]> {
const response = await llm.generate({
model: 'gpt-4o-mini', // نموذج سريع لتخطيط الأفعال
messages: [
{
role: 'system',
content: `You are a browser automation assistant. Given a page structure and a task,
return a JSON array of actions to accomplish the task.
Available actions: click(selector), type(selector, text), select(selector, value),
wait(ms), extract(selector).
Use the element text/labels to identify targets, not CSS selectors.`,
},
{
role: 'user',
content: `Page structure:\n${pageStructure}\n\nTask: ${task}`,
},
],
responseFormat: 'json',
});
return JSON.parse(response.text);
}
// مهمة مثال: "تعبئة نموذج الاتصال بالاسم Sara Mustermann والإيميل sara.mustermann@beispiel.de"
// الـ LLM يرجع:
// [
// { "action": "type", "target": "Name input field", "value": "Sara Mustermann" },
// { "action": "type", "target": "Email input field", "value": "sara.mustermann@beispiel.de" },
// { "action": "click", "target": "Submit button" }
// ]
تحويل أفعال الـ LLM لأوامر Playwright
الـ LLM يرجع أهداف بشرية القراءة ("Name input field"). الـ Resolver يحولها لـ Playwright selectors:
async function resolveAndExecute(page: Page, actions: Action[]): Promise<void> {
for (const action of actions) {
// إيجاد العنصر المطابق لوصف الـ LLM
const element = await findElementByDescription(page, action.target);
if (!element) {
throw new ActionError(`ما لقينا العنصر: ${action.target}`);
}
switch (action.action) {
case 'click':
await element.click();
await page.waitForLoadState('networkidle');
break;
case 'type':
await element.fill(action.value);
break;
case 'select':
await element.selectOption(action.value);
break;
case 'wait':
await page.waitForTimeout(action.value);
break;
case 'extract':
const text = await element.textContent();
results.push({ field: action.target, value: text });
break;
}
}
}
async function findElementByDescription(page: Page, description: string): Promise<ElementHandle | null> {
// استراتيجيات متعددة لإيجاد العنصر
const strategies = [
// بالـ aria-label
() => page.$(`[aria-label*="${description}" i]`),
// بالـ placeholder
() => page.$(`[placeholder*="${description}" i]`),
// بالنص المرئي
() => page.$(`text=${description}`),
// بربط الـ label
() => page.$(`label:has-text("${description}") + input, label:has-text("${description}") input`),
// بالدور والاسم
() => page.getByRole('textbox', { name: new RegExp(description, 'i') }).first().elementHandle(),
() => page.getByRole('button', { name: new RegExp(description, 'i') }).first().elementHandle(),
];
for (const strategy of strategies) {
try {
const element = await strategy();
if (element) return element;
} catch {
continue;
}
}
return null;
}
أساسيات مكافحة الكشف
بعض المواقع تكشف وتحظر المتصفحات Headless. إجراءات مضادة أساسية:
const context = await browser.newContext({
// تعشير أبعاد النافذة
viewport: {
width: 1280 + Math.floor(Math.random() * 200),
height: 720 + Math.floor(Math.random() * 100),
},
// تدوير User Agents
userAgent: getRandomUserAgent(),
// ضبط locale ومنطقة زمنية واقعية
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
// موقع جغرافي واقعي
geolocation: { latitude: 48.1351, longitude: 11.5820 },
permissions: ['geolocation'],
});
// تجاوز navigator.webdriver (كشف الـ Headless)
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
});
ملاحظة: مكافحة الكشف هي سباق تسلح. للمواقع اللي عندها كشف بوتات متطور (Cloudflare, Akamai)، Playwright المستضاف ذاتياً راح ينكشف في النهاية. هنا الخدمات المدفوعة مثل BrowserBase تقدم قيمة حقيقية: يستثمرون باستمرار في مكافحة الكشف. لمعظم مهام الأتمتة التجارية (أدوات داخلية، بوابات شركاء، بيانات عامة)، مكافحة الكشف الأساسية تكفي.
متى الأدوات المدفوعة تستاهل فعلاً
| السيناريو | مستضاف ذاتياً | خدمة مدفوعة |
|---|---|---|
| أتمتة أدوات داخلية | أفضل خيار (ما تحتاج مكافحة كشف) | مبالغة |
| استخراج بيانات عامة (بسيط) | جيد (مكافحة الكشف الأساسية تكفي) | غير ضروري |
| مواقع بكشف بوتات | ممكن لكن صيانة مستمرة | تستاهل (مكافحة الكشف شغلهم الأساسي) |
| حجم عالي (10 آلاف+ صفحة/يوم) | معقد (تدوير البروكسيات، إدارة IP) | تستاهل (بنية تحتية مُدارة) |
| بيانات منظمة (GDPR, Compliance) | أفضل (البيانات تبقى على بنيتك التحتية) | خطر (البيانات تمر عبر طرف ثالث) |
| هجرة لمرة واحدة | جيد (حمل عمل مؤقت) | تكلفة غير ضرورية |
قاعدة القرار: إذا تأتمت workflows داخلية أو تعالج بيانات عامة من مواقع بدون كشف بوتات عدواني، استضف بنفسك. إذا تستخرج بحجم عالي من مواقع بحماية مستوى Cloudflare، ادفع لخدمة تتعامل مع مكافحة الكشف كشغلها الأساسي.
مقارنة التكاليف
| المكون | مستضاف ذاتياً (شهرياً) | BrowserBase (شهرياً) |
|---|---|---|
| حوسبة (5 مثيلات) | $50-100 (Container/VPS) | N/A |
| استدعاءات LLM (تخطيط الأفعال) | $20-50 (GPT-4o-mini) | N/A |
| جلسات BrowserBase | N/A | $500-2,000 |
| خدمة بروكسي (عند الحاجة) | $50-200 | مشمولة |
| صيانة | 2-4 ساعات/شهر | لا شيء |
| الإجمالي (1,000 صفحة/يوم) | $120-350/شهر | $500-2,000/شهر |
| الإجمالي (10,000 صفحة/يوم) | $300-800/شهر | $3,000-10,000/شهر |
الاستضافة الذاتية أرخص 3-10x عند التوسع. المقايضة هي وقت الصيانة وقدرات مكافحة الكشف المحدودة.
أخطاء شائعة
-
ما فيه Instance Pooling. تشغيل متصفح جديد لكل مهمة يضيع 1-3 ثواني cold start و 200-400 ميجابايت RAM. اعمل Pool وأعد استخدام المثيلات.
-
CSS selectors مكتوبة بشكل ثابت. الصفحات تغير بنية الـ DOM بشكل منتظم. تحديد العناصر بالـ LLM أكثر مقاومة من الـ selectors الثابتة.
-
ما فيه حفظ للجلسات. الـ workflows متعددة الخطوات اللي تحتاج تسجيل دخول تفشل لما حالة الجلسة تضيع بين الخطوات.
-
تجاهل مكافحة الكشف بالكامل. حتى الإجراءات الأساسية (viewport عشوائي، تدوير User Agent، تجاوز webdriver) تمنع الكشف على أغلب المواقع.
-
استخدام نموذج كبير لتخطيط الأفعال. GPT-4o-mini أو Claude Haiku سريعين بما فيه الكفاية لفهم الصفحات. النموذج الكبير يضيف تأخير بدون دقة أفضل لهالمهمة.
-
ما فيه timeout على تحميل الصفحات. بعض الصفحات تحمل بلا نهاية (Infinite Scrolling، سكريبتات طرف ثالث بطيئة). حط navigation timeout وعالج الحالة.
-
التشغيل في الإنتاج بدون مراقبة. تابع نسبة النجاح، متوسط وقت التنفيذ، وأنواع الأخطاء لكل workflow. أرسل تنبيه لما نسبة النجاح تنزل.
النقاط الرئيسية
-
Playwright المستضاف ذاتياً + LLM يغطي 90% من حالات أتمتة المتصفح. للأدوات الداخلية، بوابات الشركاء، والبيانات العامة بدون كشف بوتات عدواني، هذا هو النهج الصحيح.
-
Instance Pooling ضروري. أعد استخدام مثيلات المتصفح عبر المهام. الـ Cold starts وتخصيص الذاكرة هي أكبر عوائق الأداء.
-
فهم الصفحات بالـ LLM يستبدل الـ selectors الهشة. أرسل شجرة الوصول لنموذج سريع. خليه يقرر مع أي عناصر يتفاعل. أكثر مقاومة لتغييرات الصفحات من CSS selectors الثابتة.
-
الخدمات المدفوعة تستاهل سعرها في مكافحة الكشف. إذا مواقعك المستهدفة عندها Cloudflare أو حماية مشابهة، BrowserBase يستثمر باستمرار في تجاوزها. هذا شغلهم الأساسي. لا تحاول تنافسهم.
-
الاستضافة الذاتية أرخص 3-10x عند التوسع. لكن تدفع بوقت الصيانة وقيود مكافحة الكشف. اتخذ المقايضة بوعي.
نبني أتمتة المتصفح في أنظمة سير عمل الذكاء الاصطناعي ومشاريع البرمجيات المخصصة. إذا تحتاج مساعدة في هندسة أتمتة المتصفح، تواصل مع فريقنا أو اطلب عرض سعر.
المواضيع المغطاة
أدلة ذات صلة
الدليل الشامل لأنظمة الذكاء الاصطناعي الوكيلي
دليل تقني لأنظمة الذكاء الاصطناعي الوكيلي في بيئات الأعمال. تعرف على البنية والقدرات والتطبيقات العملية للوكلاء المستقلين.
اقرأ الدليلالتجارة الوكيلية: كيف تخلي وكلاء الذكاء الاصطناعي يشترون بأمان
كيف تصمم تجارة وكيلية محكومة. محركات السياسات، بوابات الموافقة البشرية، إيصالات HMAC، الـ idempotency، عزل المستأجرين، وبروتوكول الدفع الوكيلي الكامل.
اقرأ الدليلالـ 9 أماكن اللي نظام AI تبعك بيسرّب بيانات منها (وكيف تسد كل وحدة)
خارطة منهجية لكل مكان البيانات بتتسرب منه بأنظمة AI. البرومبتات، الـ embeddings، السجلات، استدعاءات الأدوات، ذاكرة الـ agent، رسائل الأخطاء، الكاش، بيانات التدريب، وتسليمات الـ agents.
اقرأ الدليلجاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟
فريقنا متخصص في بناء أنظمة ذكاء اصطناعي جاهزة للإنتاج. خلينا نحكي كيف نقدر نساعد.
ابدأ محادثة