Il Chrome headless non si avvia su Windows
Alcuni criteri di Chrome potrebbero applicare l'esecuzione di Chrome o Chromium con determinate estensioni.
Puppeteer passa il flag --disable-extensions
per impostazione predefinita, quindi non viene avviato quando questi criteri sono attivi.
Per risolvere il problema, prova a eseguire il comando senza il flag:
const browser = await puppeteer.launch({
ignoreDefaultArgs: ['--disable-extensions'],
});
Contesto: problema 3681.
Il Chrome headless non viene lanciato su UNIX
Assicurati che siano installate tutte le dipendenze necessarie. Puoi eseguire ldd chrome | grep not
su una macchina Linux per verificare quali dipendenze mancano.
Dipendenze 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
Dipendenze 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
Dopo aver installato le dipendenze devi aggiornare la libreria nss utilizzando questo comando
yum update nss -y
Dai un'occhiata alle discussioni:
- #290 - Risoluzione dei problemi relativi a Debian
- #391. Risoluzione dei problemi di CentOS
- #379 - Risoluzione dei problemi relativi ad Alpi
Chrome headless disattiva il compositing GPU
Chrome e Chromium richiedono --use-gl=egl
per attivare l'accelerazione GPU in modalità headless.
const browser = await puppeteer.launch({
headless: true,
args: ['--use-gl=egl'],
});
Chrome è stato scaricato, ma non si avvia su Node.js
Se quando provi ad avviare Chromium viene visualizzato un errore simile al seguente:
(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
Significa che il browser è stato scaricato ma non è stato possibile estrarlo correttamente.
La causa più comune è un bug in Node.js v14.0.0 che ha infranto extract-zip
, il modulo utilizzato da Puppeteer per estrarre i download del browser nella posizione corretta. Il bug è stato corretto in Node.js v14.1.0, quindi assicurati di eseguire questa versione o una versione successiva.
Configurare una sandbox per Chrome Linux
Per proteggere l'ambiente host da contenuti web non attendibili, Chrome utilizza
più livelli di sandboxing.
Affinché funzioni correttamente, è necessario prima configurare l'host. Se non esiste una sandbox valida per Chrome, si arresta in modo anomalo con l'errore No usable sandbox!
.
Se ti fidi totalmente dei contenuti che apri in Chrome, puoi avviare Chrome con l'argomento --no-sandbox
:
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
Esistono due modi per configurare una sandbox in Chromium.
[consigliato] Abilita la clonazione dello spazio dei nomi utente
La clonazione dello spazio dei nomi CSS è supportata solo dai kernel moderni. Gli spazi dei nomi degli utenti senza privilegi sono generalmente abilitati, ma possono creare una maggiore superficie di attacco del kernel (senza sandbox) per aumentare i privilegi del kernel.
sudo sysctl -w kernel.unprivileged_userns_clone=1
[alternativa] Configura la sandbox di Setuid
La sandbox di setuid è un file eseguibile autonomo che si trova accanto al dispositivo Chromium scaricato da Puppeteer. Puoi riutilizzare lo stesso eseguibile sandbox per diverse versioni di Chromium, pertanto le seguenti operazioni possono essere eseguite una sola volta per ogni ambiente 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
Potresti voler esportare la variabile env CHROME_DEVEL_SANDBOX
per impostazione predefinita. In questo caso, aggiungi quanto segue a ~/.bashrc
o .zshenv
:
export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox
Esegui Puppeteer su Travis CI
Abbiamo eseguito i nostri test per Puppeteer su Travis CI fino alla versione 6.0.0, dopodiché abbiamo eseguito la migrazione a GitHub Actions. Puoi vedere .travis.yml
(v5.5.0) come riferimento.
Di seguito sono riportate alcune best practice.
- Per eseguire Chromium in modalità non headless, devi avviare il servizio xvfb
- Viene eseguito su Xenial Linux su Travis per impostazione predefinita
- Esegue
npm install
per impostazione predefinita node_modules
è memorizzato nella cache per impostazione predefinita
.travis.yml
potrebbe avere il seguente aspetto:
language: node_js
node_js: node
services: xvfb
script:
- npm run test
Esegui Puppeteer su CircleCI
- Inizia con un'immagine NodeJS nella configurazione.
yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
- Probabilmente è necessario che le dipendenze come
libXtst6
debbano essere installate conapt-get
, quindi utilizza l'orb Threetreeslight/puppeteer (instructions) o incolla parti della sua origine nella tua configurazione. - Infine, se utilizzi Puppeteer tramite Jest, potresti riscontrare un errore durante la generazione di processi secondari:
shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11)
Ciò è probabilmente causato dal fatto che Jest rileva automaticamente il numero di processi sull'intera macchina (36
) anziché il numero consentito al tuo container (2
). Per risolvere il problema, impostajest --maxWorkers=2
nel comando di test.
Esegui Puppeteer in Docker
L'avvio e l'esecuzione di Chrome headless in Docker può essere complicato. Il Chromium in bundle installato da Puppeteer non ha le dipendenze di libreria condivisa necessarie.
Per risolvere il problema, devi installare le dipendenze mancanti e il pacchetto Chromium più recente nel 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"]
Crea il container:
docker build -t puppeteer-chrome-linux .
Esegui il container passando node -e "<yourscript.js content as a string>"
come comando:
docker run -i --init --rm --cap-add=SYS_ADMIN \
--name puppeteer-chrome puppeteer-chrome-linux \
node -e "`cat yourscript.js`"
Puoi trovare un esempio completo all'indirizzo https://github.com/ebidel/try-puppeteer che mostra come eseguire questo Dockerfile da un server web in esecuzione su App Engine Flex (Node).
Corsa su alpino
Il più recente pacchetto di Chromium supportato su Alpine è 100, che corrisponde a Puppeteer v13.5.0.
Dockerfile di esempio:
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 practice con Docker
Per impostazione predefinita, Docker esegue un container con uno spazio di memoria condiviso di /dev/shm
pari a 64 MB.
In genere questa dimensione è in genere troppo piccola per Chrome e causa l'arresto anomalo di Chrome in caso di rendering di pagine di grandi dimensioni. Per risolvere il problema, esegui il container con docker run --shm-size=1gb
per aumentare le dimensioni di /dev/shm
. A partire da Chrome 65, questo passaggio non è più necessario. Avvia invece il browser con il flag --disable-dev-shm-usage
:
const browser = await puppeteer.launch({
args: ['--disable-dev-shm-usage'],
});
I file di memoria condivisi verranno scritti in /tmp
anziché in /dev/shm
. Visita la pagina
crbug.com/736452.
Noti altri errori strani quando avvii Chrome? Prova a eseguire il container con docker run --cap-add=SYS_ADMIN
durante lo sviluppo locale. Poiché il Dockerfile aggiunge un utente pptr
come utente senza privilegi, potrebbe non disporre di tutti i privilegi necessari.
dumb-init vale la pena provare se stai
sperimentando molti processi di zombi che Chrome non riesce a guardare. Per i processi con PID=1
viene applicato un trattamento speciale, che rende difficile terminare correttamente Chrome in alcuni casi (ad esempio con Docker).
Esegui Puppeteer nel cloud
Su Google App Engine
Il runtime Node.js dell'ambiente standard di App Engine include tutti i pacchetti di sistema necessari per eseguire Chrome senza testa.
Per utilizzare puppeteer
, elenca il modulo come dipendenza in package.json
ed esegui il deployment in Google App Engine. Scopri di più sull'utilizzo di puppeteer
in App Engine seguendo il tutorial ufficiale.
Su Google Cloud Functions
Il runtime Node.js 10 di Google Cloud Functions include tutti i pacchetti di sistema necessari per eseguire Chrome headless.
Per utilizzare puppeteer
, elenca il modulo come dipendenza in package.json
ed esegui il deployment della funzione in Google Cloud Functions utilizzando il runtime nodejs10
.
Esegui Puppeteer su Google Cloud Run
Il runtime Node.js predefinito di Google Cloud Run non include i pacchetti di sistema necessari per eseguire Chrome headless. Configura il tuo Dockerfile
e
includi le dipendenze mancanti.
Su Heroku
L'esecuzione di Puppeteer su Heroku richiede alcune dipendenze aggiuntive che non sono incluse nella confezione Linux che Heroku avvia per te. Per aggiungere le dipendenze al deployment, aggiungi il buildpack Puppeteer Heroku all'elenco di buildpack per la tua app in Impostazioni > Buildpack.
L'URL del buildpack è https://github.com/jontewks/puppeteer-heroku-buildpack
Assicurati di utilizzare la modalità '--no-sandbox'
quando avvii Puppeteer. Puoi farlo passandolo come argomento alla tua chiamata .launch()
: puppeteer.launch({ args: ['--no-sandbox'] });
.
Quando fai clic su add buildpack, incolla l'URL nell'input e fai clic su salva. Al successivo deployment, l'app installerà anche le dipendenze necessarie per l'esecuzione di Puppeteer.
Se devi eseguire il rendering di caratteri cinesi, giapponesi o coreani, potresti dover utilizzare un buildpack con file di caratteri aggiuntivi come https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack
C'è anche un'altra guida di @timleland che include un progetto di esempio.
Su AWS Lambda
AWS Lambda limita le dimensioni dei pacchetti di deployment a circa 50 MB. Ciò rappresenta una sfida per l'esecuzione di Chrome (e quindi Puppeteer) headless su Lambda. La community ha raccolto alcune risorse per risolvere questi problemi:
- Chromium Binary per AWS Lambda e Google Cloud Functions (contenuto aggiornato con l'ultima release stabile di Puppeteer)
- Chrome/Chromium su AWS Lambda di Marco Lüthy è un plug-in serverless ed è obsoleto.
Istanza AWS EC2 che esegue Amazon-Linux
Se hai un'istanza EC2 che esegue amazon-linux nella pipeline CI/CD e vuoi eseguire i test Puppeteer in amazon-linux, segui questi passaggi.
Per installare Chromium, devi prima abilitare
amazon-linux-extras
, che fa parte di EPEL (Extra Packages for Enterprise Linux):sudo amazon-linux-extras install epel -y
Dopodiché, installa Chromium:
sudo yum install -y chromium
Ora Puppeteer può avviare Chromium per eseguire i tuoi test. Se non abiliti EPEL e continui a installare Chromium come parte di npm install
, Puppeteer non può avviare Chromium perché libatk-1.0.so.0
e molti altri pacchetti non sono disponibili.
Problemi di traspilazione del codice
Se utilizzi un transpiler JavaScript come babel o TypeScript, la chiamata a evaluate()
con una funzione asincrona potrebbe non funzionare. Questo perché puppeteer
usa Function.prototype.toString()
per serializzare le funzioni, mentre i transpiler potrebbero modificare il codice di output in modo che non sia compatibile con puppeteer
.
Alcune soluzioni alternative a questo problema potrebbero essere indicare al transpiler di non confondere
il codice, ad esempio configurare TypeScript per utilizzare la versione ecma più recente
("target": "es2018"
). Un'altra soluzione alternativa potrebbe essere utilizzare modelli di stringa anziché funzioni:
await page.evaluate(`(async() => {
console.log('1');
})()`);