تحديد مشاكل Puppeteer وحلّها

تعذُّر تشغيل Chrome بلا واجهة مستخدم رسومية على نظام التشغيل Windows

قد تفرض بعض سياسات Chrome تشغيل Chrome أو Chromium باستخدام إضافات معيّنة.

يمرر محرّك الدمى علامة --disable-extensions تلقائيًا، وبالتالي يتعذّر تشغيله عندما تكون هذه السياسات نشطة.

للتغلب على هذا الأمر، يمكنك تجربة الجري بدون العلامة:

const browser = await puppeteer.launch({
  ignoreDefaultArgs: ['--disable-extensions'],
});

السياق: issue 3681.

عدم إطلاق Chrome بلا واجهة مستخدم رسومية على نظام التشغيل UNIX

تأكد من تثبيت جميع التبعيات الضرورية. يمكنك تشغيل ldd chrome | grep not على جهاز يعمل بنظام التشغيل Linux للتحقق من التبعيات المفقودة.

تبعيات Debian (Ubuntu)

ca-certificates
fonts-liberation
libappindicator3-1
libasound2
libatk-bridge2.0-0
libatk1.0-0
libc6
libcairo2
libcups2
libdbus-1-3
libexpat1
libfontconfig1
libgbm1
libgcc1
libglib2.0-0
libgtk-3-0
libnspr4
libnss3
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
lsb-release
wget
xdg-utils

تبعيات CentOS

alsa-lib.x86_64
atk.x86_64
cups-libs.x86_64
gtk3.x86_64
ipa-gothic-fonts
libXcomposite.x86_64
libXcursor.x86_64
libXdamage.x86_64
libXext.x86_64
libXi.x86_64
libXrandr.x86_64
libXScrnSaver.x86_64
libXtst.x86_64
pango.x86_64
xorg-x11-fonts-100dpi
xorg-x11-fonts-75dpi
xorg-x11-fonts-cyrillic
xorg-x11-fonts-misc
xorg-x11-fonts-Type1
xorg-x11-utils

بعد تثبيت التبعيات، عليك تحديث مكتبة nss باستخدام هذا الأمر.

yum update nss -y

الاطّلاع على المناقشات:

  • #290 - تحديد مشاكل نظام التشغيل Debian وحلّها
  • #391 - تحديد مشاكل CentOS وحلّها
  • #379 - تحديد مشاكل جبال الألب وحلّها

يوقف متصفّح Chrome بلا واجهة مستخدم رسوم وحدة معالجة الرسومات.

يتطلّب متصفّح Chrome ومتصفّح Chromium --use-gl=egl لتفعيل تسريع وحدة معالجة الرسومات في وضع التشغيل بلا واجهة مستخدم رسومية.

const browser = await puppeteer.launch({
  headless: true,
  args: ['--use-gl=egl'],
});

تم تنزيل Chrome ولكن يتعذر تشغيله على Node.js

إذا ظهرت لك رسالة خطأ مثل هذه عند محاولة تشغيل Chromium:

(node:15505) UnhandledPromiseRejectionWarning: Error: Failed to launch the browser process!
spawn /Users/.../node_modules/puppeteer/.local-chromium/mac-756035/chrome-mac/Chromium.app/Contents/MacOS/Chromium ENOENT

يعني هذا أنّه تم تنزيل المتصفّح ولكن تعذّر استخراجه بشكل صحيح. السبب الأكثر شيوعًا هو حدوث خطأ في الإصدار 14.0.0 من Node.js يعطّل extract-zip، وهي الوحدة التي يستخدمها Puppeteer لاستخراج عمليات التنزيل الخاصة بالمتصفّح إلى المكان الصحيح. تم إصلاح هذا الخطأ في الإصدار 14.1.0 من Node.js، لذا تأكد من تشغيل هذا الإصدار أو إصدار أحدث.

إعداد وضع الحماية في Chrome Linux

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

إذا كنت تثق تمامًا المحتوى الذي تفتحه في Chrome، يمكنك تشغيل Chrome باستخدام الوسيطة --no-sandbox:

const browser = await puppeteer.launch({
  args: ['--no-sandbox', '--disable-setuid-sandbox'],
});

