什麼是 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 的別名
1 |
alias google-chrome="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" |
Linux下無需繫結別名,從官網上下載最新版 Chrome 之後直接執行以下命令即可。
然後,在終端中輸入:
1 |
google-chrome --headless --disable-gpu --remote-debugging-port=9222 https://github.com |
增加 –disable-gpu 主要是為了遮蔽現階段可能觸發的錯誤。
此時,Headless Chrome已經成功執行了。開啟瀏覽器,輸入 http://localhost:9222,你會看到如下的介面:
在終端中,我們還可以做以下操作:
獲取螢幕截圖:
1 |
google-chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://github.com |
獲取頁面為PDF:
1 |
google-chrome --headless --disable-gpu --print-to-pdf https://github.com |
列印頁面DOM:
1 |
google-chrome --headless --disable-gpu --dump-dom https://github.com/ |
遠端控制
在上文中講述的都使用終端命令啟動 Headless Chrome,下文以獲取截圖為例,嘗試如何在程式裡控制 Headless Chrome。
安裝依賴
1 |
npm install lighthouse chrome-remote-interface --save |
實現截圖的大體思路為:通過使用 lighthouse 啟動 Headless Chrome,然後通過 chrome-remote-interface 遠端控制瀏覽器,使用 Page 監控頁面的載入,使用 Emulation 模組調整視口縮放,最終生成一張截圖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
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 樣式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 指令碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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 等資訊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 專案。
安裝依賴
1 |
npm install jasmine-core karma karma-chrome-launcher karma-jasmine -D |
配置 Karma
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// 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 }) } |
編寫測試用例
1 2 3 4 5 6 7 |
// test.js describe('test', function() { it('should be true', function() { console.log(window.navigator.userAgent) expect(true).toEqual(true); }); }); |
配置npm script
1 2 3 4 5 6 |
// package.json ... scripts: { test: "karma start" } ... |
在終端中執行
1 |
npm test |
結果
從返回結果中可以看出,測試已執行在 Headless Chrome 環境下。
小結
本文簡單介紹了一下 Headless Chrome 在終端的用法,以及如何使用 Headless Chrome 獲取截圖、獲取頁面中的CSS和DOM、設定UA和Cookie、執行JS指令碼、配合 Karma 進行單元測試。接下來,就等著你探索更多關於 Headless Chrome 的用法了…
參考:
https://developers.google.com/web/updates/2017/04/headless-chrome
How to install and use Headless Chrome on OSX