1. 程式人生 > 其它 >初探 Headless Chrome

初探 Headless Chrome

初探 Headless Chrome

xiguaxigua 專心做技術宅

什麼是 Headless Chrome

Headless Chrome 是 Chrome 瀏覽器的無介面形態,可以在不開啟瀏覽器的前提下,使用所有 Chrome 支援的特性執行你的程式。相比於現代瀏覽器,Headless Chrome 更加方便測試 web 應用,獲得網站的截圖,做爬蟲抓取資訊等。相比於出道較早的 PhantomJS,SlimerJS 等,Headless Chrome 則更加貼近瀏覽器環境。

如何獲取 Headless Chrome

目前,Mac 上

Chrome 59 beta版本與 Linux 上的 Chrome 57+ 已經開始支援 headless 特性。Windows 上 Chrome 暫時不支援,可以使用Chrome Canary 60進行開發。

如何在終端中使用

在Mac上使用前,建議先繫結 Chrome 的別名

alias google-chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
Linux下無需繫結別名,從官網上下載最新版 Chrome 之後直接執行以下命令即可。
然後,在終端中輸入:
google-chrome --headless --disable-gpu --remote-debugging-port=9222  https://github.com
增加 --disable-gpu 主要是為了遮蔽現階段可能觸發的錯誤。
此時,Headless Chrome已經成功運行了。開啟瀏覽器,輸入http://localhost:9222,你會看到如下的介面:

在終端中,我們還可以做以下操作:

獲取螢幕截圖:

google-chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://github.com

獲取頁面為PDF:

google-chrome --headless --disable-gpu --print-to-pdf https://github.com

列印頁面DOM:

google-chrome --headless --disable-gpu --dump-dom https://github.com/

遠端控制

在上文中講述的都使用終端命令啟動 Headless Chrome,下文以獲取截圖為例,嘗試如何在程式裡控制 Headless Chrome。

安裝依賴

npm install lighthouse chrome-remote-interface --save
實現截圖的大體思路為:通過使用 lighthouse 啟動 Headless Chrome,然後通過chrome-remote-interface遠端控制瀏覽器,使用 Page 監控頁面的載入,使用 Emulation 模組調整視口縮放,最終生成一張截圖。
const { ChromeLauncher } = require('lighthouse/lighthouse-cli/chrome-launcher')
const chrome = require('chrome-remote-interface')
const fs = require('fs')
const deviceMetrics = {
  width: 1200,
  height: 800,
  deviceScaleFactor: 0,
  mobile: false,
  fitWindow: false
}
const screenshotMetrics = {
  width: deviceMetrics.width,
  height: deviceMetrics.height
}
let protocol
let launcher

function launchChrome () {
  const launcher = new ChromeLauncher({
    port: 9222,
    autoSelectChrome: true,
    additionalFlags: ['--window-size=412,732', '--disable-gpu', '--headless']
  })
  return launcher.run().then(() => launcher)
}
function getScreenShot () {
  const { Page, Emulation } = protocol
  return Page.enable()
    .then(() => {
      Emulation.setDeviceMetricsOverride(deviceMetrics) // 配置瀏覽器尺寸
      Emulation.setVisibleSize(screenshotMetrics) // 配置截圖尺寸
      Page.navigate({ url: 'https://github.com/' })
      return new Promise((resolve, reject) => {
        Page.loadEventFired(() => {
          resolve(Page.captureScreenshot({ format: 'jpeg', fromSurface: true }))
        })
      })
    })
    .then(image => {
      const buffer = new Buffer(image.data, 'base64')
      return new Promise((resolve, reject) => {
        fs.writeFile('output.jpeg', buffer, 'base64', err => {
          if (err) return reject(err)
          resolve()
        })
      })
    })
}
launchChrome()
  .then(Launcher => {
    launcher = Launcher
    return new Promise((resolve, reject) =>{
      chrome(Protocol => {
        protocol = Protocol
        resolve()
      }).on('error', err => { reject(err) })
    })
  })
  .then(getScreenShot)
  .then(() => {
    protocol.close()
    launcher.kill()
  })
  .catch(console.error)
這裡使用 lighthouse 提供的 ChromeLauncher 模組來呼叫 Chrome,如果電腦上安裝了Chrome Canary,lighthouse 預設會啟動 Chrome Canary,可以將 autoSelectChrome 設定為false 然後自行選擇使用什麼版本。

通過 chrome-remote-interface 配合 Headless Chrome,我們還可以做更多事情。

