Problemen met poppenspeler oplossen

Headless Chrome start niet op Windows

Sommige Chrome-beleidsregels kunnen het uitvoeren van Chrome of Chromium met bepaalde extensies afdwingen.

Puppeteer passeert standaard de vlag --disable-extensions en start daarom niet wanneer dergelijk beleid actief is.

Om dit te omzeilen, probeer zonder de vlag te rennen:

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

Context: uitgave 3681 .

Headless Chrome start niet op UNIX

Zorg ervoor dat alle benodigde afhankelijkheden zijn geïnstalleerd. U kunt ldd chrome | grep not op een Linux-machine om te controleren welke afhankelijkheden ontbreken.

Afhankelijkheden van 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-afhankelijkheden

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

Na het installeren van afhankelijkheden moet u de nss-bibliotheek bijwerken met deze opdracht

yum update nss -y

Bekijk discussies:

  • #290 - Probleemoplossing voor Debian
  • #391 - Probleemoplossing CentOS
  • #379 - Alpine-probleemoplossing

Headless Chrome schakelt GPU-compositing uit

Chrome en Chromium vereisen --use-gl=egl om GPU-versnelling in headless-modus in te schakelen.

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

Chrome wordt gedownload, maar kan niet worden gestart op Node.js

Als je een foutmelding krijgt die er als volgt uitziet wanneer je Chromium probeert te starten:

(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

Dit betekent dat de browser is gedownload, maar niet correct is uitgepakt. De meest voorkomende oorzaak is een bug in Node.js v14.0.0 waardoor extract-zip kapot ging, de module die Puppeteer gebruikt om browserdownloads naar de juiste plek te extraheren. De bug is opgelost in Node.js v14.1.0, dus zorg ervoor dat je die versie of hoger gebruikt.

Stel een Chrome Linux-sandbox in

Om de hostomgeving te beschermen tegen niet-vertrouwde webinhoud, gebruikt Chrome meerdere lagen sandboxing . Om dit goed te laten werken, moet de host eerst worden geconfigureerd. Als er geen goede sandbox is die Chrome kan gebruiken, crasht het programma met de foutmelding No usable sandbox! .

Als u de inhoud die u in Chrome opent absoluut vertrouwt , kunt u Chrome starten met het argument --no-sandbox :

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

Er zijn twee manieren om een ​​sandbox in Chromium te configureren.

Het klonen van Sser-naamruimten wordt alleen ondersteund door moderne kernels. Gebruikersnaamruimten zonder privileges zijn over het algemeen prima in te schakelen, maar kunnen meer kernelaanvaloppervlak openen voor (niet in de sandbox geplaatste) niet-rootprocessen, zodat ze kernelprivileges kunnen krijgen.

sudo sysctl -w kernel.unprivileged_userns_clone=1

[alternatief] Setuid-sandbox instellen

De setuid-sandbox wordt geleverd als een zelfstandig uitvoerbaar bestand en bevindt zich naast het Chromium dat Puppeteer downloadt. Het is prima om hetzelfde uitvoerbare sandbox-bestand opnieuw te gebruiken voor verschillende Chromium-versies, dus het volgende kan slechts één keer per hostomgeving worden gedaan:

# 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

Mogelijk wilt u de CHROME_DEVEL_SANDBOX env-variabele standaard exporteren. Voeg in dit geval het volgende toe aan ~/.bashrc of .zshenv :

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

Voer Puppeteer uit op Travis CI

We hebben onze tests voor Puppeteer op Travis CI uitgevoerd tot v6.0.0, waarna we zijn gemigreerd naar GitHub Actions. U kunt .travis.yml (v5.5.0) ter referentie raadplegen.

Hier zijn enkele best practices:

  • De xvfb -service moet worden gestart om Chromium in de niet-headless-modus uit te voeren
  • Draait standaard op Xenial Linux op Travis
  • Voert standaard npm install uit
  • node_modules wordt standaard in de cache opgeslagen

.travis.yml zou er zo uit kunnen zien:

language: node_js
node_js: node
services: xvfb

script:
  - npm run test

Voer Puppeteer uit op CircleCI

  1. Begin met een NodeJS-image in uw configuratie. yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
  2. Afhankelijkheden zoals libXtst6 moeten waarschijnlijk worden geïnstalleerd met apt-get , dus gebruik de threetreeslight/puppeteer orb ( instructies ), of plak delen van de broncode in uw eigen configuratie.
  3. Als u ten slotte Puppeteer via Jest gebruikt, kunt u een fout tegenkomen bij het voortbrengen van onderliggende processen: shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11) Dit wordt waarschijnlijk veroorzaakt doordat Jest automatisch het aantal processen op de hele machine detecteert ( 36 ) in plaats van het aantal dat is toegestaan ​​voor uw container ( 2 ). Om dit op te lossen, stelt u jest --maxWorkers=2 in uw testopdracht in.

Voer Puppeteer uit in Docker

Het kan lastig zijn om headless Chrome in Docker te gebruiken. Het gebundelde Chromium dat Puppeteer installeert, mist de noodzakelijke afhankelijkheden van de gedeelde bibliotheek.

Om dit probleem op te lossen, moet je de ontbrekende afhankelijkheden en het nieuwste Chromium-pakket in je Dockerfile installeren:

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

Bouw de container:

docker build -t puppeteer-chrome-linux .

Voer de container uit door node -e "<yourscript.js content as a string>" als opdracht:

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

Er is een volledig voorbeeld op https://github.com/ebidel/try-puppeteer dat laat zien hoe u dit Dockerfile kunt uitvoeren vanaf een webserver die draait op App Engine Flex (Node).

Rijd op Alpine

