بناء جهاز Puppeteer Web Scraper مع Docker على منصة DigitalOcean App

كهاوي لرياضة الماراثون الفائق، غالبا ما أواجه تحديًا شائعًا: كيف يمكنني تقدير وقت الانتهاء من السباقات الطويلة التي لم أجربها بعد؟ عندما ناقشت هذا مع مدربي، اقترح علي أسلوبًا عمليًا – أنظر إلى العدائين الذين أكملوا كل من السباق الذي فعلته والسباق الذي أستهدفه. يمكن أن توفر هذه الارتباطات رؤى قيمة حول أوقات الانتهاء المحتملة. لكن البحث اليدوي في نتائج السباق سيكون مستهلكًا للوقت بشكل لا يصدق.

هذا ما دفعني إلى إنشاء مبادرة تحليل أوقات السباق، وهي أداة تقارن تلقائيًا نتائج السباقات عن طريق العثور على الرياضيين الذين أكملوا كل من الحدثين. تقوم التطبيق بجمع نتائج السباق من منصات مثل UltraSignup و Pacific Multisports، مما يسمح للعدائين بإدخال رابطين لسباقين ورؤية كيف أداء الرياضيين الآخرين عبر الحدثين.

إن بناء هذه الأداة أظهر لي مدى قوة DigitalOcean App Platform يمكن أن يكون. باستخدام Puppeteer مع Chrome اللا رأسي في حاويات Docker، يمكنني التركيز على حل المشكلة للعدائين بينما يتعامل App Platform مع كل تعقيدات البنية التحتية. النتيجة كانت حلا قويًا وقابلا للتوسيع يساعد مجتمع العدو على اتخاذ قرارات مستندة إلى البيانات حول أهدافهم في السباق.

بعد بناء مبادرة تحليل أوقات السباق، أردت إنشاء دليل يظهر للمطورين الآخرين كيفية استغلال هذه التكنولوجيا نفسها – Puppeteer، حاويات Docker، و DigitalOcean App Platform. وبالطبع، عند العمل مع البيانات الخارجية، يجب أن تكون حذرًا من أشياء مثل تحديد معدل الاستجابة وشروط الخدمة.

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

لقد قمت ببناء ومشاركة تطبيق ويب يقوم بجمع معلومات عن الكتب من Project Gutenberg بشكل مسؤول. التطبيق، الذي يمكنك العثور عليه في هذا مستودع GitHub، يتيح للمستخدمين البحث في آلاف الكتب ذات الملكية العامة، عرض معلومات مفصلة حول كل كتاب، والوصول إلى تنسيقات التنزيل المختلفة. ما يجعل هذا مثيرًا للاهتمام بشكل خاص هو كيفية عرض ممارسات جمع البيانات على الويب بشكل مسؤول مع تقديم قيمة حقيقية للمستخدمين.

كونك مواطنًا رقميًا جيدًا

عند بناء جهاز استنساخ ويب، فإن اتباع الممارسات الجيدة واحترام الحدود التقنية والقانونية أمر حاسم. Project Gutenberg هو مثال ممتاز لتعلم هذه المبادئ لأن:

  1. لديه شروط خدمة واضحة
  2. يوفر توجيهات robots.txt
  3. محتواه بشكل صريح في المجال العام
  4. يستفيد من زيادة إمكانية الوصول إلى موارده

تتضمن تنفيذنا عدة ممارسات جيدة:

تحديد السرعة

لأغراض العرض، نقوم بتنفيذ حد حجم بسيط يضمن وجود ثانية واحدة على الأقل بين الطلبات:

// تنفيذ بسيط لتحديد السرعة
const rateLimiter = {
    lastRequest: 0,
    minDelay: 1000, // 1 ثانية بين الطلبات
    async wait() {
        const now = Date.now();
        const timeToWait = Math.max(0, this.lastRequest + this.minDelay - now);
        if (timeToWait > 0) {
            await new Promise(resolve => setTimeout(resolve, timeToWait));
        }
        this.lastRequest = Date.now();
    }
};

هذا التنفيذ مبسط بشكل متعمد للمثال. يفترض وجود نسخة تطبيق واحدة وتخزين الحالة في الذاكرة، وهو ما لن يكون مناسبًا للاستخدام الإنتاجي. قد تستخدم الحلول الأكثر قوة Redis لتحديد السرعة الموزع أو تنفيذ أنظمة قائمة على الطابور لزيادة قابلية التوسع.

يتم استخدام هذا المحدد للسرعة قبل كل طلب لمشروع غوتنبرغ:

async searchBooks(query, page = 1) {
    await this.initialize();
    await rateLimiter.wait();  // فرض حد للسرعة
    // ... بقية منطق البحث
}

async getBookDetails(bookUrl) {
    await this.initialize();
    await rateLimiter.wait();  // فرض حد للسرعة
    // ... بقية منطق التفاصيل
}

تحديد البوت بوضوح

