最近在研究如何用JavaScript实现网页截图,包括浏览器运行的JS和后台运行的nodeJs。我主要看了以下内容:PhantomJSPuppeteer(chromeheadless)SlimerJSdom-to-imagehtml2canvas测试的网页使用了WebGL技术,所以下面的总结会和WebGL相关。名词定义headlessbrowser无界面浏览器,多用于网页自动化测试,网页截图,网页网络监控等。实现截屏的代码,假设文件名为github.js://创建网页实例varpage=require('webpage').create();//加载页面page.open('http://github.com/',function(){//网页截图保存到github.png文件中page.render('github.png');phantom.exit();})运行:phantomjsgithub.js普通页面是没有问题的,但是如果你运行一个包含WebGL的页面,你会发现截图是错误的。经过一番排查,发现不支持WebGL,githubissue。总结:PhantomJs已经停止维护,不建议继续使用。停止维护的一个原因是chrome发布的headless版本对其造成了一定的影响。不支持WebGL。不过,还是有开发者表示可以自己给PhantomJS添加WebGL支持。不过这个方案目前超出了我的知识范围,所以没有继续研究下去。Puppeteer(chromeheadless)Puppeteer是一个Node库,它提供了一个API来控制chrome和chromium。默认以headless模式运行,也支持interface运行。截图代码example.js:constpuppeteer=require('puppeteer');(async()=>{constbrowser=awaitpuppeteer.launch();constpage=awaitbrowser.newPage();awaitpage.setViewport({//设置窗口大小width:600,height:800});awaitpage.goto('https://example.com');//打开页面awaitpage.screenshot({path:'example.png'});//路径:截图文件保存路径awaitbrowser.close();})();运行:nodeexample.js接下来看screenshot方法的实现原理:screenshot源码位于lib/cjs/puppeteer/common/Page.js文件中,是一个异步方法:asyncscreenshot(options={}){//...returnthis._screenshotTaskQueue.postTask(()=>this._screenshotTask(screenshotType,options));}async_screenshotTask(format,options){//...constresult=awaitthis._client.send('Page.captureScreenshot',{format,quality:options.quality,clip,});//...}this._client又是什么.send?别担心,让我们回顾一下Puppeteer的定义:Puppeteer是一个Node库,它提供了一个高级API来通过DevTools协议控制Chrome或Chromium。看到最后你遇到过DevTools协议吗?这是什么:ChromeDevTools协议允许使用工具来检测、检查、调试和分析Chromium、Chrome和其他基于Blink的浏览器。可以在这个博客中找到详细的解释。简单的说,Puppeteer通过WebSocket向浏览器发送消息。按照ChromeDevtoolsProtocol数据指示浏览器执行一些操作。然后,浏览器通过WebSocket将结果返回给Puppeteer。这个过程是异步的,所以如果你看源码,会发现很多async/await。所以截图方法调用了ChromeDevtoolsProtocol的captureScreenshot。总结:支持WebGL。如果网页比较复杂,截屏时间就相当长。我测试的页面是几百毫秒。Puppeteer是(CDP)ChromeDevtools协议功能的包装器。大部分功能都是通过WebSocket转给CDP处理的。SlimerJSSlimerJS类似于PhantomJS。不同的是,SlimerJS是基于Firefox的浏览器引擎Gecko,而不是Webkit。SlimerJS可以通过npm安装,最新版本是1.x。但是,兼容的Firefox版本为53.0到59.0。我认为现在最新版本的Firefox是82。因为我的机器上安装的是最新版本的Firefox,所以我不得不安装一个旧版本的Firefox,比如59.0.您可以参考这篇文章来安装旧版本的Firefox。我是mac系统,感觉安装还是挺容易的。截图代码screenshot.js:varpage=require('webpage').create();page.open("http://slimerjs.org",function(status){page.viewportSize={width:1024,height:第768章运行//macOS设置Firefox路径exportSLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox./node_modules/.bin/slimerjsscreenshot.js//我在本地安装了slimer包。需要注意的是,SLIMERJSLAUNCHER=/Applications/Firefox.app/Contents/MacOS/firefox开始是Firefox的默认安装路径,因为我一开始就是Firefox浏览器。于是启动最新版本的浏览器,然后报错,说不兼容。我之前安装了一个59版本的Firefox,那么这个Firefox浏览器的路径是什么呢?在应用程序中,我将这个旧版火狐命名为Firefox59,路径为/Applications/Firefox59.app/Contents/MacOS/firefox。将SLIMERJSLAUNCHER重置为59版本的火狐浏览器后,即可成功。但是Puppeteer默认会打开浏览器界面,也就是非headless模式。如果你想使用无头模式,你可以./node_modules/.bin/slimerjs--headlessscreenshot.js但是,无头模式不支持WebGL。我在写例子的时候发现一个明显的区别,Puppeteer截图是异步函数,而SlimerJS截图是同步函数?出于好奇,我阅读了源代码(src/modules/slimer-sdk/webpage.js):render:function(filename,options){//...比率,finalOptions.onlyViewport,这个);}canvas.toBlob(function(blob){letreader=newbrowser.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){//...//创建画布letcanvas=window.document.createElementNS("http://www.w3.org/1999/xhtml","canvas");canvas.width=canvasWidth;canvas.height=canvasHeight;让ctx=canvas.getContext("2d");ctx.scale(比率,比率);ctx.drawWindow(窗口,clip.left,clip.top,clip.width,clip.height,"rgba(0,0,0,0)");ctx.restore();returncanvas;}关键代码是ctx.drawWindowwhat这一行?JS原生API也支持直接截图?CanvasRenderingContext2D.drawWindow():仅Firefox支持,非标准API,已弃用。总结1.0版支持的Firefox版本为53.??0到59.0。无法保证提供最新版本的Firefox。在无头模式下,不支持WebGL。dom-to-imagedom-to-image:前端截图的开源库。工作原理是:SVG的foreignObject标签可以包裹任何html内容。然后,为了渲染一个节点,主要执行以下步骤:递归复制原始dom节点和后代节点;递归地将原始节点和后代节点的样式应用到相应的复制节点和后代节点;字体处理;图像处理;序列化复制的节点,插入到foreignObject中,然后组成一个svg,然后生成一个dataURL;如果要获取PNG内容或原始像素值,可以先使用数据URL创建图像并使用离屏画布渲染此图像,然后从画布中获取所需数据。测试时发现无法加载外部资源,简单了解后放弃。html2canvashtml2canvas。在网上查了一下,感觉有一篇文章写的很好:浅析两种用js实现网页截图的方法。有兴趣的可以看看。未经验证的猜想虽然后两种是前端实现方式,但是结合上面提到的headless库,也是可以实现后端截图的。以Puppeteer的API为例,可以先使用page.addScriptTag(options)将前端截图库添加到网页中,然后在page.evaluate(pageFunction[,...args])就是这样,因为pageFunction的执行上下文是网页上下文,所以可以拿到文档等对象。总结一个简单的研究截图,写一篇博客整理一下思路。如果文章中的描述有错误,欢迎大家留言。
