Fehlerbehebung bei Puppeteer

Monitorloses Chrome wird unter Windows nicht gestartet

Einige Chrome-Richtlinien erzwingen möglicherweise die Ausführung von Chrome oder Chromium mit bestimmten Erweiterungen.

Puppeteer übergibt standardmäßig das Flag --disable-extensions und startet daher nicht, wenn solche Richtlinien aktiv sind.

Um dieses Problem zu umgehen, führen Sie ohne das Flag aus:

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

Kontext: Problem 3681.

Monitorloses Chrome wird unter UNIX nicht gestartet

Prüfen Sie, ob alle erforderlichen Abhängigkeiten installiert sind. Sie können ldd chrome | grep not auf einem Linux-Computer ausführen, um zu prüfen, welche Abhängigkeiten fehlen.

Debian (Ubuntu)-Abhängigkeiten

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-Abhängigkeiten

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

Nach der Installation der Abhängigkeiten müssen Sie die NSS-Bibliothek mit diesem Befehl aktualisieren

yum update nss -y

Diskussionen ansehen:

  • #290 – Debian-Fehlerbehebung
  • #391 – CentOS-Fehlerbehebung
  • #379 – Alpine Fehlerbehebung

Monitorloses Chrome deaktiviert GPU-Compositing

Für Chrome und Chromium ist --use-gl=egl erforderlich, um die GPU-Beschleunigung im monitorlosen Modus zu aktivieren.

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

Chrome wurde heruntergeladen, startet aber nicht auf Node.js

Wenn Sie beim Starten von Chromium eine Fehlermeldung wie diese erhalten, gehen Sie so vor:

(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

Dies bedeutet, dass der Browser heruntergeladen wurde, aber nicht richtig extrahiert werden konnte. Die häufigste Ursache ist ein Programmfehler in Node.js v14.0.0, durch den extract-zip beschädigt wurde. Das Modul, mit dem Puppeteer Browserdownloads an die richtige Stelle extrahiert, ist das. Der Fehler wurde in Node.js v14.1.0 behoben. Achten Sie daher darauf, dass Sie diese oder eine höhere Version ausführen.

Chrome Linux-Sandbox einrichten

Um die Hostumgebung vor nicht vertrauenswürdigen Webinhalten zu schützen, nutzt Chrome mehrere Sandbox-Ebenen. Damit dies richtig funktioniert, muss der Host zuerst konfiguriert werden. Wenn keine geeignete Sandbox für Chrome verfügbar ist, stürzt der Browser mit dem Fehler No usable sandbox! ab.

Wenn Sie dem Inhalt, den Sie in Chrome öffnen, absolut vertrauen, können Sie Chrome mit dem Argument --no-sandbox starten:

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

Es gibt zwei Möglichkeiten, eine Sandbox in Chromium zu konfigurieren.

Das Klonen von Server-Namespaces wird nur von modernen Kerneln unterstützt. Nicht privilegierte Nutzer-Namespaces können in der Regel problemlos aktiviert werden, können aber eine größere Kernel-Angriffsfläche für Nicht-Root-Prozesse (ohne Sandbox) öffnen, um Kernel-Berechtigungen zu erhalten.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[alternative] Setuid-Sandbox einrichten

Die setuid-Sandbox ist eine eigenständige ausführbare Datei und befindet sich neben dem Chromium, den Puppeteer herunterlädt. Sie können dieselbe ausführbare Sandbox-Datei für verschiedene Chromium-Versionen wiederverwenden. Daher ist Folgendes nur einmal pro Hostumgebung möglich:

# 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

Sie können die Umgebungsvariable CHROME_DEVEL_SANDBOX standardmäßig exportieren. Fügen Sie in diesem Fall ~/.bashrc oder .zshenv Folgendes hinzu:

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

Puppeteer auf Travis CI ausführen

Wir haben unsere Tests für Puppeteer auf Travis CI bis Version 6.0.0 durchgeführt. Danach erfolgten wir zu GitHub Actions. Weitere Informationen finden Sie in der Referenz zu .travis.yml (Version 5.5.0).

Hier sind einige Best Practices:

  • Der Dienst xvfb sollte gestartet werden, um Chromium im nicht monitorlosen Modus auszuführen
  • Wird standardmäßig unter Xenial Linux unter Travis ausgeführt
  • Wird standardmäßig npm install ausgeführt
  • node_modules wird standardmäßig im Cache gespeichert

.travis.yml könnte so aussehen:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

Puppeteer auf CircleCI ausführen

  1. Beginnen Sie mit einem NodeJS-Image in der Konfiguration. yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
  2. Abhängigkeiten wie libXtst6 müssen wahrscheinlich mit apt-get installiert werden. Verwenden Sie daher den Orb threetreeslight/puppeteer (instructions) oder fügen Sie Teile der Quelle in Ihre eigene Konfiguration ein.
  3. Wenn Sie Puppeteer über Jest verwenden, kann ein Fehler auftreten, der untergeordnete Prozesse erzeugt: shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11) Dies liegt wahrscheinlich daran, dass Jest die Anzahl der Prozesse auf dem gesamten Computer (36) und nicht die Anzahl der Prozesse, die Ihrem Container (2) erlaubt ist, automatisch erkennt. Um das Problem zu beheben, legen Sie jest --maxWorkers=2 in Ihrem Testbefehl fest.