يساعد وكيل المستخدم المخصص مسؤولي المواقع على فهم من يصل إلى موقعهم ولماذا. تتيح هذه الشفافية لهم:

  1. الاتصال بك في حال وجود مشاكل
  2. مراقبة وتحليل حركة البوت بشكل منفصل عن مستخدمي البشر
  3. توفير إمكانية الوصول أو الدعم الأفضل للمستخدمين الشرعيين
await browserPage.setUserAgent('GutenbergScraper/1.0 (Educational Project)');

إدارة الموارد بكفاءة

يمكن أن يكون Chrome مُستهلكًا للذاكرة، خاصة عند تشغيل عدة حالات. إغلاق صفحات المتصفح بشكل صحيح بعد الاستخدام يمنع تسرب الذاكرة ويضمن تشغيل تطبيقك بكفاءة، حتى عند التعامل مع العديد من الطلبات:

try {
    // ... منطق الخدش
} finally {
    await browserPage.close();  // تحرير الذاكرة وموارد النظام
}

من خلال اتباع هذه الممارسات، نقوم بإنشاء خداشة فعالة ومحترمة للموارد التي تصل إليها. هذا مهم بشكل خاص عند العمل مع موارد عامة قيمة مثل Project Gutenberg.

الحصول على البيانات من الويب في السحابة

تستفيد التطبيق من الهندسة المعمارية السحابية الحديثة والحاويات من خلال منصة التطبيقات في DigitalOcean. يوفر هذا النهج توازنًا مثاليًا بين بساطة التطوير وموثوقية الإنتاج.

قوة منصة التطبيقات

تبسط منصة التطبيقات عملية النشر من خلال التعامل مع:

  • تكوين خادم الويب
  • إدارة شهادة SSL
  • تحديثات الأمان
  • توازن الحمل
  • رصد الموارد

هذا يسمح لنا بالتركيز على كود التطبيق بينما تدير منصة التطبيقات البنية التحتية.

Chrome دون رأس في حاوية

يستخدم جوهر وظيفتنا للحصول على البيانات Puppeteer، الذي يوفر واجهة برمجية عالية المستوى للتحكم بـ Chrome بشكل برمجي. ها هي كيفية إعداد واستخدام Puppeteer في تطبيقنا:

const puppeteer = require('puppeteer');

class BookService {
    constructor() {
        this.baseUrl = 'https://www.gutenberg.org';
        this.browser = null;
    }

    async initialize() {
        if (!this.browser) {
            // إضافة تسجيل بيانات البيئة لأغراض تصحيح الأخطاء
            console.log('Environment details:', {
                PUPPETEER_EXECUTABLE_PATH: process.env.PUPPETEER_EXECUTABLE_PATH,
                CHROME_PATH: process.env.CHROME_PATH,
                NODE_ENV: process.env.NODE_ENV
            });

            const options = {
                headless: 'new',
                args: [
                    '--no-sandbox',
                    '--disable-setuid-sandbox',
                    '--disable-dev-shm-usage',
                    '--disable-gpu',
                    '--disable-extensions',
                    '--disable-software-rasterizer',
                    '--window-size=1280,800',
                    '--user-agent=GutenbergScraper/1.0 (+https://github.com/wadewegner/doappplat-puppeteer-sample) Chromium/120.0.0.0'
                ],
                executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || '/usr/bin/chromium-browser',
                defaultViewport: {
                    width: 1280,
                    height: 800
                }
            };

            this.browser = await puppeteer.launch(options);
        }
    }

    // مثال على الحصول على البيانات باستخدام Puppeteer
    async searchBooks(query, page = 1) {
        await this.initialize();
        await rateLimiter.wait();

        const browserPage = await this.browser.newPage();
        try {
            // تعيين رؤوس لتقليد متصفح حقيقي وتحديد بوتنا
            await browserPage.setExtraHTTPHeaders({
                'Accept-Language': 'en-US,en;q=0.9',
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                'Connection': 'keep-alive',
                'Upgrade-Insecure-Requests': '1',
                'X-Bot-Info': 'GutenbergScraper - A tool for searching Project Gutenberg'
            });

            const searchUrl = `${this.baseUrl}/ebooks/search/?query=${encodeURIComponent(query)}&start_index=${(page - 1) * 24}`;
            await browserPage.goto(searchUrl, { waitUntil: 'networkidle0' });
            
            // ... باقي منطق البحث
        } finally {
            await browserPage.close();  // تنظيف دائم
        }
    }
}

يتيح هذا الإعداد لنا:

  • تشغيل Chrome في وضع الرأسي (دون واجهة مستخدم)
  • تنفيذ JavaScript في سياق صفحات الويب
  • إدارة موارد المتصفح بأمان
  • العمل بشكل موثوق في بيئة محاكاة

يتضمن الإعداد أيضًا عدة تكوينات مهمة للتشغيل في بيئة محاكاة:

  1. وسيطات Chrome الصحيحة: علامات أساسية مثل --no-sandbox و --disable-dev-shm-usage للتشغيل في الحاويات
  2. مسار يتفهم البيئة: يستخدم مسار تنفيذ Chrome الصحيح من متغيرات البيئة
  3. إدارة الموارد: تعيين حجم العرض وتعطيل الميزات غير الضرورية
  4. هوية بوت احترافية: وكيل مستخدم ورؤوس HTTP واضحة تحدد جهاز الحصول على البيانات الخاص بنا
  5. معالجة الأخطاء: تنظيف صفحات المتصفح بشكل صحيح لمنع تسرب الذاكرة

