数据库

使用Headless Chrome进行页面渲染

时间:2010-12-5 17:23:32  作者:应用开发   来源:应用开发  查看:  评论:0
内容摘要:使用 Headless Chrome 进行页面渲染 从属于笔者的 Web 开发基础与工程实践系列文章,主要介绍了使用 Node.js 利用 Chrome Remote Protocol 远程控制 He

使用 Headless Chrome 进行页面渲染 从属于笔者的使用 Web 开发基础与工程实践系列文章,主要介绍了使用 Node.js 利用 Chrome Remote Protocol 远程控制 Headless Chrome 渲染界面的进行基础用法。本文涉及的页面参考与引用资料统一列举在这里。

近日笔者在为 declarative-crawler 编写动态页面的渲染蜘蛛,即在使用 declarative-crawler 爬取知乎美图 一文中介绍的使用 HeadlessChromeSpider 时,需要选择某个无界面浏览器以执行 JavaScript 代码来动态生成页面。进行之前笔者往往是页面使用 PhantomJS 或者 Selenium 执行动态页面渲染,而在 Chrome 59 之后 Chrome 提供了 Headless 模式,渲染其允许在命令行中使用 Chromium 以及 Blink 渲染引擎提供的使用完整的现代 Web 平台特性。需要注意的进行是,Headless Chrome 仍然存在一定的页面局限,站群服务器相较于 Nightmare 或 Phantom 这样的渲染工具, Chrome 的使用远程接口仍然无法提供较好的开发者体验。我们在下文介绍的进行代码示例中也会发现,目前我们仍需要大量的页面模板代码进行控制。

安装与启动

在 Chrome 安装完毕后我们可以利用其包体内自带的命令行工具启动:

$ chrome --headless --remote-debugging-port=9222 https://chromium.org 

笔者为了部署方便,使用 Docker 镜像来进行快速部署,如果你本地存在 Docker 环境,可以使用如下命令快速启动:

docker run -d -p 9222:9222 justinribeiro/chrome-headless 

如果是在 Mac 下本地使用的话我们还可以创建命令别名:

alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome" alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary" alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium" 

如果是在 Ubuntu 环境下我们可以使用 deb 进行安装:

# Install Google Chrome # https://askubuntu.com/questions/79280/how-to-install-chrome-browser-properly-via-command-line sudo apt-get install libxss1 libappindicator1 libindicator7 wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo dpkg -i google-chrome*.deb  # Might show "errors", fixed by next line sudo apt-get install -f 

chrome 命令行也支持丰富的命令行参数,--dump-dom 参数可以将 document.body.innerHTML 打印到标准输出中:

chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/ 

而 --print-to-pdf 标识则会将网页输出位 PDF:

chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/ 

初次之外,我们也可以使用 --screenshot 参数来获取页面截图:

chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/ # Size of a standard letterhead. chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://www.chromestatus.com/ # Nexus 5x chrome --headless --disable-gpu --screenshot --window-size=412,732 https://www.chromestatus.com/ 

如果我们需要更复杂的截图策略,云南idc服务商譬如进行完整页面截图则需要利用代码进行远程控制。

代码控制

启动

在上文中我们介绍了如何利用命令行来手动启动 Chrome,这里我们尝试使用 Node.js 来启动 Chrome,最简单的方式就是使用 child_process 来启动:

const exec = require(child_process).exec; function launchHeadlessChrome(url, callback) {    // Assuming MacOSx.   const CHROME = /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome;   exec(`${ CHROME} --headless --disable-gpu --remote-debugging-port=9222 ${ url}`, callback); } launchHeadlessChrome(https://www.chromestatus.com, (err, stdout, stderr) => {    ... }); 

远程控制

这里我们使用 chrome-remote-interface 来远程控制 Chrome ,实际上 chrome-remote-interface 是对于 Chrome DevTools Protocol 的远程封装,我们可以参考协议文档了解详细的功能与参数。使用 npm 安装完毕之后,我们可以用如下代码片进行简单控制:

const CDP = require(chrome-remote-interface); CDP((client) => {      // extract domains     const { Network, Page} = client;     // setup handlers     Network.requestWillBeSent((params) => {          console.log(params.request.url);     });     Page.loadEventFired(() => {          client.close();     });     // enable events then start!     Promise.all([         Network.enable(),         Page.enable()     ]).then(() => {          return Page.navigate({ url: https://github.com});     }).catch((err) => {          console.error(err);         client.close();     }); }).on(error, (err) => {      // cannot connect to the remote endpoint     console.error(err); }); 

我们也可以使用 chrome-remote-interface 提供的命令行功能,譬如我们可以在命令行中访问某个界面并且记录所有的网络请求:

$ chrome-remote-interface inspect >>> Network.enable() {  result: { } } >>> Network.requestWillBeSent(params => params.request.url) {  Network.requestWillBeSent: params => params.request.url } >>> Page.navigate({ url: https://www.wikipedia.org}) {  Network.requestWillBeSent: https://www.wikipedia.org/ } {  result: {  frameId: 5530.1 } } {  Network.requestWillBeSent: https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia_wordmark.png } {  Network.requestWillBeSent: https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2.png } {  Network.requestWillBeSent: https://www.wikipedia.org/portal/wikipedia.org/assets/js/index-3b68787aa6.js } {  Network.requestWillBeSent: https://www.wikipedia.org/portal/wikipedia.org/assets/js/gt-ie9-c84bf66d33.js } {  Network.requestWillBeSent: https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-bookshelf_icons.png?16ed124e8ca7c5ce9d463e8f99b2064427366360 } {  Network.requestWillBeSent: https://www.wikipedia.org/portal/wikipedia.org/assets/img/sprite-project-logos.png?9afc01c5efe0a8fb6512c776955e2ad3eb48fbca } 

我们也可以直接查看内置的接口文档:

>>> Page.navigate {  [Function]   category: command,   parameters: {  url: {  type: string, description: URL to navigate the page to. } },   returns:    [ {  name: frameId,        $ref: FrameId,        hidden: true,        description: Frame id that will be navigated. } ],   description: Navigates current page to the given URL.,   handlers: [ browser, renderer ] }>>> Page.navigate {  [Function]   category: command,   parameters: {  url: {  type: string, description: URL to navigate the page to. } },   returns:    [ {  name: frameId,        $ref: FrameId,        hidden: true,        description: Frame id that will be navigated. } ],   description: Navigates current page to the given URL.,   handlers: [ browser, renderer ] } 

我们在上文中还提到需要以代码控制浏览器进行完整页面截图,这里需要利用 Emulation 模块控制页面视口缩放:

const CDP = require(chrome-remote-interface); const argv = require(minimist)(process.argv.slice(2)); const file = require(fs); // CLI Args const url = argv.url || https://www.google.com; const format = argv.format === jpeg ? jpeg : png; const viewportWidth = argv.viewportWidth || 1440; const viewportHeight = argv.viewportHeight || 900; const delay = argv.delay || 0; const userAgent = argv.userAgent; const fullPage = argv.full; // Start the Chrome Debugging Protocol CDP(async function(client) {    // Extract used DevTools domains.   const { DOM, Emulation, Network, Page, Runtime} = client;   // Enable events on domains we are interested in.   await Page.enable();   await DOM.enable();   await Network.enable();   // If user agent override was specified, pass to Network domain   if (userAgent) {      await Network.setUserAgentOverride({ userAgent});   }   // Set up viewport resolution, etc.   const deviceMetrics = {      width: viewportWidth,     height: viewportHeight,     deviceScaleFactor: 0,     mobile: false,     fitWindow: false,   };   await Emulation.setDeviceMetricsOverride(deviceMetrics);   await Emulation.setVisibleSize({ width: viewportWidth, height: viewportHeight});   // Navigate to target page   await Page.navigate({ url});   // Wait for page load event to take screenshot   Page.loadEventFired(async () => {      // If the `full` CLI option was passed, we need to measure the height of     // the rendered page and use Emulation.setVisibleSize     if (fullPage) {        const { root: { nodeId: documentNodeId}} = await DOM.getDocument();       const { nodeId: bodyNodeId} = await DOM.querySelector({          selector: body,         nodeId: documentNodeId,       });       const { model: { height}} = await DOM.getBoxModel({ nodeId: bodyNodeId});       await Emulation.setVisibleSize({ width: viewportWidth, height: height});       // This forceViewport call ensures that content outside the viewport is       // rendered, otherwise it shows up as grey. Possibly a bug?       await Emulation.forceViewport({ x: 0, y: 0, scale: 1});     }     setTimeout(async function() {        const screenshot = await Page.captureScreenshot({ format});       const buffer = new Buffer(screenshot.data, base64);       file.writeFile(output.png, buffer, base64, function(err) {          if (err) {            console.error(err);         } else {            console.log(Screenshot saved);         }         client.close();       });     }, delay);   }); }).on(error, err => {    console.error(Cannot connect to browser:, err); }); 

 【本文是专栏作者“张梓雄 ”的原创文章,如需转载请通过与作者联系】

戳这里,源码库看该作者更多好文

copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap