关于node.js:JavaScript实现网页截屏方法总结

4次阅读

共计 4788 个字符,预计需要花费 12 分钟才能阅读完成。

最近钻研了下如何利用 JavaScript 实现网页截屏,包含在浏览器运行的 JS,以及在后盾运行的 nodeJs 的办法。次要看了以下几个:

  1. PhantomJS
  2. Puppeteer(chrome headless)
  3. SlimerJS
  4. dom-to-image
  5. html2canvas

测试的网页应用了 WebGL 技术,所以上面的总结会和 WebGL 相干。

名词定义

headless browser

无界面浏览器,多用于网页自动化测试、网页截屏、网页的网络监控等。

PhantomJS

PhantomJS 是能够通过 JS 进行编程的 headless 浏览器,应用的是 QtWebKit 内核。

实现截屏的代码,假如文件名为 github.js:

// 创立一个网页实例
var page = require('webpage').create();
// 加载页面
page.open('http://github.com/', function () {
  // 给网页截屏,保留到 github.png 文件中
  page.render('github.png');
  phantom.exit();})

运行:

phantomjs github.js

一般的页面没有问题,然而如果运行蕴含 WebGL 的页面,发现截屏不对。通过一些考察,发现不反对 WebGL,github issue。

总结:

  1. PhantomJs 曾经进行保护了,所以不太倡议持续应用。进行保护的一个起因是 chrome 公布的 headless 版本对它造成了肯定冲击。
  2. 不反对 WebGL。然而,还是有开发者说能够本人给 PhantomJS 增加 WebGL 反对,不过,这个计划目前超出我的常识范畴了,就没有持续钻研。

Puppeteer(chrome headless)

Puppeteer 是一个 Node 库,提供了管制 chrome 和 chromium 的 API。默认运行 headless 模式,也反对界面运行。

实现截屏的代码 example.js:

const puppeteer = require('puppeteer');

(async () => {const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({ // 设置视窗大小
    width: 600,
    height: 800
  });
  await page.goto('https://example.com'); // 关上页面
  await page.screenshot({path: 'example.png'}); // path: 截屏文件保留门路

  await browser.close();})();

运行:

node example.js

接下来看下 screenshot 办法的实现原理:

screenshot的源码位于 lib/cjs/puppeteer/common/Page.js 文件中,是一个异步办法:

async screenshot(options = {}) {
  // ...
  return this._screenshotTaskQueue.postTask(() => this._screenshotTask(screenshotType, options));
}
async _screenshotTask(format, options) {
  // ...
  const result = await this._client.send('Page.captureScreenshot', {
    format,
    quality: options.quality,
    clip,
  });
  // ...
}

这个 this._client.send 又是个什么货色?别急,咱们从新看下 Puppeteer 的定义:

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol.

看到最初面那个 DevTools Protocol 了吗?这是个什么货色:

The Chrome DevTools Protocol allows for tools to instrument, inspect, debug and profile Chromium, Chrome and other Blink-based browsers.

具体的解释能够看这篇博客。

简略来说,Puppeteer 就是通过 WebSocket 给浏览器发送遵循 Chrome Devtools Protocol 的数据,命令浏览器去执行一些操作。而后,浏览器再通过 WebSocket 把后果返回给 Puppeteer。这个过程是异步的,所以看源代码会发现好多 async/await。

所以 screenshot 办法是调用了 Chrome Devtools Protocol 的 captureScreenshot。

总结:

  1. 反对 WebGL。
  2. 网页比较复杂的话,截屏工夫也挺长的,我测试的页面是几百毫秒。
  3. Puppeteer 是对(CDP)Chrome Devtools Protocol 性能的封装。大部分性能都是通过 WebSocket 传输给 CDP 解决的。

SlimerJS

SlimerJS 和 PhantomJS 相似。不同点是 SlimerJS 是基于火狐的浏览器引擎 Gecko,而不是 Webkit。

SlimerJS 能够通过 npm 装置,最新版本是 1.x。不过兼容的火狐版本是 53.0 到 59.0。我看当初火狐最新版本都 82 了。因为我本机是装置了火狐最新版本的,所以我还得装置一个老版本的火狐,比方 59.0。能够参考这篇装置旧版本的火狐浏览器。我是 mac 零碎,感觉装置还是挺容易的。

实现截屏的代码 screenshot.js:

