O Headless Chrome não é iniciado no Windows
Algumas políticas do Chrome podem exigir a execução do Chrome ou do Chromium com determinadas extensões.
O Puppeteer transmite a sinalização --disable-extensions
por padrão e, portanto, não
é iniciada quando essas políticas estão ativas.
Para contornar esse problema, execute sem a flag:
const browser = await puppeteer.launch({
ignoreDefaultArgs: ['--disable-extensions'],
});
Contexto: problema 3681 (link em inglês).
O Headless Chrome não é lançado no UNIX
Verifique se todas as dependências necessárias estão instaladas. Execute
ldd chrome | grep not
em uma máquina Linux para verificar quais dependências estão
ausentes.
Dependências do 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
Dependências do 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
Depois de instalar as dependências, atualize a biblioteca nss usando este comando
yum update nss -y
Confira as discussões:
- #290: solução de problemas no Debian
- #391: solução de problemas do CentOS
- N.o 379: solução de problemas alpino
O Headless Chrome desativa a composição de GPU
O Chrome e o Chromium exigem que --use-gl=egl
ative a
aceleração de GPU no modo headless.
const browser = await puppeteer.launch({
headless: true,
args: ['--use-gl=egl'],
});
O download do Chrome é feito, mas não é possível iniciar no Node.js.
Se você receber um erro parecido com este ao tentar iniciar o 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
Isso significa que o download do navegador foi feito, mas ocorreu uma falha na extração correta.
A causa mais comum é um bug no Node.js v14.0.0 que corrompeu o extract-zip
, o módulo usado pelo Puppeteer para extrair os downloads do navegador para o lugar certo. O bug foi corrigido no Node.js v14.1.0. Portanto, verifique se você está executando essa versão ou mais recente.
Configurar um sandbox do Chrome Linux
Para proteger o ambiente do host contra conteúdo da Web não confiável, o Chrome usa
várias camadas de sandbox.
Para que isso funcione corretamente, o host deve ser configurado primeiro. Se não houver um
sandbox bom para o Chrome usar, ele vai falhar com o erro No usable sandbox!
.
Se você confiança absolutamente no conteúdo aberto no Chrome, inicie
o navegador com o argumento --no-sandbox
:
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
Há duas maneiras de configurar um sandbox no Chromium.
[recomendado] Ativar a clonagem do namespace do usuário
A clonagem de namespace do Sser só é compatível com kernels modernos. Namespaces de usuários sem privilégios geralmente podem ser ativados, mas podem abrir mais superfície de ataque do kernel para processos não raiz (sem sandbox) para elevar a privilégios do kernel.
sudo sysctl -w kernel.unprivileged_userns_clone=1
[alternativa] Configurar o sandbox setuid
O sandbox setuid é disponibilizado como um executável autônomo e está localizado ao lado do Chromium que o Puppeteer faz o download. Não há problema em reutilizar o mesmo executável de sandbox para diferentes versões do Chromium. Assim, o seguinte só pode ser feito uma vez por ambiente de 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
Você pode exportar a variável de ambiente CHROME_DEVEL_SANDBOX
por padrão. Nesse caso, adicione o seguinte a ~/.bashrc
ou .zshenv
:
export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox
Executar o Puppeteer no Travis CI
Executamos nossos testes para o Puppeteer no Travis CI até a v6.0.0. Depois disso, migramos para o GitHub Actions. Consulte
.travis.yml
(v5.5.0)
para referência.
Confira algumas práticas recomendadas:
- O serviço xvfb precisa ser iniciado para executar o Chromium no modo não headless.
- É executado no Xenial Linux no Travis por padrão.
- Executa
npm install
por padrão node_modules
é armazenado em cache por padrão
.travis.yml
pode ter esta aparência:
language: node_js
node_js: node
services: xvfb
script:
- npm run test
Executar o Puppeteer no CircleCI
- Comece com uma imagem NodeJS na configuração.
yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
- Dependências como
libXtst6
provavelmente precisam ser instaladas comapt-get
. Portanto, use o esfera threetreeslight/puppeteer (instructions) ou cole partes da fonte na sua própria configuração. - Por fim, se você usa o Puppeteer com o Jest, pode encontrar um erro que gera processos filhos:
shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11)
Isso provavelmente é causado pela detecção automática do número de processos em toda a máquina (36
) em vez do número permitido no contêiner (2
). Para corrigir isso, definajest --maxWorkers=2
no comando de teste.
Executar o Puppeteer no Docker
Pode ser complicado instalar o Chrome headless no Docker. O Chromium empacotado que o Puppeteer instala não tem as dependências necessárias da biblioteca compartilhada.
Para corrigir, você precisa instalar as dependências que faltam e o pacote mais recente do Chromium no 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"]
Criar o contêiner:
docker build -t puppeteer-chrome-linux .
Execute o contêiner passando node -e "<yourscript.js content as a string>"
como o comando:
docker run -i --init --rm --cap-add=SYS_ADMIN \
--name puppeteer-chrome puppeteer-chrome-linux \
node -e "`cat yourscript.js`"
Há um exemplo completo em https://github.com/ebidel/try-puppeteer (link em inglês) que mostra como executar esse Dockerfile a partir de um servidor da Web em execução no ambiente flexível do App Engine (Node).
Executar no Alpine
O pacote Chromium mais recente com suporte no Alpine é o 100, que corresponde ao Puppeteer v13.5.0.
Exemplo de 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
...
Práticas recomendadas com o Docker
Por padrão, o Docker executa um contêiner com 64 MB de espaço de memória compartilhada do /dev/shm
.
Esse valor geralmente é muito pequeno
para o Chrome e faz com que ele falhe ao renderizar páginas grandes. Para corrigir,
execute o contêiner com docker run --shm-size=1gb
para aumentar o tamanho de
/dev/shm
. Desde o Chrome 65, isso não é mais necessário. Em vez disso, inicie o
navegador com a flag --disable-dev-shm-usage
:
const browser = await puppeteer.launch({
args: ['--disable-dev-shm-usage'],
});
Isso grava arquivos de memória compartilhada em /tmp
em vez de /dev/shm
. Consulte
crbug.com/736452 (link em inglês).
Você encontrou outros erros estranhos ao iniciar o Chrome? Tente executar seu contêiner
com docker run --cap-add=SYS_ADMIN
ao desenvolver localmente. Como o
Dockerfile adiciona um usuário pptr
como sem privilégios, é possível que ele não tenha todos os
privilégios necessários.
Vale a pena conferir o dumb-init caso você
esteja passando por vários processos zumbis que persistem no Chrome. Há tratamento especial
para processos com PID=1
, o que dificulta o encerramento correto do Chrome
em alguns casos (como com o Docker).
Executar o Puppeteer na nuvem
No Google App Engine
O ambiente de execução do Node.js do ambiente padrão do App Engine vem com todos os pacotes de sistema necessários para executar o Headless Chrome.
Para usar puppeteer
, liste o módulo como uma dependência em package.json
e implante no Google App Engine. Leia mais sobre como usar o puppeteer
no App Engine
seguindo o tutorial oficial.
No Google Cloud Functions
O ambiente de execução do Node.js 10 do Google Cloud Functions vem com todos os pacotes de sistema necessários para executar o Headless Chrome.
Para usar puppeteer
, liste o módulo como uma dependência em package.json
e
implante a função no Google Cloud Functions usando o ambiente de execução nodejs10
.
Executar o Puppeteer no Google Cloud Run
O ambiente de execução padrão do Node.js do
Google Cloud Run não vem com os
pacotes de sistema necessários para executar o Headless Chrome. Configure seu próprio Dockerfile
e
inclua as dependências ausentes.
No Heroku
A execução do Puppeteer no Heroku requer algumas dependências adicionais que não estão incluídas na caixa do Linux que o Heroku gera. Para adicionar as dependências na implantação, adicione o buildpack do Puppeteer Heroku à lista de pacotes de criação do aplicativo em Configurações > Buildpacks.
O URL do buildpack é https://github.com/jontewks/puppeteer-heroku-buildpack
Use o modo '--no-sandbox'
ao iniciar o Puppeteer. Isso pode ser
feito transmitindo-o como um argumento para a chamada de .launch()
:
puppeteer.launch({ args: ['--no-sandbox'] });
.
Ao clicar em "Adicionar buildpack", cole o URL na entrada e clique em Salvar. Na próxima implantação, o aplicativo também instalará as dependências que o Puppeteer precisa executar.
Se você precisa renderizar caracteres chineses, japoneses ou coreanos, pode ser necessário usar um buildpack com arquivos de fonte adicionais, como https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack
Há também outro guia do @timleland que inclui um projeto de exemplo.
No AWS Lambda
O AWS Lambda limita os tamanhos dos pacotes de implantação para aproximadamente 50 MB. Isso apresenta desafios para a execução da versão headless do Chrome (e, portanto, do Puppeteer) no Lambda. A comunidade reuniu alguns recursos que abordam os problemas:
- Binário do Chromium para AWS Lambda e Google Cloud Functions (atualizado com a versão estável mais recente do Puppeteer)
- O Chrome/Chromium no AWS Lambda do Marco Lüthy é um plug-in sem servidor e está desatualizado.
Instância do AWS EC2 executando o Amazon-Linux
Se você tiver uma instância do EC2 que executa o amazon-linux no pipeline de CI/CD e quiser executar testes do Puppeteer no amazon-linux, siga estas etapas.
Para instalar o Chromium, primeiro ative
amazon-linux-extras
, que faz parte do EPEL (Extra Packages for Enterprise Linux):sudo amazon-linux-extras install epel -y
Em seguida, instale o Chromium:
sudo yum install -y chromium
Agora o Puppeteer pode iniciar o Chromium para executar os testes. Se você não ativar o EPEL
e continuar instalando o Chromium como parte de npm install
, o Puppeteer não poderá
iniciar o Chromium devido à indisponibilidade de libatk-1.0.so.0
e de muitos outros pacotes.
Problemas de transpilação de código
Se você estiver usando um transpilador JavaScript, como babel ou TypeScript, é possível que a chamada de
evaluate()
com uma função assíncrona não funcione. Isso ocorre porque
puppeteer
usa Function.prototype.toString()
para serializar funções, enquanto
os transpiladores podem alterar o código de saída de modo que ele seja incompatível
com puppeteer
.
Algumas soluções alternativas para esse problema são instruir o transpilador a não confundir
o código. Por exemplo, configurar o TypeScript para usar a versão mais recente do ecma
("target": "es2018"
). Outra solução alternativa é usar modelos de string
em vez de funções:
await page.evaluate(`(async() => {
console.log('1');
})()`);