تم نشره في 4 يونيو 2018 على مدونة الشركات بدون متصفح.يسرنا أن نعلن أننا تجاوزنا مؤخرًا خط
مليوني جلسة عمل ! هذه
الملايين من لقطات الشاشة المولدة وملفات PDF المطبوعة والمواقع التي تم اختبارها. لقد فعلنا كل شيء تقريبًا يمكنك التفكير فيه باستخدام متصفح بلا رأس.
على الرغم من أنه من الجميل تحقيق مثل هذا المعلم ، ولكن في الطريق كان هناك
الكثير من التداخل والمشاكل. نظرًا للكم الهائل من حركة المرور المستلمة ، أود أن أتراجع خطوة للوراء وأن أذكر التوصيات العامة لإطلاق متصفحات بلا رأس (و
دمية ) في الإنتاج.
إليك بعض النصائح.
1. لا تستخدم متصفح بدون رأس على الإطلاق

استهلاك موارد الكروم بدون رأس
بأي حال من الأحوال ، إذا كان ذلك ممكنًا ،
لا تبدأ المتصفح على الإطلاق في وضع بلا رأس . خاصة على نفس البنية التحتية للتطبيق الخاص بك (انظر أعلاه). المتصفح بدون رأس ، لا يمكن التنبؤ به ، وشراهة وتكاثر مثل السيد ميسيكس من ريك ومورتي. يمكن تنفيذ كل ما يمكن أن يفعله المتصفح تقريبًا (باستثناء إقحام JavaScript وتشغيله) باستخدام أدوات Linux البسيطة. تقدم مكتبات Cheerio وغيرها واجهة Node API أنيقة لاسترداد البيانات باستخدام طلبات HTTP والقص ، إذا كان هذا هو هدفك.
على سبيل المثال ، يمكنك اختيار صفحة (بافتراض أنها نوع من HTML) ونسخها باستخدام أوامر بسيطة مثل هذه:
import cheerio from 'cheerio'; import fetch from 'node-fetch'; async function getPrice(url) { const res = await fetch(url); const html = await res.test(); const $ = cheerio.load(html); return $('buy-now.price').text(); } getPrice('https://my-cool-website.com/');
من الواضح أن البرنامج النصي لا يغطي جميع حالات الاستخدام ، وإذا قرأت هذه المقالة ، فعلى الأرجح
ستضطر إلى استخدام متصفح بلا رأس. لذلك دعونا نبدأ.
2. لا تقم بتشغيل متصفح بدون رأس
لقد واجهنا العديد من المستخدمين الذين يحاولون الحفاظ على تشغيل المتصفح حتى إذا لم يكن قيد الاستخدام (مع اتصالات مفتوحة). على الرغم من أن هذه قد تكون إستراتيجية جيدة لتسريع الجلسة ، إلا أنها ستنهار في غضون ساعات قليلة. إلى حد كبير لأن المتصفحات
تحب تخزين كل شيء على التوالي وتخزين الذاكرة بشكل تدريجي. بمجرد التوقف عن استخدام المتصفح بشكل مكثف ، قم بإغلاقه على الفور!
import puppeteer from 'puppeteer'; async function run() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.example.com/');
في المتصفح بدون متصفح ، نقوم عادةً بإصلاح هذا الخطأ للمستخدمين بأنفسنا ، ونقوم دائمًا بتعيين نوع من المؤقت للجلسة وإغلاق المتصفح عند قطع اتصال WebSocket. ولكن إذا لم تستخدم خدمتنا أو
صورة Docker الاحتياطية ، فتأكد من إغلاق المتصفح
تلقائيًا ، لأنه سيكون مزعجًا عندما يسقط كل شيء في منتصف الليل.
3. صفحة صديقك
كن حذرًا عند المترجمين مثل بابل أو المطبعية ، لأنهم يحبون إنشاء وظائف مساعدة ويفترضون أنه يمكن الوصول إليها عن طريق الإغلاق. أي ، قد لا يعمل رد الاتصال التقييم بشكل صحيح.
لدى Puppeteer العديد من الطرق الرائعة ، مثل تخزين محددات DOM وأشياء أخرى في بيئة العقدة. على الرغم من أنها مريحة للغاية ، يمكنك بسهولة تصوير نفسك في القدم إذا كان هناك شيء على الصفحة يجبر عقدة DOM على
التحور . قد لا يكون هذا رائعًا ، ولكن في الواقع من الأفضل القيام بكل العمل على جانب المتصفح
في سياق المتصفح . هذا يعني عادة تحميل الصفحة.
page.evaulate
على كل العمل الذي يجب القيام به.
على سبيل المثال ، بدلاً من شيء من هذا القبيل (
ثلاثة إجراءات غير متزامنة):
const $anchor = await page.$('a.buy-now'); const link = await $anchor.getProperty('href'); await $anchor.click(); return link;
من الأفضل القيام بذلك (إجراء غير متزامن):
await page.evaluate(() => { const $anchor = document.querySelector('a.buy-now'); const text = $anchor.href; $anchor.click(); });
ميزة أخرى لتغليف الإجراءات في مكالمة
evaluate
هي إمكانية النقل: يمكن تشغيل هذا الرمز في المتصفح للتحقق بدلاً من محاولة إعادة كتابة رمز العقدة. بالطبع ، يوصى دائمًا
باستخدام مصحح أخطاء لتقليل وقت التطوير.
القاعدة الأساسية البسيطة هي حساب عدد
await
أو في الشفرة. إذا كان هناك أكثر من واحد ، فمن الأفضل على الأرجح تشغيل الكود داخل
page.evaluate
. والسبب هو أن جميع الإجراءات غير المتزامنة تتحرك ذهابًا وإيابًا بين وقت تشغيل العقدة والمتصفح ، مما يعني تسلسل JSON المستمر وإلغاء التسلسل. على الرغم من عدم وجود مثل هذا المبلغ الضخم من التحليل (لأن كل شيء مدعوم بواسطة WebSockets) ، فإنه لا يزال يستغرق وقتًا ، والذي يُنفق بشكل أفضل على شيء آخر.
4. موازية المتصفحات ، وليس صفحات الويب
لذلك ، أدركنا أن تشغيل المتصفح ليس جيدًا ونحتاج إلى القيام بذلك فقط في حالة الطوارئ. النصيحة التالية هي تشغيل جلسة واحدة فقط لكل متصفح. على الرغم من أنه من الممكن في الواقع حفظ الموارد عن طريق موازنة العمل عبر
pages
، ولكن إذا سقطت صفحة واحدة ، فيمكن أن تتعطل المتصفح بأكمله. بالإضافة إلى ذلك ، ليس من المضمون أن تكون كل صفحة نظيفة تمامًا (يمكن أن تصبح ملفات تعريف الارتباط والتخزين صداعًا ،
كما نرى ).
بدلاً من ذلك:
import puppeteer from 'puppeteer';
من الأفضل القيام بذلك:
import puppeteer from 'puppeteer'; const runJob = async (url) {
يحصل كل مثيل متصفح جديد على نظيف
--user-data-dir
(
ما لم يذكر خلاف ذلك ). أي أنه يتم معالجتها بالكامل كجلسة جديدة جديدة. إذا تعطل Chrome لسبب ما ، فلن يسحب جلسات أخرى معه.
5. حدود الطابور والتزامن
واحدة من الميزات الرئيسية للمتصفح هي القدرة على الحد بدقة من التوازي والانتظار. لذا فإن تطبيقات العميل تعمل فقط على
puppeteer.connect
، لكنها لا تفكر في تنفيذ قائمة الانتظار. يمنع هذا قدرًا كبيرًا من المشاكل ، بشكل أساسي مع مثيلات Chrome المتزامنة التي تلتهم جميع الموارد المتاحة لتطبيقك.
الطريقة الأفضل والأسهل هي التقاط صورة Docker وتشغيلها بالمعلمات الضرورية:
# Pull in Puppeteer@1.4.0 support $ docker pull browserless/chrome:release-puppeteer-1.4.0 $ docker run -e "MAX_CONCURRENT_SESSIONS=10" browserless/chrome:release-puppeteer-1.4.0
هذا يحد من عدد الطلبات المتزامنة إلى عشرة (بما في ذلك جلسات التصحيح والمزيد). تم تكوين قائمة الانتظار بواسطة المتغير
MAX_QUEUE_LENGTH
. عادةً ، يمكنك تنفيذ ما يقرب من 10 طلبات متزامنة لكل غيغابايت من الذاكرة. يمكن أن تختلف النسبة المئوية لاستخدام وحدة المعالجة المركزية للمهام المختلفة ، ولكن في الأساس ستحتاج إلى الكثير والكثير من ذاكرة الوصول العشوائي.
6. لا تنسى page.waitForNavigation
واحدة من أكثر المشاكل الشائعة التي واجهناها هي الإجراءات التي تبدأ في تحميل الصفحات مع الإنهاء المفاجئ للبرامج النصية. وذلك لأن الإجراءات التي تؤدي إلى
pageload
غالبًا ما تسبب ابتلاع العمل اللاحق. للتغلب على المشكلة ، تحتاج عادةً إلى استدعاء إجراء تحميل الصفحة - وبعد انتظار التحميل مباشرة.
على سبيل المثال ، لا تعمل هذه
console.log
في مكان واحد (
انظر العرض ):
await page.goto('https://example.com'); await page.click('a'); const title = await page.title(); console.log(title);
لكنها
تعمل في مكان آخر (
انظر العرض ).
await page.goto('https://example.com'); page.click('a'); await page.waitForNavigation(); const title = await page.title(); console.log(title);
يمكنك قراءة المزيد عن waitForNavigation
هنا . تحتوي هذه الوظيفة تقريبًا على نفس معلمات الواجهة مثل
page.goto
، ولكن فقط مع جزء "الانتظار".
7. استخدم Docker لكل ما تحتاجه.
يحتاج Chrome إلى الكثير من التبعيات للعمل بشكل صحيح. حقا الكثير. حتى بعد تثبيت كل شيء ، يجب أن تقلق بشأن أشياء مثل الخطوط والعمليات الوهمية. لذلك ، من الأفضل استخدام نوع من الحاويات لوضع كل شيء هناك. تم تصميم Docker خصيصًا لهذه المهمة ، لأنه يمكنك تحديد كمية الموارد المتاحة وعزلها. إذا كنت ترغب في إنشاء
Dockerfile
الخاص بك ، فراجع أدناه جميع التبعيات اللازمة:
# Dependencies needed for packages downstream RUN apt-get update && apt-get install -y \ unzip \ fontconfig \ locales \ gconf-service \ libasound2 \ libatk1.0-0 \ libc6 \ libcairo2 \ libcups2 \ libdbus-1-3 \ libexpat1 \ libfontconfig1 \ libgcc1 \ libgconf-2-4 \ libgdk-pixbuf2.0-0 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libstdc++6 \ libx11-6 \ libx11-xcb1 \ libxcb1 \ libxcomposite1 \ libxcursor1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxi6 \ libxrandr2 \ libxrender1 \ libxss1 \ libxtst6 \ ca-certificates \ fonts-liberation \ libappindicator1 \ libnss3 \ lsb-release \ xdg-utils \ wget
ولتجنب عمليات الزومبي (وهو أمر شائع في Chrome) ، من الأفضل استخدام شيء مثل
dumb-init للتشغيل بشكل صحيح:
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init RUN chmod +x /usr/local/bin/dumb-init
إذا كنت تريد معرفة المزيد ، ألق نظرة على
ملف Dockerfile الخاص بنا .
8. تذكر اثنين من أوقات التشغيل المختلفة.
من المفيد أن نتذكر أن هناك وقتين لتشغيل جافا سكريبت (العقدة والمتصفح). يعد هذا أمرًا رائعًا لفصل المهام ، ولكن يحدث الالتباس حتمًا لأن بعض الطرق تتطلب تمرير ارتباط صريحًا بدلاً من الرفع.
على سبيل المثال ، خذ
page.evaluate
. في أعماق الأمعاء ، يوجد
تشفير حرفي للوظيفة ونقلها إلى Chrome . لذلك ،
لن تعمل أشياء مثل الإغلاق والمصاعد
على الإطلاق . إذا كنت بحاجة إلى تمرير بعض المراجع أو القيم إلى مكالمة التقييم ، فما عليك سوى إضافتها كوسيطة ستتم معالجتها بشكل صحيح.
وبالتالي ، بدلاً من الرجوع إلى
selector
عبر عمليات الإغلاق:
const anchor = 'a'; await page.goto('https://example.com/');
معلمة تمرير أفضل:
const anchor = 'a'; await page.goto('https://example.com/');
page.evaluate
إضافة وسيطة أو أكثر إلى دالة
page.evaluate
، لأنها متغيرة هنا. تأكد من الاستفادة من هذا!
المستقبل
نحن متفائلون بشكل لا يصدق حول مستقبل المتصفحات الخالية من الأتمتة وجميع الأتمتة التي يمكنهم تحقيقها. باستخدام أدوات فعالة مثل العرائس وعديمة المتصفح ، نأمل أن يكون تصحيح الأخطاء وتشغيل الأتمتة بدون رأس في الإنتاج أسهل وأسرع. قريبًا ، سنطلق
نظام الفوترة بالدفع الفوري للحسابات والوظائف التي ستساعدك على التأقلم بشكل أفضل مع عملك بدون رأس!