على الرغم من أن Puppeteer يجعل من السهل التحكم في Chrome بشكل برمجي، إلا أن تشغيله في حاوية يتطلب وجود التبعيات النظامية والتكوين الصحيح. دعنا نلقي نظرة على كيفية إعداد هذا في بيئة Docker الخاصة بنا.

Docker: ضمان بيئات متسقة

أحد أكبر التحديات في نشر مقتطفات الويب هو ضمان أنها تعمل بنفس الطريقة في التطوير والإنتاج. يمكن أن تعمل مقتطفات البيانات الخاصة بك بشكل مثالي على جهازك المحلي ولكن تفشل في السحابة بسبب عدم وجود التبعيات الضرورية أو التكوينات النظامية المختلفة. يحل Docker هذه المشكلة عن طريق تعبئة كل ما تحتاجه التطبيق – بدءًا من Node.js إلى Chrome نفسه – في حاوية واحدة تعمل بنفس الطريقة في كل مكان.

يُعين ملف Dockerfile الخاص بنا هذه البيئة المتسقة:

FROM node:18-alpine

# تثبيت Chromium والتبعيات
RUN apk add --no-cache \
    chromium \
    nss \
    freetype \
    harfbuzz \
    ca-certificates \
    ttf-freefont \
    dumb-init

# تعيين المتغيرات البيئية
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser \
    PUPPETEER_DISABLE_DEV_SHM_USAGE=true

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

التطوير إلى النشر

لنتابع كيفية تشغيل هذا المشروع:

1. التطوير المحلي

أولاً، قم بنسخ مستودع المثال إلى حسابك على GitHub. هذا يمنحك نسخة خاصة بك للعمل عليها ونشرها. ثم انسخ نسختك محليًا:

# انسخ نسختك
git clone https://github.com/YOUR-USERNAME/doappplat-puppeteer-sample.git
cd doappplat-puppeteer-sample

# قم بالبناء والتشغيل باستخدام Docker
docker build -t gutenberg-scraper .
docker run -p 8080:8080 gutenberg-scraper

2. فهم الشفرة

التطبيق مبني حول ثلاث مكونات رئيسية:

  1. خدمة الكتب: تتعامل مع جلب البيانات واستخراجها من الويب

    async searchBooks(query, page = 1) {
     await this.initialize();
     await rateLimiter.wait();
    
     const itemsPerPage = 24;
     const searchUrl = `${this.baseUrl}/ebooks/search/?query=${encodeURIComponent(query)}&start_index=${(page - 1) * itemsPerPage}`;
     
     // ... منطق الجلب
    }
    
  2. خادم Express: يدير المسارات ويقوم بتقديم القوالب

    app.get('/book/:url(*)', async (req, res) => {
     try {
         const bookUrl = req.params.url;
         const bookDetails = await bookService.getBookDetails(bookUrl);
         res.render('book', { book: bookDetails, error: null });
     } catch (error) {
         // معالجة الأخطاء
     }
    });
    
  3. واجهات المستخدم الأمامية: واجهة مستخدم نظيفة ومتجاوبة باستخدام Bootstrap

    <div class="card book-card h-100">
     <div class="card-body">
         <span class="badge bg-secondary downloads-badge">
             <%= book.downloads.toLocaleString() %> تنزيل
         </span>
         <h5 class="card-title"><%= book.title %></h5>
         <!-- ... عناصر واجهة المستخدم الأخرى ... -->
     </div>
    </div>
    

3. نشر إلى DigitalOcean

الآن بعد أن لديك نسختك من المستودع، يكون نشرها على منصة التطبيقات في DigitalOcean سهلاً:

  1. إنشاء تطبيق جديد على منصة التطبيقات
  2. الاتصال بالمستودع الخاص بك
  3. في الموارد، احذف المورد الثاني (الذي ليس ملف Dockerfile)؛ حيث يتم إنشاؤه تلقائياً بواسطة منصة التطبيقات ولا يلزم
  4. نشر عن طريق النقر على إنشاء الموارد

سيتم بناء التطبيق تلقائياً ونشره، مع منصة التطبيقات تتولى تفاصيل البنية التحتية بالكامل.

الاستنتاج

تبرز هذه الأداة المساعدة لـ Project Gutenberg كيفية بناء تطبيق ويب عملي باستخدام تقنيات السحابة الحديثة. من خلال دمج Puppeteer للحصول على بعض من تطويقات الويب، و Docker لتحجيم الحاويات، و DigitalOcean’s App Platform للنشر، قمنا بإنشاء حلاً قويًا وسهل الصيانة.

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

تفقد المشروع على GitHub لمعرفة المزيد ونشر نسختك الخاصة!

Source:
https://www.digitalocean.com/community/tutorials/build-a-puppeteer-web-scrapper-with-docker-and-app-platform