Puppeteer in Docker ausführen

Es kann sich als schwierig erweisen, die monitorlose Chrome-Version in Docker einzurichten und auszuführen. Beim gebündelten Chromium, das Puppeteer installiert, fehlen die erforderlichen Abhängigkeiten der gemeinsam genutzten Bibliothek.

Zur Behebung dieses Problems müssen Sie die fehlenden Abhängigkeiten und das neueste Chromium-Paket in Ihrem Dockerfile installieren:

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

Erstellen Sie den Container:

docker build -t puppeteer-chrome-linux .

Führen Sie den Container aus, indem Sie node -e "<yourscript.js content as a string>" als Befehl übergeben:

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

Unter https://github.com/ebidel/try-puppeteer finden Sie ein vollständiges Beispiel, das zeigt, wie dieses Dockerfile von einem Webserver ausgeführt wird, der auf App Engine Flex (Knoten) ausgeführt wird.

Alpines Laufen

Das neueste Chromium-Paket, das auf Alpine unterstützt wird, ist 100, was der Puppeteer-Version 13.5.0 entspricht.

Beispiel für ein 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

...

Best Practices für Docker

Standardmäßig führt Docker einen Container mit einem gemeinsamen /dev/shm-Arbeitsspeicher von 64 MB aus. Dieser Wert ist in der Regel zu klein für Chrome und führt beim Rendern großer Seiten zum Absturz von Chrome. Zur Behebung dieses Fehlers führen Sie den Container mit docker run --shm-size=1gb aus, um die Größe von /dev/shm zu erhöhen. Seit Chrome 65 ist dies nicht mehr erforderlich. Starten Sie den Browser stattdessen mit dem Flag --disable-dev-shm-usage:

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

Dadurch werden Dateien mit gemeinsam genutztem Arbeitsspeicher in /tmp anstatt in /dev/shm geschrieben. Prüfen Sie crbug.com/736452.

Werden beim Starten von Chrome weitere seltsame Fehler angezeigt? Versuchen Sie, den Container mit docker run --cap-add=SYS_ADMIN bei der lokalen Entwicklung auszuführen. Da das Dockerfile einen pptr-Nutzer als nicht privilegierten Nutzer hinzufügt, verfügt es möglicherweise nicht über alle erforderlichen Berechtigungen.

dumb-init sollte überprüft werden, wenn Chrome-Prozesse viele Zombies haben. Prozesse mit PID=1 werden speziell behandelt, was es in einigen Fällen erschwert, Chrome ordnungsgemäß zu beenden (z. B. mit Docker).

Puppeteer in der Cloud ausführen

In der Google App Engine

Die Node.js-Laufzeit der App Engine-Standardumgebung enthält alle Systempakete, die zum Ausführen von Headless Chrome erforderlich sind.

Wenn Sie puppeteer verwenden möchten, listen Sie das Modul als Abhängigkeit in Ihrer package.json auf und stellen Sie es in Google App Engine bereit. Weitere Informationen zur Verwendung von puppeteer in App Engine finden Sie in der offiziellen Anleitung.

In Google Cloud Functions

