如何在 Docker 中设置无头 Chrome Node.js 服务器
作者:Tigran Bayburtsyan ✏️
随着应用程序开发过程中自动化 UI 测试的兴起,无头浏览器变得非常流行。此外,网站爬虫和基于 HTML 的内容分析也有着无数的应用场景。
在99%的情况下,你实际上并不需要浏览器图形用户界面,因为它是完全自动化的。运行图形用户界面比启动一个基于Linux的服务器或在Kubernetes等微服务集群上扩展一个简单的Docker容器成本更高。
但言归正传。简而言之,为了最大限度地提高灵活性和可扩展性,拥有一个基于 Docker 容器的无头浏览器变得越来越重要。在本教程中,我们将演示如何创建一个 Dockerfile,以便在 Node.js 中设置一个无头 Chrome 浏览器。
使用 Node.js 的无头 Chrome
Node.js 是 Google Chrome 开发团队使用的主要语言接口,它集成了一个几乎原生的库 Puppeteer.js,用于与 Chrome 通信。该库通过 DevTools 界面使用 WebSocket 或基于系统管道的协议,可以执行各种操作,例如截屏、测量页面加载指标、连接速度和下载内容大小等等。您可以使用它在不同的设备模拟环境中测试 UI 并截屏。最重要的是,Puppeteer 不需要运行 GUI;所有操作都可以在无头模式下完成。
const puppeteer = require('puppeteer');
const fs = require('fs');
Screenshot('https://google.com');
async function Screenshot(url) {
const browser = await puppeteer.launch({
headless: true,
args: [
"--no-sandbox",
"--disable-gpu",
]
});
const page = await browser.newPage();
await page.goto(url, {
timeout: 0,
waitUntil: 'networkidle0',
});
const screenData = await page.screenshot({encoding: 'binary', type: 'jpeg', quality: 30});
fs.writeFileSync('screenshot.jpg', screenData);
await page.close();
await browser.close();
}
上面展示的是在无头 Chrome 浏览器中截屏的简单可操作代码。请注意,我们没有指定 Google Chrome 的可执行文件路径,因为 Puppeteer 的 NPM 模块已经内置了无头 Chrome 版本。Chrome 的开发团队在简化库的使用和减少必要的设置方面做得非常出色。这也使得我们更容易将此代码嵌入到 Docker 容器中。
在 Docker 容器内的 Google Chrome
从上面的代码来看,在容器内运行浏览器似乎很简单,但安全问题不容忽视。默认情况下,容器内的所有程序都以 root 用户身份运行,浏览器会在本地执行 JavaScript 文件。
当然,谷歌浏览器本身是安全的,它不允许用户通过浏览器脚本访问本地文件,但仍然存在潜在的安全风险。您可以创建一个专门用于运行浏览器的新用户,从而最大限度地降低这些风险。此外,谷歌默认启用了沙盒模式,该模式会限制外部脚本访问本地环境。
以下是负责 Google Chrome 安装的 Dockerfile 示例。我们将选择 Alpine Linux 作为基础容器,因为它作为 Docker 镜像占用空间最小。
FROM alpine:3.6
RUN apk update && apk add --no-cache nmap && \
echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
apk update && \
apk add --no-cache \
chromium \
harfbuzz \
"freetype>2.8" \
ttf-freefont \
nss
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
....
....
该run命令负责处理 Edge 仓库,以获取适用于 Linux 的 Chromium 以及运行 Alpine 版 Chrome 所需的库。关键在于确保我们不会下载嵌入在 Puppeteer 中的 Chrome。否则,容器镜像中将占用不必要的空间,因此我们需要保留PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true环境变量。
运行 Docker 构建后,我们得到了 Chromium 可执行文件:/usr/bin/chromium-browser。这应该是我们的 Puppeteer Chrome 主可执行文件路径。
现在让我们跳到 JavaScript 代码,完成 Dockerfile。
将 Node.js 服务器和 Chromium 容器结合起来
在继续之前,我们先修改一下代码,使其能够以微服务的形式用于截取指定网站的屏幕截图。为此,我们将使用 Express.js 搭建一个基本的 HTTP 服务器。
// server.js
const express = require('express');
const puppeteer = require('puppeteer');
const app = express();
// /?url=https://google.com
app.get('/', (req, res) => {
const {url} = req.query;
if (!url || url.length === 0) {
return res.json({error: 'url query parameter is required'});
}
const imageData = await Screenshot(url);
res.set('Content-Type', 'image/jpeg');
res.set('Content-Length', imageData.length);
res.send(imageData);
});
app.listen(process.env.PORT || 3000);
async function Screenshot(url) {
const browser = await puppeteer.launch({
headless: true,
executablePath: '/usr/bin/chromium-browser',
args: [
"--no-sandbox",
"--disable-gpu",
]
});
const page = await browser.newPage();
await page.goto(url, {
timeout: 0,
waitUntil: 'networkidle0',
});
const screenData = await page.screenshot({encoding: 'binary', type: 'jpeg', quality: 30});
await page.close();
await browser.close();
// Binary data of an image
return screenData;
}
这是完成 Dockerfile 的最后一步。运行后docker build -t headless:node,我们将得到一个包含 Node.js 服务和用于屏幕截图的无头 Chrome 浏览器的镜像。
截图固然有趣,但它还有无数其他用途。幸运的是,上述流程几乎适用于所有场景。大多数情况下,只需要对 Node.js 代码进行少量修改。其余的都是相当标准的环境配置。
无头 Chrome 浏览器常见问题
Google Chrome 在运行时会占用大量内存,因此 Headless Chrome 在服务器端也占用大量内存也就不足为奇了。如果一直保持浏览器窗口打开并多次重复使用同一个浏览器实例,最终会导致服务崩溃。
最佳方案是遵循“一个连接对应一个浏览器实例”的原则。虽然这比每个浏览器管理多个页面成本更高,但坚持只使用一个页面和一个浏览器能使系统更加稳定。当然,这一切都取决于个人偏好和具体使用场景。根据您的独特需求和目标,您或许可以找到一个折衷方案。
以性能监控工具Hexometer的官方网站为例。该环境包含一个远程浏览器服务,其中包含数百个空闲浏览器池。这些浏览器池会在需要执行任务时通过 WebSocket 建立新的连接,但严格遵循“一页一浏览器”的原则。这使得它不仅能够稳定高效地保持运行中的浏览器处于空闲状态,还能确保它们始终处于活动状态。
通过 WebSocket 连接的 Puppeteer 非常稳定,您可以通过创建类似browserless.io的自定义服务来实现类似的功能(也有开源版本)。
...
...
const browser = await puppeteer.launch({
browserWSEndpoint: `ws://repo.treescale.com:6799`,
});
...
...
这将使用相同的浏览器管理协议连接到无头 Chrome DevTools 套接字。
结论
在容器中运行浏览器提供了极大的灵活性和可扩展性。而且,它比传统的基于虚拟机的实例便宜得多。现在,我们只需使用 AWS Fargate 或 Google Cloud Run 等容器服务,即可在需要时触发容器执行,并在几秒钟内扩展到数千个实例。
最常见的用例仍然是使用 Jest 和 Mocha 进行UI 自动化测试。但考虑到实际上你可以在容器中使用 Node.js 来操作整个网页,那么它的用例就只受限于你的想象力了。
仅限 200 个 ✅:监控失败,并在生产环境中显示 GraphQL 请求
虽然 GraphQL 提供了一些用于调试请求和响应的功能,但确保 GraphQL 能可靠地为生产应用提供资源才是真正的挑战。如果您希望确保对后端或第三方服务的网络请求能够成功,不妨试试 LogRocket。
LogRocket就像 Web 应用的 DVR,它会记录网站上发生的一切。您无需猜测问题原因,即可聚合并报告有问题的 GraphQL 请求,从而快速了解根本原因。此外,您还可以跟踪 Apollo 客户端状态并检查 GraphQL 查询的键值对。
LogRocket 会对您的应用进行检测,记录页面加载时间、首字节到达时间、慢网络请求等基准性能指标,并记录 Redux、NgRx 和 Vuex 的操作/状态。立即免费开始监控。
文章《如何在 Docker 中设置无头 Chrome Node.js 服务器》最初发表于LogRocket 博客。
文章来源:https://dev.to/bnevilleoneill/how-to-set-up-a-headless-chrome-node-js-server-in-docker-leh

