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

更新日期: 2020-01-14阅读: 2.6k标签: Docker

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

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

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


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 文件。

当然,Google Chrome 是安全的,它不允许用户从基于浏览器的脚本访问本地文件,但仍然存在潜在的安全风险。你可以通过创建新用户来执行浏览器本身的特定操作来最大大地降低这些风险。 Google 默认还启用了沙箱模式,该模式限制了外部脚本访问本地环境。

以下是负责 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 来操纵整个网页,则用例仅受到你想象力的限制。

原文:https://blog.logrocket.com/how-to-set-up-a-headless-chrome-node-js-server-in-docker/

链接: https://www.fly63.com/article/detial/7307

npm私有仓库 配置verdaccio在docker环境

前端开发过程中,少不了自己封装一些通用的包,但又不想放在公共的平台,所以搭建一个npm私有的仓库是很有必要的。在这里简单介绍如何使用 verdoccio 在docker环境下的配置。verdoccio,轻量级私有npm代理注册表。

ASP.NET Core 如何在运行Docker容器时指定容器外部端口(docker compose)

所以我们可以通过修改docker compose的配置文件来完成我们的需求。熟悉Docker的都应该知道容器运行时其内部会有一个端口以映射到我们外部的端口,我们需要固定的就是这个外部端口。

DOCKER上运行DOTNET CORE

下载microsoft/dotnet镜像、创建.NET Core MVC项目、上面dotnet restore这一步可能会卡很久遇到超时的状况,因为Nuget在国外的原因,博客园有提供加速镜像,参照设定好之后,速度会快很多

Docker---大型项目容器化改造

虚拟化和容器化是项目云化不可避免的两个问题。虚拟化由于是纯平台操作,一个运行于linux操作系统的项目几乎不需要做任何改造就可以支持虚拟化。而项目如果要支持容器化则需要做许多细致的改造工作。

开发人员爱Docker的10个理由

Stack Overflow开发人员调查中,开发人员将Docker评为:最受欢迎的平台,最喜欢的平台,最常用的平台。来自世界各地的近90,000名开发人员对调查做出了回应。

Docker部署网站之后映射域名

Docker中部署tomcat相信大家也都知道,不知道的可以google 或者bing 一下。这里主要是为了记录在我们启动容器之后,tomcat需要直接定位到网站信息,而不是打开域名之后,还得加个blog后缀才能访问到我们的网站首页。

微服务架构之「 容器技术 」

现在一聊到容器技术,大家就默认是指 Docker 了。但事实上,在 Docker 出现之前,PaaS社区早就有容器技术了,以 Cloud Foundry、OpenShift 为代表的就是当时的主流。那为啥最终还是 Docker 火起来了呢?

Docker部署ngnix静态网站

首先获取ngnix镜像(默认的是最新版),先来编写一个最简单的Dockerfile,一个Dockerfile修改该Nginx镜像的首页.Dockerfile是一个文本文件,其中包含了若干条指令

如何从单独的容器调试运行中的Docker容器?

容器非常适合封装软件,但是有时一味地改造容器镜像以使其尽可能小时,您可能走得太远。我们需要在“简洁”的镜像和无法调试的镜像之间找到很好的平衡。看到人们调试正在运行的容器的正常方法

内部集群的 DNS server 搭建

当我们使用 traefik 反向代理和自动服务发现后,我们对集群内部的服务分为两类:公有服务。如我的博客,网站,以及为它们提供服务的 API。我们可以通过公有的域名去映射服务使得外网能够访问

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!