Puppeteer से जुड़ी समस्या हल करना

बिना ग्राफ़िक यूज़र इंटरफ़ेस वाला Chrome, Windows पर लॉन्च नहीं होता

Chrome की कुछ नीतियां, Chrome या Chromium को कुछ एक्सटेंशन के साथ चलाने के लिए मजबूर कर सकती हैं.

Puppeteer ने डिफ़ॉल्ट रूप से --disable-extensions फ़्लैग पास किया है. इसलिए, ऐसी नीतियों के चालू रहने पर यह लॉन्च नहीं हो पाता.

इससे बचने के लिए, फ़्लैग के बिना चलने की कोशिश करें:

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

संदर्भ: समस्या 3681.

UNIX पर बिना ग्राफ़िक यूज़र इंटरफ़ेस वाला Chrome लॉन्च नहीं होता

पक्का करें कि सभी ज़रूरी डिपेंडेंसी इंस्टॉल कर ली गई हों. किसी Linux मशीन पर ldd chrome | grep not चलाकर, यह देखा जा सकता है कि कौनसी डिपेंडेंसी मौजूद नहीं हैं.

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

डिपेंडेंसी इंस्टॉल करने के बाद, आपको इस कमांड का इस्तेमाल करके एनएसएस लाइब्रेरी को अपडेट करना होगा

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

इसका मतलब है कि ब्राउज़र डाउनलोड कर लिया गया था, लेकिन उसे सही तरीके से नहीं निकाला जा सका. सबसे आम वजह Node.js v14.0.0 में हुई गड़बड़ी है जिसकी वजह से extract-zip टूट गया. Puppeteer मॉड्यूल का इस्तेमाल करके, ब्राउज़र से डाउनलोड की गई फ़ाइलों को सही जगह पर एक्सट्रैक्ट किया जाता है. यह गड़बड़ी Node.js v14.1.0 में ठीक कर दी गई है. इसलिए, पक्का करें कि इस या इसके बाद के वर्शन का इस्तेमाल किया जा रहा हो.

Chrome Linux सैंडबॉक्स सेट अप करना

होस्ट एनवायरमेंट को गैर-भरोसेमंद वेब कॉन्टेंट से सुरक्षित रखने के लिए, Chrome सैंडबॉक्सिंग की कई लेयर का इस्तेमाल करता है. यह ठीक से काम करे, इसके लिए पहले होस्ट को कॉन्फ़िगर किया जाना चाहिए. अगर Chrome के लिए इस्तेमाल करने के लिए कोई अच्छा सैंडबॉक्स नहीं है, तो यह No usable sandbox! गड़बड़ी के साथ क्रैश हो जाएगा.

अगर आपको Chrome में खोले गए कॉन्टेंट पर पूरा भरोसा है, तो आपके पास --no-sandbox तर्क के साथ Chrome को लॉन्च करने का विकल्प होता है:

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

Chromium में सैंडबॉक्स को कॉन्फ़िगर करने के दो तरीके हैं.

उपयोगकर्ता नाम स्थान की क्लोनिंग सिर्फ़ आधुनिक कर्नेल के साथ काम करती है. आम तौर पर, जिन उपयोगकर्ताओं के पास नेमस्पेस नहीं होते हैं उन्हें चालू करना आसान होता है. हालांकि, ये नॉन-रूट प्रोसेस के लिए कर्नेल हमले की सतह को खोल सकते हैं, ताकि कर्नेल के खास अधिकारों पर स्विच किया जा सके.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[alternative] setuid सैंडबॉक्स को सेटअप करना

setuid Sandbox एक स्टैंडअलोन एक्ज़ीक्यूटेबल के तौर पर आता है. यह 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

डिफ़ॉल्ट रूप से, CHROME_DEVEL_SANDBOX एनवायरमेंट वैरिएबल को एक्सपोर्ट किया जा सकता है. इस मामले में, ~/.bashrc या .zshenv में यह जोड़ें:

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

Travis CI पर Puppeteer चलाएं

हमने 6.0.0 वर्शन तक Travis CI पर Puppeteer के लिए अपने टेस्ट किए. इसके बाद, हम GitHub की कार्रवाइयों पर माइग्रेट हो गए. रेफ़रंस के लिए, .travis.yml (5.5.0 वर्शन) देखें.

सबसे सही कुछ तरीके यहां दिए गए हैं:

  • Chromium को बिना ग्राफ़िक यूज़र इंटरफ़ेस वाले ब्राउज़र में चलाने के लिए, xvfb सेवा लॉन्च की जानी चाहिए
  • डिफ़ॉल्ट रूप से, Travis पर Xenial Linux पर चलता है
  • डिफ़ॉल्ट रूप से npm install पर चलता है
  • node_modules को डिफ़ॉल्ट रूप से कैश मेमोरी में सेव किया जाता है

.travis.yml ऐसी दिख सकती है:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