var page = require('webpage').create();
page.open("http://slimerjs.org", function (status) {page.viewportSize = { width:1024, height:768};
  page.render('screenshot.png');
});

运行

// mac 操作系统设置火狐门路
export SLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox
./node_modules/.bin/slimerjs screenshot.js // 我是部分装置的 slimer 包

须要留神的是 SLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox 启动的是火狐默认的装置门路,因为我一开始就有火狐浏览器,所以启动的是最新版本的浏览器,而后就报错了,说不兼容。在后面我装置过一个 59 版本的火狐,那么这个火狐浏览器的门路是什么?

在应用程序外面我把这个旧版本的火狐命名为 Firefox59,而后这个门路就是 /Applications/Firefox59.app/Contents/MacOS/firefox。从新设置SLIMERJSLAUNCHER 为 59 版本的火狐浏览器之后,发现就能胜利了。

不过,Puppeteer 默认会关上浏览器界面,也就是 non-headless 模式。如果要应用 headless 模式,能够

./node_modules/.bin/slimerjs --headless screenshot.js

不过,headless 模式下,不反对 WebGL。

我在写例子的时候,发现的一个显著的不同就是 Puppeteer 截屏是异步函数,而 SlimerJS 截屏是同步函数?好奇心驱使下,看了下源码(src/modules/slimer-sdk/webpage.js):

render: function(filename, options) {
  // ...
  let canvas = webpageUtils.getScreenshotCanvas(
    browser.contentWindow,
    finalOptions.ratio,
    finalOptions.onlyViewport, this);
  }
  canvas.toBlob(function(blob) {let reader = new browser.contentWindow.FileReader();
    reader.onloadend = function() {content = reader.result;}
    reader.readAsBinaryString(blob);
  }, finalOptions.contentType, finalOptions.quality);
  // ...
}

webpageUtils.getScreenshotCanvas(src/modules/webpageUtils.jsm):

getScreenshotCanvas : function(window, ratio, onlyViewport, webpage) {
  // ...
  // create the canvas
  let canvas = window.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
  canvas.width = canvasWidth;
  canvas.height = canvasHeight;

  let ctx = canvas.getContext("2d");
  ctx.scale(ratio, ratio);
  ctx.drawWindow(window, clip.left, clip.top, clip.width, clip.height, "rgba(0,0,0,0)");
  ctx.restore();

  return canvas;
}

要害代码就是那行ctx.drawWindow。what?JS 原生 API 还反对间接截屏?
CanvasRenderingContext2D.drawWindow():只有火狐反对,曾经被废除掉的非标准定义的规范 API。

总结

  1. 1.0 版本反对的火狐版本是 53.0 到 59.0。不保障最新版本火狐可用。
  2. headless 模式下,不反对 WebGL。

dom-to-image

dom-to-image:前端截屏的开源库。工作原理是:
SVG 的 foreignObject 标签能够包裹任意的 html 内容。那么,为了渲染一个节点,次要进行了以下步骤:

  1. 递归地拷贝原始 dom 节点和后辈节点;
  2. 把原始节点以及后辈节点的款式递归的利用到对应的拷贝后的节点和后辈节点上;
  3. 字体解决;
  4. 图片解决;
  5. 序列化拷贝后的节点,把它插入到 foreignObject 外面,而后组成一个 svg,而后生成一个 data URL;
  6. 如果想得到 PNG 内容或原始像素值,能够先应用 data URL 创立一个图片,应用一个离屏 canvas 渲染这张图片,而后从 canvas 中获取想要的数据。

测试的时候,发现内部资源不能加载,所以简略的理解了后就放弃了。

html2canvas

html2canvas。网上查了下感觉有一篇文章写的挺好的:浅析 js 实现网页截图的两种形式。感兴趣的能够看下。

未验证的猜测

尽管前面这两种是前端的实现形式,然而联合后面讲的 headless 库,也是能够实现后端截屏的。以 Puppeteer 的 API 为例,能够首先应用 page.addScriptTag(options) 往网页中增加前端截屏的库,而后在 page.evaluate(pageFunction[, ...args]) 中的 pageFunction 函数外面写相应的截屏代码就能够了,因为 pageFunction 的执行上下文是网页上下文,所以能够获取到 document 等对象。

总结

对截屏的一个简略钻研,写篇博客整顿下思路。如果文中有叙述谬误的中央,欢送评论留言。

正文完
 0