如何在 Docker 中设置 Headless Chrome Node.js 服务器

做者:Tigran Bayburtsyan

翻译:疯狂的技术宅javascript

原文:https://blog.logrocket.com/ho...前端

未经容许严禁转载java

随着开发过程当中自动 UI 测试的兴起,无头浏览器已变得很是流行。网站爬虫和基于 HTML 的内容分析也有无数的用例。node

在 99% 的场合下,你实际上不须要浏览器 GUI,由于它是彻底自动化的。运行 GUI 比发布基于 Linux 的服务器或在微服务集群(例如 Kubernetes)上扩展简单的Docker容器的代价要高得多。react

可是我跑题了。简而言之,经过一个基于 Docker 容器的无头浏览器来拥有最大的化灵活性和可扩展性变得愈来愈重要。在本教程中,咱们将演示如何建立 Dockerfile 以在 Node.js 中设置无头 Chrome 浏览器。linux

Headless Chrome 与 Node.js

Node.js 是 Google Chrome 开发团队使用的主要环境,它拥有用于与 Chrome 通讯的原生集成库:Puppeteer.js。该库在 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();
}

上面是用于在 Headless Chrome 上截图的简单可执行代码。请注意,咱们未指定 Google Chrome 浏览器的可执行路径,由于 Puppeteer 的 NPM 模块内置了 Headless Chrome 版本。 Chrome 的开发团队不只使库用起来很简单,并且在最小化设置方面作得很是好。这也使咱们把代码嵌入 Docker 容器更加容易。面试

Docker 容器中的 Google Chrome

根据上面的代码,在容器内运行浏览器彷佛很简单,但重要的是不要忽视安全性。默认状况下,容器中的全部内容都以 root 用户身份运行,浏览器会在本地执行 JavaScript 文件。chrome

固然,Google Chrome 是安全的,它不容许用户从基于浏览器的脚本访问本地文件,但仍然存在潜在的安全风险。你能够经过建立新用户来执行浏览器自己的特定操做来最大大地下降这些风险。 Google 默认还启用了沙箱模式,该模式限制了外部脚本访问本地环境。docker

如下是负责 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 命令处理用于获取 Chromium for Linux 的边缘存储库以及在 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 服务的镜像和一个 Headless Chrome 浏览器,用于截取屏幕截图。

截屏颇有趣,可是还有许多其余的使用案例。幸运的是,上述过程几乎适用于全部案例。在大多数状况下,只须要对 Node.js 代码进行较小的更改。其他的是很是标准的环境设置。

Headless Chrome 的常见问题

Google Chrome 在执行时会占用大量内存,所以 Headless Chrome 在服务器端产生相同的状况也就不足为奇了。若是使同一浏览器打开多个实例,则服务最终将崩溃。

最好的解决方案是遵循同一种链接、同一种浏览器实例的原则。尽管这比多个浏览器管理多个页面的成本更高,但仅保留一个浏览器和一个页面会使你的系统更稳定。固然这取决于我的喜爱和你特定的用例。根据独特的需求和目标,你也许能够找到最佳的权衡点。

以性能监控工具 Hexometer 的官方网站为例。该环境包括一个远程浏览器服务,其中包含几百个空闲浏览器池。它们用于在须要执行时经过 WebSocket 打开新链接,但严格遵循一个浏览器一个页面的原则。这使之成为一种稳定而有效的方法,不只可使运行中的浏览器保持空闲状态,并且还能使它们保持活动状态。

经过 WebSocket 进行伪造的链接很是稳定,你能够经过自定义服务(例如 browserless.io)来作相似的事情(也有开源版本)。

...
...

const browser = await puppeteer.launch({
    browserWSEndpoint: `ws://repo.treescale.com:6799`,
});

...
...

这将使用相同的浏览器管理协议链接到 headless Chrome DevTools 套接字。

结论

在容器内运行浏览器可提供不少灵活性和可伸缩性。它也比传统的基于 VM 的实例便宜不少。如今,咱们只需使用容器服务(例如 AWS Fargate 或 Google Cloud Run)就能够在须要时触发容器执行,并在一秒钟内扩展到数千个实例。

最多见的用例还是使用 Jest和 UI automated tests。可是若是你认为能够在容器中用 Node.js 来操纵整个网页,则用例仅受到你想象力的限制。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


相关文章
相关标签/搜索