CircleCI पर Puppeteer चलाएं

  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 की मदद से इंस्टॉल करना ज़रूरी होगा. इसलिए, Teachtreeslight/puppeteer ऑर्ब (instructions) का इस्तेमाल करें या इसके सोर्स के कुछ हिस्सों को अपने कॉन्फ़िगरेशन में चिपकाएं.
  3. आखिर में, अगर आपने Jest के ज़रिए Puppeteer का इस्तेमाल किया, तो आपको बच्चों में होने वाली प्रोसेस में गड़बड़ी हो सकती है: shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11) ऐसा इसलिए हो सकता है, क्योंकि Jest, आपके कंटेनर (2) में अनुमति वाली संख्या के बजाय, पूरी मशीन (36) पर प्रोसेस की संख्या का पता अपने-आप लगा लेता है. इसे ठीक करने के लिए, अपने टेस्ट कमांड में jest --maxWorkers=2 सेट करें.

Docker में Puppeteer चलाएं

Docker में बिना ग्राफ़िक यूज़र इंटरफ़ेस वाले Chrome को चलाना और उसे चलाना मुश्किल हो सकता है. बंडल किए गए जिस Chromium को Puppeteer ने इंस्टॉल किया है उसमें शेयर की गई ज़रूरी लाइब्रेरी डिपेंडेंसी मौजूद नहीं हैं.

इसे ठीक करने के लिए, आपको अपनी Dockerfile में अनुपलब्ध डिपेंडेंसी और नया Chromium पैकेज इंस्टॉल करना होगा:

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 पर एक उदाहरण दिया गया है जिसमें बताया गया है कि App Engine Flex (नोड) पर चल रहे वेब सर्वर से इस Dockerfile को कैसे चलाया जाता है.

अल्पाइन पर दौड़ें

Alpine पर इस्तेमाल किया जाने वाला सबसे नया Chromium पैकेज 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 64 एमबी के /dev/shm शेयर किए गए मेमोरी स्पेस वाला कंटेनर चलाता है. यह Chrome के लिए आम तौर पर बहुत छोटा है और इससे बड़े पेज रेंडर करते समय Chrome क्रैश हो सकता है. इसे ठीक करने के लिए, कंटेनर को docker run --shm-size=1gb के साथ चलाएं, ताकि /dev/shm का साइज़ बढ़ाया जा सके. Chrome 65 से, अब इसकी ज़रूरत नहीं है. इसके बजाय, --disable-dev-shm-usage फ़्लैग के साथ ब्राउज़र लॉन्च करें:

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

इससे शेयर की गई मेमोरी फ़ाइलों को /dev/shm के बजाय, /tmp में सेव किया जाता है. इसके बारे में ज़्यादा जानने के लिए, crbug.com/736452 पर जाएं.

क्या Chrome लॉन्च करते समय आपको दूसरी अजीब गड़बड़ियां दिखती हैं? इसे स्थानीय तौर पर बनाते समय, docker run --cap-add=SYS_ADMIN के साथ अपना कंटेनर चलाएं. Dockerfile, pptr उपयोगकर्ता को गैर-खास अधिकारों वाले उपयोगकर्ता के तौर पर जोड़ता है. इसलिए, हो सकता है कि उसके पास सभी ज़रूरी अधिकार न हों.

अगर आपको कहीं बहुत सारे ज़ॉम्बी के अनुभव हो रहे हैं, तो dumb-init PID=1 के साथ प्रोसेस के लिए एक खास तरीका अपनाया जाता है. इससे कुछ मामलों (जैसे, Docker के साथ) में Chrome को सही तरीके से बंद करना मुश्किल हो जाता है.

क्लाउड में Puppeteer चलाएं

Google App Engine पर

App Engine स्टैंडर्ड एनवायरमेंट का Node.js रनटाइम, बिना ग्राफ़िक यूज़र इंटरफ़ेस वाला Chrome चलाने के लिए ज़रूरी सभी सिस्टम पैकेज के साथ आता है.

puppeteer का इस्तेमाल करने के लिए, अपने package.json में मॉड्यूल को डिपेंडेंसी के तौर पर शामिल करें और Google App Engine पर डिप्लॉय करें. App Engine पर puppeteer का इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, आधिकारिक ट्यूटोरियल पढ़ें.

Google Cloud Functions पर

Google Cloud Functions का Node.js 10 रनटाइम, बिना ग्राफ़िक यूज़र इंटरफ़ेस वाला Chrome चलाने के लिए ज़रूरी सभी सिस्टम पैकेज के साथ आता है.

puppeteer का इस्तेमाल करने के लिए, अपने package.json में मॉड्यूल को डिपेंडेंसी के तौर पर सूची में शामिल करें. साथ ही, nodejs10 रनटाइम का इस्तेमाल करके, अपने फ़ंक्शन को Google Cloud Functions में डिप्लॉय करें.

Google Cloud Run पर Puppeteer चलाएं

Google Cloud Run का डिफ़ॉल्ट Node.js रनटाइम, ऐसे सिस्टम पैकेज के साथ नहीं आता जो हेडलेस Chrome को चलाने के लिए ज़रूरी हैं. अपना Dockerfile सेट अप करें और वे डिपेंडेंसी शामिल करें जो उपलब्ध नहीं हैं.

हेरोकू पर

Heroku पर Puppeteer को चलाने के लिए कुछ अतिरिक्त डिपेंडेंसी की ज़रूरत होती है. ये ऐसी चीज़ें होती हैं जो Heroku के Linux बॉक्स में शामिल नहीं होती. डिप्लॉयमेंट पर डिपेंडेंसी जोड़ने के लिए, Settings > Buildpacks में जाकर, अपने ऐप्लिकेशन के बिल्डपैक की सूची में Puppeteer Heroku बिल्डपैक को जोड़ें.

बिल्डपैक का यूआरएल https://github.com/jontewks/puppeteer-heroku-buildpack है

Puppeteer को लॉन्च करते समय '--no-sandbox' मोड का ज़रूर इस्तेमाल करें. इसे आपके .launch() कॉल में तर्क के तौर पर पास करके किया जा सकता है: puppeteer.launch({ args: ['--no-sandbox'] });.

'बिल्डपैक जोड़ें' पर क्लिक करने के बाद, उस यूआरएल को इनपुट में चिपकाएं और सेव करें पर क्लिक करें. अगले डिप्लॉयमेंट पर, आपका ऐप्लिकेशन उन ज़रूरी चीज़ों को भी इंस्टॉल करेगा जिन्हें Puppeteer को चलाने के लिए ज़रूरत होगी.

अगर आपको चाइनीज़, जैपनीज़ या कोरियन वर्णों को रेंडर करना है, तो आपको https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack जैसी अतिरिक्त फ़ॉन्ट फ़ाइलों वाले बिल्डपैक का इस्तेमाल करना पड़ सकता है

@timleland की एक और गाइड भी है, जिसमें सैंपल प्रोजेक्ट शामिल है.

AWS Lambda पर

AWS Lambda सीमाएं डिप्लॉयमेंट पैकेज का साइज़ ~50 एमबी तक होना चाहिए. यह Lambda पर बिना ग्राफ़िक वाला Chrome (और इस वजह से Puppeteer) चलाने में चुनौतियां आती हैं. समुदाय ने कुछ संसाधन इकट्ठा किए हैं, जो इन समस्याओं को हल करने में मदद करते हैं:

Amazon-Linux पर चलने वाला AWS EC2 इंस्टेंस

अगर आपकी CI/CD पाइपलाइन में EC2 इंस्टेंस पर amazon-linux चल रहा है और आप amazon-linux में Puppeteer टेस्ट चलाना चाहते हैं, तो यह तरीका अपनाएं.

  1. Chromium इंस्टॉल करने के लिए, आपको सबसे पहले amazon-linux-extras को चालू करना होगा, जो EPEL (Enterprise Linux के लिए अतिरिक्त पैकेज) का हिस्सा है:

    sudo amazon-linux-extras install epel -y
    
  2. इसके बाद, Chromium इंस्टॉल करें:

    sudo yum install -y chromium
    

अब Puppeteer आपके टेस्ट करने के लिए, Chromium लॉन्च कर सकता है. अगर आपने ईपीईएल चालू नहीं किया है और npm install के हिस्से के तौर पर Chromium इंस्टॉल करना जारी रखता है, तो libatk-1.0.so.0 और कई अन्य पैकेज उपलब्ध न होने की वजह से, Puppeteer Chromium को लॉन्च नहीं कर सकता.

कोड ट्रांस्पिलेशन से जुड़ी समस्याएं

अगर babel या TypeScript जैसे JavaScript ट्रांसपाइलर का इस्तेमाल किया जा रहा है, तो हो सकता है कि evaluate() को एसिंक्रोनस फ़ंक्शन के साथ कॉल करने से काम न हो. इसकी वजह यह है कि puppeteer, फ़ंक्शन को क्रम में लगाने के लिए Function.prototype.toString() का इस्तेमाल करता है. वहीं, ट्रांसपाइलर आउटपुट कोड को इस तरह बदल सकता है कि वह puppeteer के साथ काम न करता हो.

इस समस्या को ठीक करने का एक तरीका यह है कि ट्रांसपाइलर को कोड के साथ गड़बड़ी न करने का निर्देश दिया जाए. उदाहरण के लिए, सबसे नए ecma वर्शन ("target": "es2018") का इस्तेमाल करने के लिए TypeScript को कॉन्फ़िगर करें. इसके लिए, फ़ंक्शन के बजाय स्ट्रिंग टेंप्लेट का इस्तेमाल किया जा सकता है:

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