Memecahkan Masalah Puppeteer

Chrome Headless tidak diluncurkan di Windows

Beberapa kebijakan Chrome mungkin menerapkan Chrome atau Chromium dengan Ekstensi tertentu.

Puppeteer meneruskan flag --disable-extensions secara default sehingga gagal diluncurkan saat kebijakan tersebut aktif.

Untuk mengatasi hal ini, coba jalankan tanpa flag:

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

Konteks: masalah 3681.

Chrome Headless tidak diluncurkan di UNIX

Pastikan semua dependensi yang diperlukan telah diinstal. Anda dapat menjalankan ldd chrome | grep not di mesin Linux untuk memeriksa dependensi mana yang tidak ada.

Dependensi 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

Dependensi 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

Setelah menginstal dependensi, Anda perlu memperbarui library nss menggunakan perintah ini

yum update nss -y

Lihat diskusi:

  • #290 - Pemecahan masalah Debian
  • #391 - Pemecahan masalah CentOS
  • #379 - Pemecahan masalah Alpine

Chrome Headless menonaktifkan komposisi GPU

Chrome dan Chromium memerlukan --use-gl=egl untuk mengaktifkan akselerasi GPU dalam mode headless.

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

Chrome telah didownload tetapi gagal diluncurkan di Node.js

Jika Anda mendapatkan error yang terlihat seperti ini saat mencoba meluncurkan 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

Artinya, browser telah didownload tetapi gagal diekstrak dengan benar. Penyebab paling umum adalah bug di Node.js v14.0.0 yang merusak extract-zip, modul yang digunakan Puppeteer untuk mengekstrak download browser ke tempat yang tepat. Bug ini telah diperbaiki di Node.js v14.1.0, jadi pastikan Anda menjalankan versi tersebut atau yang lebih tinggi.

Menyiapkan sandbox Chrome Linux

Untuk melindungi lingkungan host dari konten web yang tidak tepercaya, Chrome menggunakan beberapa lapisan sandbox. Agar berfungsi dengan baik, {i>host<i} harus dikonfigurasi terlebih dahulu. Jika tidak ada sandbox yang baik untuk digunakan Chrome, Chrome akan mengalami error dan menampilkan error No usable sandbox!.

Jika Anda sangat memercayai konten yang dibuka di Chrome, Anda dapat meluncurkan Chrome dengan argumen --no-sandbox:

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

Ada 2 cara untuk mengonfigurasi sandbox di Chromium.

Kloning namespace Sser hanya didukung oleh kernel modern. Namespace pengguna yang tidak memiliki hak istimewa biasanya dapat diaktifkan, tetapi dapat membuka lebih banyak permukaan serangan kernel untuk proses non-root (yang tidak di-sandbox) guna meningkatkan hak istimewa kernel.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[alternatif] Menyiapkan sandbox setuid

Sandbox setuid tersedia sebagai file yang dapat dieksekusi mandiri dan terletak di samping Chromium yang didownload Puppeteer. Anda dapat menggunakan kembali sandbox yang sama yang dapat dieksekusi untuk versi Chromium yang berbeda, sehingga hal berikut hanya dapat dilakukan sekali per lingkungan host:

# 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

Anda mungkin ingin mengekspor variabel env CHROME_DEVEL_SANDBOX secara default. Dalam hal ini, tambahkan kode berikut ke ~/.bashrc atau .zshenv:

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

Jalankan Puppeteer di Travis CI

Kami menjalankan pengujian untuk Puppeteer di Travis CI hingga v6.0.0, setelah itu kami bermigrasi ke GitHub Actions. Anda dapat melihat .travis.yml (v5.5.0) untuk referensi.

Berikut ini beberapa praktik terbaik:

  • Layanan xvfb harus diluncurkan untuk menjalankan Chromium dalam mode non-headless
  • Berjalan di Xenial Linux di Travis secara default
  • Menjalankan npm install secara default
  • node_modules disimpan di cache secara default

.travis.yml mungkin terlihat seperti ini:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

Jalankan Puppeteer di CircleCI

  1. Mulai dengan image NodeJS dalam konfigurasi Anda. yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
  2. Dependensi seperti libXtst6 mungkin perlu diinstal dengan apt-get, jadi gunakan threetreeslight/puppeteer orb (instructions), atau tempelkan bagian sumber ke dalam konfigurasi Anda sendiri.
  3. Terakhir, jika menggunakan Puppeteer melalui Jest, Anda mungkin akan mengalami error saat memunculkan proses turunan: shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11) Hal ini kemungkinan disebabkan oleh Jest mendeteksi otomatis jumlah proses di seluruh mesin (36), bukan jumlah yang diizinkan untuk container (2). Untuk memperbaikinya, tetapkan jest --maxWorkers=2 dalam perintah pengujian Anda.

Jalankan Puppeteer di Docker

Mengaktifkan dan menjalankan Chrome headless di Docker bisa menjadi hal yang sulit. Chromium paket yang diinstal Puppeteer tidak memiliki dependensi library bersama yang diperlukan.

Untuk memperbaikinya, Anda harus menginstal dependensi yang hilang dan paket Chromium terbaru di Dockerfile Anda:

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"]

Buat container:

docker build -t puppeteer-chrome-linux .

Jalankan container dengan meneruskan node -e "<yourscript.js content as a string>" sebagai perintah:

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

Terdapat contoh lengkap di https://github.com/ebidel/try-puppeteer yang menunjukkan cara menjalankan Dockerfile ini dari server web yang berjalan di App Engine Flex (Node).

Lari di Alpine

Paket Chromium terbaru yang didukung di Alpine adalah 100, yang sesuai dengan Puppeteer v13.5.0.

Contoh 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

...

