无头 Chrome 无法在 Windows 上启动
某些 Chrome 政策可能会强制要求运行带有特定扩展程序的 Chrome 或 Chromium。
Puppeteer 默认传递 --disable-extensions
标志,因此当此类政策生效时,Puppeteer 无法启动。
如需解决此问题,请尝试在不使用该标志的情况下运行:
const browser = await puppeteer.launch({
ignoreDefaultArgs: ['--disable-extensions'],
});
上下文:问题 3681。
无头 Chrome 无法在 UNIX 上启动
确保安装所有必要的依赖项。您可以在 Linux 机器上运行 ldd chrome | grep not
来检查缺少哪些依赖项。
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 依赖项
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
安装依赖项后,您需要使用此命令更新 nss 库
yum update nss -y
查看讨论:
无头 Chrome 会停用 GPU 合成
Chrome 和 Chromium 需要 --use-gl=egl
才能在无头模式下启用 GPU 加速。
const browser = await puppeteer.launch({
headless: true,
args: ['--use-gl=egl'],
});
Chrome 已下载,但无法在 Node.js 上启动
如果您在尝试启动 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
这表示已下载浏览器,但无法正确解压。
最常见的原因是 Node.js v14.0.0 中的一个 bug 会破坏 extract-zip
,Puppeteer 使用该模块将浏览器下载内容提取到正确位置。该 bug 在 Node.js v14.1.0 中已得到修复,因此请确保您使用的是该版本或更高版本。
设置 Chrome Linux 沙盒
为了保护主机环境免受不受信任的 Web 内容的影响,Chrome 使用了多层沙盒机制。为使此操作正常运行,应首先配置主机。如果没有良好的沙盒可供 Chrome 使用,Chrome 就会崩溃,并显示 No usable sandbox!
错误。
如果您绝对信任您在 Chrome 中打开的内容,则可以使用 --no-sandbox
参数启动 Chrome:
const browser = await puppeteer.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
在 Chromium 中配置沙盒的方法有两种。
[推荐] 启用用户命名空间克隆
只有新型内核支持 Sser 命名空间克隆。无特权用户命名空间通常可以正常启用,但(未经过沙盒屏蔽的)非 Root 进程可能会引发更多内核攻击面,进而导致此类进程提升到内核特权。
sudo sysctl -w kernel.unprivileged_userns_clone=1
[替代方案] 设置 SetUID 沙盒
setuid 沙盒作为独立的可执行文件,位于 Puppeteer 下载的 Chromium 旁边。您可以针对不同的 Chromium 版本重复使用同一沙盒可执行文件,因此在每个主机环境中只能执行以下操作一次:
# 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
您可能希望默认导出 CHROME_DEVEL_SANDBOX
环境变量。在这种情况下,请将以下代码添加到 ~/.bashrc
或 .zshenv
中:
export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox
在 Travis CI 上运行 Puppeteer
我们在 Travis CI 上对 Puppeteer 进行了测试,直到 6.0.0 版为止,之后我们迁移到了 GitHub Actions。您可以参阅 .travis.yml
(v5.5.0)。
下面提供了一些最佳实践:
- 应启动 xvfb 服务,以便在无头模式下运行 Chromium
- 默认情况下在 Travis 上的 Xenial Linux 上运行
- 默认情况下运行
npm install
node_modules
默认缓存
.travis.yml
可能如下所示:
language: node_js
node_js: node
services: xvfb
script:
- npm run test
在 CircleCI 上运行 Puppeteer
- 从配置中的 NodeJS 映像开始。
yaml docker: - image: circleci/node:14 # Use your desired version environment: NODE_ENV: development # Only needed if puppeteer is in `devDependencies`
libXtst6
等依赖项可能需要使用apt-get
进行安装,因此请使用 threetreeslight/puppeteer orb(instructions),或将其部分源代码粘贴到您自己的配置中。- 最后,如果您通过 Jest 使用 Puppeteer,则可能会遇到生成子进程的错误:
shell [00:00.0] jest args: --e2e --spec --max-workers=36 Error: spawn ENOMEM at ChildProcess.spawn (internal/child_process.js:394:11)
这可能是由于 Jest 自动检测了整台机器上的进程数 (36
),而不是容器允许的数量 (2
)。如需解决此问题,请在测试命令中设置jest --maxWorkers=2
。
在 Docker 中运行 Puppeteer
在 Docker 中启动并运行无头 Chrome 可能并非易事。Puppeteer 安装的捆绑的 Chromium 缺少必要的共享库依赖项。
如需解决此问题,您需要在 Dockerfile 中安装缺少的依赖项和最新的 Chromium 软件包:
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"]
构建容器。
docker build -t puppeteer-chrome-linux .
通过传递 node -e "<yourscript.js content as a string>"
作为命令来运行容器:
docker run -i --init --rm --cap-add=SYS_ADMIN \
--name puppeteer-chrome puppeteer-chrome-linux \
node -e "`cat yourscript.js`"
https://github.com/ebidel/try-puppeteer 上提供了一个完整示例,展示了如何从 App Engine Flex (Node) 上运行的网络服务器运行此 Dockerfile。
在 Alpine 上运行
Alpine 支持的最新 Chromium 软件包为 100,对应于 Puppeteer v13.5.0。
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
...
使用 Docker 的最佳实践
默认情况下,Docker 运行一个 /dev/shm
共享内存空间为 64MB 的容器。这对于 Chrome 来说通常过小,并且会导致 Chrome 在呈现大型页面时崩溃。如需解决此问题,请使用 docker run --shm-size=1gb
运行容器以增加 /dev/shm
的大小。从 Chrome 65 开始,不再需要这样做。而是改用 --disable-dev-shm-usage
标志启动浏览器:
const browser = await puppeteer.launch({
args: ['--disable-dev-shm-usage'],
});
这会将共享内存文件写入 /tmp
(而非 /dev/shm
)。请访问 crbug.com/736452。
启动 Chrome 时,您是否看到了其他奇怪的错误?在本地开发时,请尝试使用 docker run --cap-add=SYS_ADMIN
运行容器。由于 Dockerfile 将 pptr
用户添加为非特权用户,因此它可能没有所有必要的权限。
如果遇到 Chrome 进程中持续存在大量僵尸进程的情况,不妨试试 dumb-init。设置了 PID=1
的进程会受到特殊处理,这使得在某些情况下(例如使用 Docker)很难正确终止 Chrome。
在云端运行 Puppeteer
在 Google App Engine 上
App Engine 标准环境的 Node.js 运行时包含运行 Headless Chrome 所需的所有系统软件包。
如需使用 puppeteer
,请在 package.json
中将该模块列为依赖项,并部署到 Google App Engine。如需详细了解如何在 App Engine 上使用 puppeteer
,请参阅官方教程。
适用于 Google Cloud Functions
Google Cloud Functions 的 Node.js 10 运行时包含运行无头 Chrome 所需的所有系统软件包。
如需使用 puppeteer
,请在 package.json
中将该模块列为依赖项,并使用 nodejs10
运行时将您的函数部署到 Google Cloud Functions。
在 Google Cloud Run 上运行 Puppeteer
Google Cloud Run 的默认 Node.js 运行时不包含运行 Headless Chrome 所需的系统软件包。设置您自己的 Dockerfile
并添加缺少的依赖项。
在 Heroku 上
在 Heroku 上运行 Puppeteer 需要一些其他依赖项,这些依赖项未包含在 Heroku 为您启动的 Linux 机箱中。如需在部署时添加依赖项,请在“Settings”>“Buildpacks”下将 Puppeteer Heroku buildpack 添加到应用的 Buildpack 列表中。
Buildpack 的网址为 https://github.com/jontewks/puppeteer-heroku-buildpack
确保在启动 Puppeteer 时使用 '--no-sandbox'
模式。为此,您可以将它作为参数传递给 .launch()
调用:puppeteer.launch({ args: ['--no-sandbox'] });
。
点击 add buildpack 时,将该网址粘贴到输入字段中,然后点击 save。 在下次部署时,您的应用还将安装 Puppeteer 需要运行的依赖项。
如果您需要渲染中文、日语或韩语字符,则可能需要使用具有其他字体文件的 buildpack,例如 https://github.com/CoffeeAndCode/puppeteer-heroku-buildpack
我们还提供了另一个 @timleland 指南,其中包括一个示例项目。
在 AWS Lambda 上
AWS Lambda 将部署包的限制限制为约 50 MB。这就给在 Lambda 上运行无头 Chrome(以及 Puppeteer)带来了挑战。社区整理了一些资源来解决这些问题:
- 适用于 AWS Lambda 和 Google Cloud Functions 的 Chromium 二进制文件(已使用 Puppeteer 的最新稳定版进行更新)
- Marco Lüthy 的 Chrome/Chromium on AWS Lambda 是一款无服务器插件,但已过时。
运行 Amazon-Linux 的 AWS EC2 实例
如果您的 CI/CD 流水线中有一个运行 amazon-linux 的 EC2 实例,并且您希望在 amazon-linux 中运行 Puppeteer 测试,请按照以下步骤操作。
如需安装 Chromium,您必须先启用
amazon-linux-extras
,它是 EPEL (Extra Packages for Enterprise Linux) 的一部分:sudo amazon-linux-extras install epel -y
接下来,安装 Chromium:
sudo yum install -y chromium
现在,Puppeteer 可以启动 Chromium 来运行测试。如果您未启用 EPEL 并继续安装 Chromium 作为 npm install
的一部分,Puppeteer 无法启动 Chromium,因为 libatk-1.0.so.0
和其他软件包不可用。
代码转译问题
如果您使用的是 babel 或 TypeScript 等 JavaScript 转译器,则使用异步函数调用 evaluate()
可能不起作用。这是因为,虽然 puppeteer
使用 Function.prototype.toString()
对函数进行序列化,而转译器可能会以与 puppeteer
不兼容的方式更改输出代码。
此问题的一些解决方法是指示转译器不要混淆代码,例如,将 TypeScript 配置为使用最新的 ecma 版本 ("target": "es2018"
)。另一种解决方法是使用字符串模板,而不是函数:
await page.evaluate(`(async() => {
console.log('1');
})()`);