Die Node.js 10-Laufzeit von Google Cloud Functions enthält alle Systempakete, die zum Ausführen von Headless Chrome erforderlich sind.

Wenn Sie puppeteer verwenden möchten, listen Sie das Modul als Abhängigkeit in Ihrer package.json auf und stellen Sie die Funktion mithilfe der nodejs10-Laufzeit in Google Cloud Functions bereit.

Puppeteer in Google Cloud Run ausführen

Die Node.js-Standardlaufzeit von Google Cloud Run enthält nicht die Systempakete, die zum Ausführen von Headless Chrome erforderlich sind. Richten Sie Ihre eigene Dockerfile ein und fügen Sie die fehlenden Abhängigkeiten hinzu.

Auf Heroku

Für die Ausführung von Puppeteer unter Heroku sind einige zusätzliche Abhängigkeiten erforderlich, die nicht im Linux-Rechner enthalten sind, den Heroku für Sie hochfährt. Um die Abhängigkeiten bei der Bereitstellung hinzuzufügen, fügen Sie das Puppeteer Heroku-Buildpack der Liste der Buildpacks für Ihre App unter Einstellungen > Buildpacks hinzu.

Die URL für das Buildpack lautet https://github.com/jontewks/puppeteer-heroku-buildpack.

Achten Sie darauf, dass Sie den '--no-sandbox'-Modus verwenden, wenn Sie Puppeteer starten. Übergeben Sie es dazu als Argument an den .launch()-Aufruf: puppeteer.launch({ args: ['--no-sandbox'] });.

Wenn Sie auf „Buildpack hinzufügen“ klicken, fügen Sie diese URL in das Eingabefeld ein und klicken Sie auf Speichern. Bei der nächsten Bereitstellung installiert Ihre App auch die Abhängigkeiten, die Puppeteer ausführen muss.

Wenn Sie chinesische, japanische oder koreanische Zeichen rendern müssen, müssen Sie möglicherweise ein Buildpack mit zusätzlichen Schriftartdateien wie https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack verwenden.

Es gibt auch einen weiteren Leitfaden von @timleland, der ein Beispielprojekt enthält.

Auf AWS Lambda

AWS Lambda begrenzt die Größe von Bereitstellungspaketen auf ca. 50 MB. Dies stellt eine Herausforderung für die Ausführung von Chrome (und damit Puppeteer) auf Lambda dar. Die Community hat einige Ressourcen zusammengestellt, die die Probleme umgehen:

AWS EC2-Instanz, auf der Amazon-Linux ausgeführt wird

Wenn eine EC2-Instanz in Ihrer CI/CD-Pipeline amazon-linux ausführt und Sie Puppeteer-Tests in amazon-linux ausführen möchten, führen Sie die folgenden Schritte aus.

  1. Damit Sie Chromium installieren können, müssen Sie zuerst amazon-linux-extras aktivieren. Das Tool ist Teil von EPEL (Extra Packages for Enterprise Linux):

    sudo amazon-linux-extras install epel -y
    
  2. Installieren Sie als Nächstes Chromium:

    sudo yum install -y chromium
    

Jetzt kann Puppeteer Chromium zum Ausführen Ihrer Tests starten. Wenn Sie EPEL nicht aktivieren und Chromium als Teil von npm install installieren, kann Puppeteer Chromium nicht starten, da libatk-1.0.so.0 und viele weitere Pakete nicht verfügbar sind.

Probleme bei der Codetranspilierung

Wenn Sie einen JavaScript-Transpiler wie Babel oder TypeScript verwenden, funktioniert der Aufruf von evaluate() mit einer asynchronen Funktion möglicherweise nicht. Das liegt daran, dass puppeteer zwar Function.prototype.toString() zum Serialisieren von Funktionen verwendet, während Transpiler den Ausgabecode so ändern könnten, dass er nicht mit puppeteer kompatibel ist.

Um dieses Problem zu umgehen, können Sie den Transpiler anweisen, den Code nicht zu verfälschen. Beispielsweise können Sie TypeScript so konfigurieren, dass die neueste ECM-Version ("target": "es2018") verwendet wird. Eine weitere Problemumgehung besteht darin, Stringvorlagen anstelle von Funktionen zu verwenden:

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