Het nieuwste Chromium-pakket dat op Alpine wordt ondersteund, is 100, wat overeenkomt met Puppeteer v13.5.0 .

Voorbeeld Dockerbestand:

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 met Docker

Standaard voert Docker een container uit met een /dev/shm gedeelde geheugenruimte van 64 MB. Dit is doorgaans te klein voor Chrome en zorgt ervoor dat Chrome crasht bij het weergeven van grote pagina's. Om dit op te lossen, voert u de container uit met docker run --shm-size=1gb om de grootte van /dev/shm te vergroten. Sinds Chrome 65 is dit niet meer nodig. Start in plaats daarvan de browser met de vlag --disable-dev-shm-usage :

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

Dit schrijft gedeelde geheugenbestanden naar /tmp in plaats van /dev/shm . Beoordeel crbug.com/736452 .

Zie je andere rare fouten bij het starten van Chrome? Probeer uw container uit te voeren met docker run --cap-add=SYS_ADMIN wanneer u lokaal ontwikkelt. Omdat de Dockerfile een pptr gebruiker toevoegt als een niet-bevoorrechte gebruiker, beschikt deze mogelijk niet over alle benodigde rechten.

dumb-init is de moeite van het bekijken waard als je veel zombie-Chrome-processen ervaart die blijven hangen. Er is een speciale behandeling voor processen met PID=1 , waardoor het in sommige gevallen moeilijk wordt om Chrome op de juiste manier te beëindigen (zoals bij Docker).

Voer Puppeteer uit in de cloud

Op Google App Engine

De Node.js-runtime van de App Engine-standaardomgeving wordt geleverd met alle systeempakketten die nodig zijn om Headless Chrome uit te voeren.

Als puppeteer wilt gebruiken, vermeldt u de module als afhankelijkheid in uw package.json en implementeert u deze in Google App Engine. Lees meer over het gebruik van puppeteer op App Engine door de officiële tutorial te volgen.

Op Google Cloud-functies

De Node.js 10-runtime van Google Cloud Functions wordt geleverd met alle systeempakketten die nodig zijn om Headless Chrome uit te voeren.

Als u puppeteer wilt gebruiken, vermeldt u de module als afhankelijkheid in uw package.json en implementeert u uw functie in Google Cloud Functions met behulp van de nodejs10 -runtime.

Voer Puppeteer uit op Google Cloud Run

De standaard Node.js-runtime van Google Cloud Run wordt niet geleverd met de systeempakketten die nodig zijn om Headless Chrome uit te voeren. Stel uw eigen Dockerfile in en neem de ontbrekende afhankelijkheden op .

Op Heroku

Het uitvoeren van Puppeteer op Heroku vereist een aantal extra afhankelijkheden die niet zijn opgenomen in de Linux-box die Heroku voor je opstart. Als u de afhankelijkheden bij de implementatie wilt toevoegen, voegt u het Puppeteer Heroku-buildpack toe aan de lijst met buildpacks voor uw app onder Instellingen > Buildpacks.

De URL voor het buildpack is https://github.com/jontewks/puppeteer-heroku-buildpack

Zorg ervoor dat u de '--no-sandbox' modus gebruikt wanneer u Puppeteer start. Dit kunt u doen door het als argument door te geven aan uw .launch() -aanroep: puppeteer.launch({ args: ['--no-sandbox'] }); .

Wanneer u op buildpack toevoegen klikt, plakt u die URL in de invoer en klikt u op opslaan . Bij de volgende implementatie installeert uw app ook de afhankelijkheden die Puppeteer moet uitvoeren.

Als u Chinese, Japanse of Koreaanse tekens moet weergeven, moet u mogelijk een buildpack gebruiken met extra lettertypebestanden zoals https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack

Er is ook nog een gids van @timleland met een voorbeeldproject.

Op AWS Lambda

AWS Lambda beperkt de grootte van implementatiepakketten tot ~50 MB. Dit brengt uitdagingen met zich mee voor het uitvoeren van headless Chrome (en dus Puppeteer) op Lambda. De community heeft een aantal bronnen samengesteld die deze problemen aanpakken:

AWS EC2-instantie met Amazon-Linux

Als u een EC2-instantie met amazon-linux in uw CI/CD-pijplijn hebt en u Puppeteer-tests wilt uitvoeren in amazon-linux, volgt u deze stappen.

  1. Om Chromium te installeren, moet je eerst amazon-linux-extras inschakelen, wat onderdeel is van EPEL (Extra Packages for Enterprise Linux) :

    sudo amazon-linux-extras install epel -y
    
  2. Installeer vervolgens Chromium:

    sudo yum install -y chromium
    

Nu kan Puppeteer Chromium starten om uw tests uit te voeren. Als u EPEL niet inschakelt en doorgaat met het installeren van Chromium als onderdeel van npm install , kan Puppeteer Chromium niet starten vanwege de onbeschikbaarheid van libatk-1.0.so.0 en nog veel meer pakketten.

Problemen met codetranspilatie

Als u een JavaScript-transpiler zoals babel of TypeScript gebruikt, werkt het aanroepen van evaluate() met een asynchrone functie mogelijk niet. Dit komt doordat puppeteer Function.prototype.toString() gebruikt om functies te serialiseren, terwijl transpilers de uitvoercode zodanig kunnen wijzigen dat deze incompatibel is met puppeteer .

Sommige oplossingen voor dit probleem zijn het instrueren van de transpiler om de code niet te verknoeien, bijvoorbeeld door TypeScript te configureren om de nieuwste ecma-versie te gebruiken ( "target": "es2018" ). Een andere oplossing zou het gebruik van tekenreekssjablonen kunnen zijn in plaats van functies:

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