使用 CSS 和 DOM 模組,可以獲取和設定頁面中的 DOM 節點內容和 CSS 樣式。

function getStyle () {
  const { Page, CSS, DOM } = protocol
  return Promise.all([
      DOM.enable(),
      CSS.enable(),
      Page.enable()
    ])
    .then(() => {
      Page.navigate({ url: 'https://github.com/' })
      return new Promise((resolve, _) => {
        Page.loadEventFired(() => { resolve(DOM.getDocument()) })
      })
    })
    .then(res => res.root.nodeId)
    .then(nodeId => DOM.querySelector({ selector: '.btn-primary', nodeId }))
    .then(({ nodeId }) => CSS.getComputedStyleForNode({ nodeId }))
    .then(style => { console.log(style) })
}

使用 Runtime 模組,可以在頁面執行時執行 JS 指令碼。

function search () {
  const { Page, Runtime } = protocol
  return Promise.all([
      Page.enable()
    ])
    .then(() => {
      Page.navigate({ url: 'https://www.baidu.com/' })
      return new Promise((resolve, _) => {
        Page.loadEventFired(() => { resolve() })
      })
    })
    .then(() => {
      const code = [
        'var input = document.querySelector(\'.s_ipt\')',
        'var btn = document.querySelector(\'#su\')',
        'input.value=\'123\''
      ].join(';')
      return Runtime.evaluate({ expression: code })
    })
    .then(() => {
      return new Promise((resolve, _) => {
        setTimeout(() => {
          resolve(Page.captureScreenshot({ format: 'jpeg', fromSurface: true }))
        }, 3000)
      })
    })
    .then(image => {
      const buffer = new Buffer(image.data, 'base64')
      return new Promise((resolve, reject) => {
        fs.writeFile('output.jpeg', buffer, 'base64', err => {
          if (err) return reject(err)
          resolve()
        })
      })
    })
}

使用 Network 模組,可以讀取並設定 UserAgent 和 Cookie 等資訊。

function setUAandCookie () {
  const { Page, Network } = protocol
  return Promise.all([
      Network.enable(),
      Page.enable()
    ])
    .then(() => {
      const userAgent = 
      Network.setUserAgentOverride({ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.71 Safari/537.36" })
      Network.setCookie({
        url: 'https://github.com',
        name: 'test',
        value: '123',
        domain: '.github.com',
        path: '/',
        httpOnly: true
      })
      Page.navigate({ url: 'https://github.com/' })
      return new Promise((resolve, _) => {
        Page.loadEventFired(() => { resolve() })
      })
    })
    .then(() => {
      return Network.getCookies()
    })
    .then(console.log)
}

在 Karma 中使用 Headless Chrome 進行單元測試

相比於 PhantomJS 等,使用 Headless Chrome 做單元測試更加貼近瀏覽器開發環境。同時 PhantomJS 作者也已經功成身退,在 Chrome 釋出 Headless 模式後,釋出通知不再維護 PhantomJS 專案。

安裝依賴

npm install jasmine-core karma karma-chrome-launcher karma-jasmine -D
配置 Karma
// karma.conf.js
module.exports = function (config) {
  config.set({
    frameworks: ['jasmine'],
    files: ['./test.js'],
    browsers: ["Chrome_Beta_Headless"],
    customLaunchers: {
      Chrome_Beta_Headless: {
        base: 'Chrome',
        flags: [
          '--headless',
          '--disable-gpu',
          '--remote-debugging-port=9222'
        ]
      }
    },
    browserConsoleLogOptions: {
      level: 'log',
      terminal: true
    },
    reporters: ['progress'],
    autoWatch: false,
    singleRun: true
  })
}
編寫測試用例
// test.js
describe('test', function() {
  it('should be true', function() {
    console.log(window.navigator.userAgent)
    expect(true).toEqual(true);
  });
});

配置npm script

// package.json
...
scripts: {
  test: "karma start"
}
...
在終端中執行
npm test
結果

從返回結果中可以看出,測試已執行在 Headless Chrome 環境下。

小結

本文簡單介紹了一下 Headless Chrome 在終端的用法,以及如何使用 Headless Chrome 獲取截圖、獲取頁面中的CSS和DOM、設定UA和Cookie、執行JS指令碼、配合 Karma 進行單元測試。接下來,就等著你探索更多關於 Headless Chrome 的用法了...

參考:


How to install and use Headless Chrome on OSX 漫思