Praktik terbaik dengan Docker

Secara default, Docker menjalankan container dengan ruang memori bersama /dev/shm sebesar 64 MB. Ukuran ini biasanya terlalu kecil untuk Chrome dan akan menyebabkan Chrome error saat merender halaman yang besar. Untuk memperbaikinya, jalankan penampung dengan docker run --shm-size=1gb untuk meningkatkan ukuran /dev/shm. Mulai versi Chrome 65, tindakan ini tidak lagi diperlukan. Sebagai gantinya, luncurkan browser dengan flag --disable-dev-shm-usage:

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

Tindakan ini menulis file memori bersama ke dalam /tmp, bukan /dev/shm. Tinjau crbug.com/736452.

Apakah Anda melihat kesalahan aneh lainnya saat meluncurkan Chrome? Coba jalankan penampung dengan docker run --cap-add=SYS_ADMIN saat melakukan pengembangan secara lokal. Karena Dockerfile menambahkan pengguna pptr sebagai pengguna tanpa hak istimewa, pengguna tersebut mungkin tidak memiliki semua hak istimewa yang diperlukan.

dumb-init ada baiknya untuk dicoba jika Anda mengalami banyak proses Chrome zombie yang terus berlanjut. Ada perlakuan khusus untuk proses dengan PID=1, yang menyulitkan penghentian Chrome dengan benar dalam beberapa kasus (misalnya pada Docker).

Jalankan Puppeteer di cloud

Di Google App Engine

Runtime Node.js dari lingkungan standar App Engine dilengkapi dengan semua paket sistem yang diperlukan untuk menjalankan Headless Chrome.

Untuk menggunakan puppeteer, cantumkan modul sebagai dependensi dalam package.json Anda, lalu deploy ke Google App Engine. Baca lebih lanjut cara menggunakan puppeteer di App Engine dengan mengikuti tutorial resmi.

Di Google Cloud Functions

Runtime Node.js 10 dari Google Cloud Functions dilengkapi dengan semua paket sistem yang diperlukan untuk menjalankan Headless Chrome.

Untuk menggunakan puppeteer, cantumkan modul sebagai dependensi dalam package.json lalu deploy fungsi Anda ke Google Cloud Functions menggunakan runtime nodejs10.

Jalankan Puppeteer di Google Cloud Run

Runtime Node.js default Google Cloud Run tidak dilengkapi dengan paket sistem yang diperlukan untuk menjalankan Headless Chrome. Siapkan Dockerfile Anda sendiri dan sertakan dependensi yang hilang.

Di Heroku

Menjalankan Puppeteer di Heroku memerlukan beberapa dependensi tambahan yang tidak disertakan di kotak Linux yang disiapkan Heroku untuk Anda. Untuk menambahkan dependensi pada deployment, tambahkan buildpack Puppeteer Heroku ke daftar buildpack untuk aplikasi Anda di bagian Settings > Buildpacks.

URL untuk buildpack adalah https://github.com/jontewks/puppeteer-heroku-buildpack

Pastikan Anda menggunakan mode '--no-sandbox' saat meluncurkan Puppeteer. Hal ini dapat dilakukan dengan meneruskannya sebagai argumen ke panggilan .launch(): puppeteer.launch({ args: ['--no-sandbox'] });.

Saat mengklik tambahkan buildpack, tempelkan URL tersebut ke input, lalu klik save. Pada deployment berikutnya, aplikasi Anda juga akan menginstal dependensi yang perlu dijalankan Puppeteer.

Jika Anda perlu merender karakter bahasa China, Jepang, atau Korea, Anda mungkin perlu menggunakan buildpack dengan file font tambahan seperti https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack

Ada juga panduan lain dari @timleland yang berisi contoh project.

Di AWS Lambda

AWS Lambda membatasi ukuran paket deployment menjadi ~50 MB. Hal ini menimbulkan tantangan untuk menjalankan Chrome headless (dan juga Puppeteer) di Lambda. Komunitas ini telah mengumpulkan beberapa referensi yang mengatasi masalah ini:

Instance AWS EC2 yang menjalankan Amazon-Linux

Jika Anda memiliki instance EC2 yang menjalankan amazon-linux di pipeline CI/CD, dan ingin menjalankan pengujian Puppeteer di amazon-linux, ikuti langkah-langkah berikut.

  1. Untuk menginstal Chromium, Anda harus mengaktifkan amazon-linux-extras terlebih dahulu, yang merupakan bagian dari EPEL (Paket Tambahan untuk Enterprise Linux):

    sudo amazon-linux-extras install epel -y
    
  2. Berikutnya, instal Chromium:

    sudo yum install -y chromium
    

Sekarang Puppeteer dapat meluncurkan Chromium untuk menjalankan pengujian. Jika Anda tidak mengaktifkan EPEL dan terus menginstal Chromium sebagai bagian dari npm install, Puppeteer tidak dapat meluncurkan Chromium karena tidak tersedia libatk-1.0.so.0 dan banyak paket lainnya.

Masalah transpilasi kode

Jika Anda menggunakan transpiler JavaScript seperti babel atau TypeScript, memanggil evaluate() dengan fungsi asinkron mungkin tidak berfungsi. Hal ini karena saat puppeteer menggunakan Function.prototype.toString() untuk melakukan serialisasi fungsi, sementara transpiler dapat mengubah kode output sedemikian rupa sehingga tidak kompatibel dengan puppeteer.

Beberapa solusi atas masalah ini adalah memerintahkan transpiler agar tidak mengacaukan kode, misalnya, mengonfigurasi TypeScript agar menggunakan versi ecma terbaru ("target": "es2018"). Solusi lainnya dapat menggunakan template string, bukan fungsi:

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