دليل تقني

أتمتة المتصفح بالذكاء الاصطناعي بدون BrowserBase: شو بنينا بدالها

كيف تبني أتمتة متصفح بالذكاء الاصطناعي مع Playwright و LLMs بدل الأدوات المدفوعة. Instance Pooling، إدارة الجلسات، فهم الصفحات بالـ LLM، ومقارنة التكاليف.

16 فبراير 202614 دقيقة للقراءةفريق هندسة أورنتس

سوق أتمتة المتصفح المدفوعة

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
جلسات BrowserBaseN/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 عند التوسع. المقايضة هي وقت الصيانة وقدرات مكافحة الكشف المحدودة.

أخطاء شائعة

  1. ما فيه Instance Pooling. تشغيل متصفح جديد لكل مهمة يضيع 1-3 ثواني cold start و 200-400 ميجابايت RAM. اعمل Pool وأعد استخدام المثيلات.

  2. CSS selectors مكتوبة بشكل ثابت. الصفحات تغير بنية الـ DOM بشكل منتظم. تحديد العناصر بالـ LLM أكثر مقاومة من الـ selectors الثابتة.

  3. ما فيه حفظ للجلسات. الـ workflows متعددة الخطوات اللي تحتاج تسجيل دخول تفشل لما حالة الجلسة تضيع بين الخطوات.

  4. تجاهل مكافحة الكشف بالكامل. حتى الإجراءات الأساسية (viewport عشوائي، تدوير User Agent، تجاوز webdriver) تمنع الكشف على أغلب المواقع.

  5. استخدام نموذج كبير لتخطيط الأفعال. GPT-4o-mini أو Claude Haiku سريعين بما فيه الكفاية لفهم الصفحات. النموذج الكبير يضيف تأخير بدون دقة أفضل لهالمهمة.

  6. ما فيه timeout على تحميل الصفحات. بعض الصفحات تحمل بلا نهاية (Infinite Scrolling، سكريبتات طرف ثالث بطيئة). حط navigation timeout وعالج الحالة.

  7. التشغيل في الإنتاج بدون مراقبة. تابع نسبة النجاح، متوسط وقت التنفيذ، وأنواع الأخطاء لكل workflow. أرسل تنبيه لما نسبة النجاح تنزل.

النقاط الرئيسية

  • Playwright المستضاف ذاتياً + LLM يغطي 90% من حالات أتمتة المتصفح. للأدوات الداخلية، بوابات الشركاء، والبيانات العامة بدون كشف بوتات عدواني، هذا هو النهج الصحيح.

  • Instance Pooling ضروري. أعد استخدام مثيلات المتصفح عبر المهام. الـ Cold starts وتخصيص الذاكرة هي أكبر عوائق الأداء.

  • فهم الصفحات بالـ LLM يستبدل الـ selectors الهشة. أرسل شجرة الوصول لنموذج سريع. خليه يقرر مع أي عناصر يتفاعل. أكثر مقاومة لتغييرات الصفحات من CSS selectors الثابتة.

  • الخدمات المدفوعة تستاهل سعرها في مكافحة الكشف. إذا مواقعك المستهدفة عندها Cloudflare أو حماية مشابهة، BrowserBase يستثمر باستمرار في تجاوزها. هذا شغلهم الأساسي. لا تحاول تنافسهم.

  • الاستضافة الذاتية أرخص 3-10x عند التوسع. لكن تدفع بوقت الصيانة وقيود مكافحة الكشف. اتخذ المقايضة بوعي.

نبني أتمتة المتصفح في أنظمة سير عمل الذكاء الاصطناعي ومشاريع البرمجيات المخصصة. إذا تحتاج مساعدة في هندسة أتمتة المتصفح، تواصل مع فريقنا أو اطلب عرض سعر.

المواضيع المغطاة

أتمتة المتصفح بالذكاء الاصطناعيHeadless Browser TypeScriptPlaywright ذكاء اصطناعيأتمتة المتصفح مفتوح المصدربديل BrowserBaseLLM متصفحWeb Scraping ذكاء اصطناعي

جاهز لبناء أنظمة ذكاء اصطناعي جاهزة للإنتاج؟

فريقنا متخصص في بناء أنظمة ذكاء اصطناعي جاهزة للإنتاج. خلينا نحكي كيف نقدر نساعد.

ابدأ محادثة