原文地址:Getting Started with Headless Chrome By Eric Bidelman Engineer @ Google working on web tooling: Headless Chrome, Puppeteer, Lighthousenode
Headless Chrome在Chrome59中發佈,用於在headless環境中運行Chrome瀏覽器,也就是在非Chrome環境中運行Chrome。它將Chromium和Blink渲染引擎提供的全部現代Web平臺功能引入命令行。git
它有什麼用處呢?github
headless瀏覽器是自動測試和服務器環境的絕佳工具,您不須要可見的UI shell。例如,針對真實的網頁進行測試,建立網頁的PDF,或者只是檢查瀏覽器如何呈現URL。web
最簡單的開始使用headless模式的方法是從命令行打開Chrome。若是你已經安裝了Chrome59+的版本,可使用 --headless 標籤:chrome
chrome \ --headless \ # 在headless模式運行Chrome --disable-gpu \ # 在Windows上運行時須要--remote-debugging-port=9222 \ https://www.chromestatus.com # 打開URL. 默認爲about:blank
注意:若在Windows中運行,則須要在命令行添加 --disable-gpu 。shell
chrome 命令須要指向Chrome的安裝路徑。(即在Chrome的安裝路徑下運行)express
在某些狀況下,您可能不須要以編程方式編寫Headless Chrome腳本。下面是一些有用的命令行標誌來執行常見任務。npm
將 document.body.innerHTML 在stdout打印出來:編程
chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/
chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/
演示:在chrome安裝目錄下運行 chrome --headless --disable-gpu --print-to-pdf https://www.baidu.com/ api
生成PDF文件:C:\Program Files (x86)\Google\Chrome\Application\69.0.3497.81\output.pdf
chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/ # 標準屏幕大小 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/
運行 --screenshot將會在當前運行目錄下生成一個 screenshot.png 文件。若想給整個頁面的截圖,那麼會比較複雜。來自 David Schnurr 的一篇很棒的博文介紹了這一內容。請查看 使用 headless Chrome 做爲自動截屏工具。
在REPL模式運行Headless,該模式容許經過命令行在瀏覽器中評估JS表達式:
$ chrome --headless --disable-gpu --repl --crash-dumps-dir=./tmp https://www.chromestatus.com/ [0608/112805.245285:INFO:headless_shell.cc(278)] Type a Javascript expression to evaluate or "quit" to exit. >>> location.href {"result":{"type":"string","value":"https://www.chromestatus.com/features"}} >>> quit $
注意:使用repl模式時須要添加 --crash-dumps-dir 命令。
當使用 --remote-debugging-port=9222 運行Chrome時,會啓用DevTools協議的實例。該協議用於與Chrome通訊而且驅動headless瀏覽器實例。除此以外,它仍是一個相似於 Sublime, VS Code, 和Node的工具,可用於遠程調試一個應用。
因爲沒有瀏覽器UI來查看頁面,所以須要在另外一個瀏覽器中導航到http:// localhost:9222以檢查一切是否正常。這將看到一個可查看頁面的列表,能夠在其中單擊並查看Headless正在呈現的內容:
DevTools遠程調試界面
在這裏,你可使用熟悉的DecTools功能來查看、調試、修改頁面。若以編程方式(programmatically)使用Headless,該頁面的功能更強大,能夠用於查看全部的DecTools協議的命令,並與瀏覽器進行通訊。
Puppeteer 由Chrome團隊開發的Node庫。它提供了控制headless Chrome的高階API。相似於 Phantom 和 NightmareJS這樣的自動測試庫,但它只用於最新版本的Chrome。
除此以外,Puppeteer還可用於截屏,建立PDF,頁面導航,以及獲取有關這些頁面的信息。若是須要快速進行瀏覽器的自動化測試,建議使用該庫。它隱藏了DevTools協議的複雜性,並負責啓動Chrome的調試實例等冗餘任務。
npm i --save puppeteer
const puppeteer = require('puppeteer'); (async() => { const browser = await puppeteer.launch(); console.log(await browser.version()); await browser.close(); })();
const puppeteer = require('puppeteer'); (async() => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle2'}); await page.pdf({path: 'page.pdf', format: 'A4'}); await browser.close(); })();
查看 Puppeteer's 文檔 學習Puppeteer的更多用法。
相對於Puppeteer's API來講,chrome-remote-interface 是一個低階的庫,推薦使用它更接近底層地直接使用DevTools協議。
chrome-remote-interface不能打開Chrome,所以須要本身打開Chrome。
在CLI部分,咱們使用--headless --remote-debugging-port = 9222手動打開Chrome。可是,要實現徹底自動化測試,您可能但願從應用程序中生成Chrome。
使用 child——process 的一種方式:
const execFile = require('child_process').execFile; function launchHeadlessChrome(url, callback) { // Assuming MacOSx. const CHROME = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'; execFile(CHROME, ['--headless', '--disable-gpu', '--remote-debugging-port=9222', url], callback); } launchHeadlessChrome('https://www.chromestatus.com', (err, stdout, stderr) => { ... });
可是若是你想要一個適用於多個平臺的可移植解決方案,那麼事情會變得棘手。看看Chrome的硬編碼路徑吧:(
Lighthouse 是測試web應用質量絕佳工具。用於啓動Chrome的強大的模塊就是在Lighthouse中開發的,如今能夠單獨使用。 chrome-launcher NPM module 能夠找到Chrome的安裝路徑,設置調試實例,打開瀏覽器,而且當程序運行完成時關掉它。最棒的是,因爲Node,它能夠跨平臺工做!
默認狀況下,chrome-launcher會嘗試啓動Chrome Canary(若是已安裝),但能夠更改它以手動選擇要使用的Chrome。要使用它,首先從npm安裝:
npm i --save chrome-launcher
const chromeLauncher = require('chrome-launcher'); // 可選: 設置launcher的日誌記錄級別以查看其輸出 // 安裝:: npm i --save lighthouse-logger // const log = require('lighthouse-logger'); // log.setLevel('info'); /** * 啓動Chrome的調試實例 * @param {boolean=} headless True (default) 啓動headless模式的Chrome. * False 啓動Chrome的完成版本. * @return {Promise<ChromeLauncher>} */ function launchChrome(headless=true) { return chromeLauncher.launch({ // port: 9222, // Uncomment to force a specific port of your choice. chromeFlags: [ '--window-size=412,732', '--disable-gpu', headless ? '--headless' : '' ] }); } launchChrome().then(chrome => { console.log(`Chrome debuggable on port: ${chrome.port}`); ... // chrome.kill(); });
運行此腳本並無太大做用,但在任務管理器中應該能夠看到Chrome實例已啓動,內容爲 about:blank 。可是沒有瀏覽器界面。由於是headless模式。
要控制瀏覽器,咱們須要DevTools協議!
npm i --save chrome-remote-interface
const CDP = require('chrome-remote-interface'); ... launchChrome().then(async chrome => { const version = await CDP.Version({port: chrome.port}); console.log(version['User-Agent']); });
結果相似於: HeadlessChrome/60.0.3082.0
const CDP = require('chrome-remote-interface'); ... (async function() { const chrome = await launchChrome(); const protocol = await CDP({port: chrome.port}); // Extract the DevTools protocol domains we need and enable them. // See API docs: https://chromedevtools.github.io/devtools-protocol/ const {Page} = protocol; await Page.enable(); Page.navigate({url: 'https://www.chromestatus.com/'}); // Wait for window.onload before doing stuff. Page.loadEventFired(async () => { const manifest = await Page.getAppManifest(); if (manifest.url) { console.log('Manifest: ' + manifest.url); console.log(manifest.data); } else { console.log('Site has no app manifest'); } protocol.close(); chrome.kill(); // Kill Chrome. }); })();
const CDP = require('chrome-remote-interface'); ... (async function() { const chrome = await launchChrome(); const protocol = await CDP({port: chrome.port}); // Extract the DevTools protocol domains we need and enable them. // See API docs: https://chromedevtools.github.io/devtools-protocol/ const {Page, Runtime} = protocol; await Promise.all([Page.enable(), Runtime.enable()]); Page.navigate({url: 'https://www.chromestatus.com/'}); // Wait for window.onload before doing stuff. Page.loadEventFired(async () => { const js = "document.querySelector('title').textContent"; // Evaluate the JS expression in the page. const result = await Runtime.evaluate({expression: js}); console.log('Title of page: ' + result.result.value); protocol.close(); chrome.kill(); // Kill Chrome. }); })();
如今,Selenium打開了一個完整地Chrome的實例,也就是說,換句話說,它是一種自動化解決方案,但並不是徹底headless。可是,Selenium能夠經過一些配置來運行headless Chrome。我建議使用headless Chrome運行Selenium,若你仍是想要如何本身設置的完整說明,我已經在下面的一些例子中展現瞭如何讓你放棄。
ChromeDriver 2.32使用了Chrome61,而且在headless Chrome運行的更好。
npm i --save-dev selenium-webdriver chromedriver
const fs = require('fs'); const webdriver = require('selenium-webdriver'); const chromedriver = require('chromedriver'); const chromeCapabilities = webdriver.Capabilities.chrome(); chromeCapabilities.set('chromeOptions', {args: ['--headless']}); const driver = new webdriver.Builder() .forBrowser('chrome') .withCapabilities(chromeCapabilities) .build(); // Navigate to google.com, enter a search. driver.get('https://www.google.com/'); driver.findElement({name: 'q'}).sendKeys('webdriver'); driver.findElement({name: 'btnG'}).click(); driver.wait(webdriver.until.titleIs('webdriver - Google Search'), 1000); // Take screenshot of results page. Save to disk. driver.takeScreenshot().then(base64png => { fs.writeFileSync('screenshot.png', new Buffer(base64png, 'base64')); }); driver.quit();
WebDriverIO 是Selenium WebDriver之上的更高階的API。
npm i --save-dev webdriverio chromedriver
const webdriverio = require('webdriverio'); const chromedriver = require('chromedriver'); const PORT = 9515; chromedriver.start([ '--url-base=wd/hub', `--port=${PORT}`, '--verbose' ]); (async () => { const opts = { port: PORT, desiredCapabilities: { browserName: 'chrome', chromeOptions: {args: ['--headless']} } }; const browser = webdriverio.remote(opts).init(); await browser.url('https://www.chromestatus.com/features'); const title = await browser.getTitle(); console.log(`Title: ${title}`); await browser.waitForText('.num-features', 3000); let numFeatures = await browser.getText('.num-features'); console.log(`Chrome has ${numFeatures} total features`); await browser.setValue('input[type="search"]', 'CSS'); console.log('Filtering features...'); await browser.pause(1000); numFeatures = await browser.getText('.num-features'); console.log(`Chrome has ${numFeatures} CSS features`); const buffer = await browser.saveScreenshot('screenshot.png'); console.log('Saved screenshot...'); chromedriver.stop(); browser.end(); })();
如下是一些有用的資源,可幫助您入門:
僅Windows平臺須要。其餘平臺不須要。--disable-gpu命令是一個臨時解決一些錯誤的方案。在未來的Chrome版本中,再也不須要此命令。有關更多信息,請參閱 crbug.com/737678。
不須要。Headless Chrome不使用窗口,所以再也不須要像Xvfb這樣的顯示服務器。沒有它,也能夠愉快地運行自動化測試。
什麼是Xvfb?Xvfb是一種用於類Unix系統的內存顯示服務器,它使您可以運行圖形應用程序(如Chrome)而無需附加物理顯示設備。許多人使用Xvfb運行早期版本的Chrome進行「headless」測試。
看看lighthouse-ci。它有一個示例 Dockerfile ,它使用node:8-slim做爲基本映像,在App Engine Flex上安裝+ 運行Lighthouse 。
Headless Chrome與PhantomJS等工具相似。二者均可用於headless環境中的自動化測試。二者之間的主要區別在於Phantom使用較舊版本的WebKit做爲其渲染引擎,而Headless Chrome使用最新版本的Blink。
目前,Phantom還提供了比DevTools 協議更高級別的API。
對於Headless Chrome的bugs,請在crbug.com上提交。
對於DevTools協議中的錯誤,請將它們發送到github.com/ChromeDevTools/devtools-protocol。