هناك طريقتان لضبط وضع الحماية في Chromium.

لا يتم دعم نسخ مساحة اسم Sser إلا من خلال النواة الحديثة. بشكل عام، لا يكفي تفعيل مساحات اسم المستخدمين التي ليس لها امتياز، إلا أنّها قد تفتح مساحة أكبر من هجوم النواة للعمليات غير الجذر (بدون وضع حماية) للارتقاء بامتيازات النواة.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[بدلاً من ذلك] إعداد وضع الحماية setuid

يأتي وضع حماية setuid كتطبيق مستقل قابل للتنفيذ ويقع بجانب Chromium الذي ينزِّله برنامج Puppeteer. لا بأس في إعادة استخدام نفس وضع الحماية القابل للتنفيذ مع إصدارات مختلفة من Chromium، وبالتالي يمكن إجراء ما يلي مرة واحدة فقط لكل بيئة مضيف:

# cd to the downloaded instance
cd <project-dir-path>/node_modules/puppeteer/.local-chromium/linux-<revision>/chrome-linux/
sudo chown root:root chrome_sandbox
sudo chmod 4755 chrome_sandbox
# copy sandbox executable to a shared location
sudo cp -p chrome_sandbox /usr/local/sbin/chrome-devel-sandbox
# export CHROME_DEVEL_SANDBOX env variable
export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox

قد تحتاج إلى تصدير متغيّر env CHROME_DEVEL_SANDBOX تلقائيًا. في هذه الحالة، أضِف ما يلي إلى ~/.bashrc أو .zshenv:

export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox

تشغيل Puppeteer على Travis CI

أجرينا اختباراتنا لـ Puppeteer على Travis CI حتى الإصدار 6.0.0، وبعد ذلك انتقلنا إلى الإصدار GitHub Actions. يمكنك الاطّلاع على .travis.yml (الإصدار 5.5.0) كمرجع لك.

في ما يلي بعض أفضل الممارسات:

  • يجب تشغيل خدمة xvfb لتشغيل Chromium في الوضع غير المستند إلى واجهة مستخدم رسومية
  • يعمل على Xenial Linux على Travis بشكل تلقائي
  • يتم تشغيل npm install تلقائيًا.
  • يتم تخزين node_modules مؤقتًا بشكل تلقائي

قد يبدو .travis.yml على النحو التالي:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

تشغيل Puppeteer على CircleCI

  1. ابدأ بصورة NodeJS في الإعدادات. yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
  2. قد تحتاج التبعيات مثل libXtst6 إلى تثبيتها باستخدام apt-get، لذا استخدِم threetreeslight/puppeteer (instructions)، أو ألصِق أجزاءً من المصدر في إعداداتك الخاصة.
  3. أخيرًا، إذا كنت تستخدم Puppeteer من خلال Jest، فقد تواجه ظهور خطأ أثناء ظهور العمليات الفرعية: shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11) من المحتمل أن يكون سبب ذلك هو اكتشاف Jest تلقائيًا لعدد العمليات في الجهاز بالكامل (36) بدلاً من الرقم المسموح به في الحاوية (2). لإصلاح ذلك، اضبط jest --maxWorkers=2 في أمر الاختبار.

تشغيل Puppeteer في Docker

قد يكون تشغيل Chrome بلا واجهة مستخدم رسومية أمرًا صعبًا. تفتقر حزمة Chromium التي يثبّتها Puppeteer إلى تبعيات المكتبة المشتركة الضرورية.

لإصلاح الخطأ، يجب تثبيت التبعيات المفقودة وأحدث حزمة Chromium في ملف Dockerfile:

FROM node:14-slim

# Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others)
# Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer
# installs, work.
RUN apt-get update \
    && apt-get install -y wget gnupg \
    && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
    && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
    && apt-get update \
    && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
      --no-install-recommends \
    && rm -rf /var/lib/apt/lists/*

# If running Docker >= 1.13.0 use docker run's --init arg to reap zombie processes, otherwise
# uncomment the following lines to have `dumb-init` as PID 1
# ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_x86_64 /usr/local/bin/dumb-init
# RUN chmod +x /usr/local/bin/dumb-init
# ENTRYPOINT ["dumb-init", "--"]

# Uncomment to skip the chromium download when installing puppeteer. If you do,
# you'll need to launch puppeteer with:
#     browser.launch({executablePath: 'google-chrome-stable'})
# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true

# Install puppeteer so it's available in the container.
RUN npm init -y &&  \
    npm i puppeteer \
    # Add user so we don't need --no-sandbox.
    # same layer as npm install to keep re-chowned files from using up several hundred MBs more space
    && groupadd -r pptruser && useradd -r -g pptruser -G audio,video pptruser \
    && mkdir -p /home/pptruser/Downloads \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /node_modules \
    && chown -R pptruser:pptruser /package.json \
    && chown -R pptruser:pptruser /package-lock.json

# Run everything after as non-privileged user.
USER pptruser

CMD ["google-chrome-stable"]

أنشِئ الحاوية:

docker build -t puppeteer-chrome-linux .

شغِّل الحاوية عن طريق تمرير node -e "<yourscript.js content as a string>" باعتباره الأمر:

 docker run -i --init --rm --cap-add=SYS_ADMIN \
   --name puppeteer-chrome puppeteer-chrome-linux \
   node -e "`cat yourscript.js`"

هناك مثال كامل على https://github.com/ebidel/try-puppeteer يعرض كيفية تشغيل ملف Docker هذا من خادم ويب يعمل على App Engine Flex (Node).

الجري في جبال الألب

أحدث حزمة Chromium المتوافقة مع Alpine هي 100 والتي تقابل Puppeteer v13.5.0.

مثال على ملف Dockerfile:

FROM alpine

# Installs latest Chromium (100) package.
RUN apk add --no-cache \
      chromium \
      nss \
      freetype \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
      nodejs \
      yarn

...

# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Puppeteer v13.5.0 works with Chromium 100.
RUN yarn add puppeteer@13.5.0

# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -G pptruser pptruser \
    && mkdir -p /home/pptruser/Downloads /app \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /app

# Run everything after as non-privileged user.
USER pptruser

...

أفضل الممارسات مع Docker

يشغِّل Docker بشكلٍ تلقائي حاوية ذات مساحة ذاكرة مشتركة /dev/shm قدرها 64 ميغابايت. يكون هذا الأمر عادةً صغيرًا جدًا بالنسبة إلى Chrome وسيتسبب في تعطُّل Chrome عند عرض صفحات كبيرة. لحلّ هذه المشكلة، يمكنك تشغيل الحاوية باستخدام docker run --shm-size=1gb لزيادة حجم /dev/shm. بدءًا من الإصدار 65 من Chrome، لم يعُد ذلك ضروريًا. بدلاً من ذلك، عليك تشغيل المتصفّح الذي يتضمّن علامة --disable-dev-shm-usage:

const browser = await puppeteer.launch({
  args: ['--disable-dev-shm-usage'],
});

يؤدي هذا الإجراء إلى كتابة ملفات الذاكرة المشتركة في /tmp بدلاً من /dev/shm. راجِع crbug.com/736452.

هل تظهر لك أخطاء غريبة أخرى عند تشغيل Chrome؟ حاوِل تشغيل الحاوية باستخدام docker run --cap-add=SYS_ADMIN عند التطوير محليًا. وبما أنّ ملف Doockerfile يضيف مستخدم pptr كمستخدم غير محمي، قد لا يملك كل الامتيازات اللازمة.

dumb-init الأمر يستحق التحقق إذا كنت تواجه الكثير من عمليات التخلص من وحوش الزومبي في Chrome. هناك معالجة خاصة للعمليات في PID=1، ما يجعل من الصعب إنهاء Chrome بشكل صحيح في بعض الحالات (كما هو الحال مع Docker).

تشغيل Puppeteer في السحابة

على Google App Engine

يأتي وقت تشغيل Node.js في بيئة App Engine القياسية مع جميع حزم النظام اللازمة لتشغيل Chrome بلا واجهة مستخدم رسومية.

لاستخدام puppeteer، يجب إدراج الوحدة كتبعية في package.json ونشرها في Google App Engine. يمكنك قراءة المزيد عن استخدام puppeteer على App Engine من خلال اتّباع البرنامج التعليمي الرسمي.

على دوال Google Cloud

يأتي وقت تشغيل Node.js 10 لدوال Google Cloud مع جميع حزم النظام اللازمة لتشغيل Chrome بلا واجهة مستخدم رسومية.

لاستخدام puppeteer، يجب إدراج الوحدة كتبعية في package.json ونشر الدالة في دوال Google Cloud باستخدام وقت التشغيل nodejs10.

تشغيل Puppeteer على Google Cloud Run

ولا يأتي وقت تشغيل Node.js التلقائي لتشغيل Google Cloud مع حزم النظام اللازمة لتشغيل Chrome بلا واجهة مستخدم رسومية. عليك إعداد Dockerfile الخاصة بك وتضمين التبعيات غير المتوفّرة.

أون هيروكو

يتطلب تشغيل Puppeteer على Heroku بعض التبعيات الإضافية التي لا يتم تضمينها في مربع Linux الذي يدور حوله Heroku. لإضافة الاعتماديات عند النشر، أضِف حزمة تصميم Puppeteer Heroku إلى قائمة حِزم الإصدار لتطبيقك ضمن الإعدادات > Buildpack.

عنوان URL الخاص بـ Buildpack هو https://github.com/jontewks/puppeteer-heroku-buildpack.

تأكد من استخدام وضع '--no-sandbox' عند تشغيل Puppeteer. يمكن تنفيذ ذلك من خلال تمريره كوسيطة إلى طلب .launch(): puppeteer.launch({ args: ['--no-sandbox'] });.

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

إذا أردت عرض أحرف صينية أو يابانية أو كورية، قد تحتاج إلى استخدام حزمة تصميم مع ملفات خطوط إضافية مثل https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack.

هناك أيضًا دليل آخر من @timleland يتضمن نموذجًا لمشروع.

على AWS Lambda

يحدد AWS Lambda حجم حزم النشر بحوالي 50 ميغابايت. يمثل هذا تحديات لتشغيل Chrome بلا واجهة مستخدم رسومية (وبالتالي Puppeteer) على Lambda. لقد جمع المنتدى بعض الموارد التي تعمل على حل المشكلات:

مثيل AWS EC2 الذي يعمل على نظام Amazon-Linux

إذا كان لديك مثيل EC2 في Amazon-linux في مسار CI/CD وتريد إجراء اختبارات Puppeteer في amazon-linux، اتّبِع الخطوات التالية.

  1. لتثبيت Chromium، يجب أولاً تفعيل amazon-linux-extras، وهو جزء من EPEL (الحزم الإضافية لنظام Enterprise Linux):

    sudo amazon-linux-extras install epel -y
    
  2. بعد ذلك، ثبِّت Chromium:

    sudo yum install -y chromium
    

الآن يمكن لتطبيق Puppeteer إطلاق Chromium لإجراء اختباراتك. في حال عدم تفعيل EPEL ومواصلة تثبيت Chromium كجزء من npm install، لن يتمكّن Puppeteer من تشغيل Chromium بسبب عدم توفّر libatk-1.0.so.0 والعديد من الحزم الأخرى.

مشاكل في ترجمة الرمز

إذا كنت تستخدم أداة ترجمة JavaScript مثل babel أو TypeScript، قد لا يعمل استدعاء evaluate() بدالة غير متزامنة. ويرجع ذلك إلى أنّ puppeteer يستخدم Function.prototype.toString() لإنشاء تسلسل للدوال، فيما قد تغيّر برامج التحويل البرمجي رمز الإخراج بطريقة لا تتوافق مع puppeteer.

هناك بعض الحلول البديلة لحل هذه المشكلة من خلال إرشاد برنامج التحويل إلى عدم العبث بالرمز، على سبيل المثال، ضبط TypeScript لاستخدام أحدث إصدار ecma ("target": "es2018"). هناك حل بديل وهو استخدام نماذج السلاسل بدلاً من الدوال:

await page.evaluate(`(async() => {
   console.log('1');
})()`);