原文地址:Getting Started with Headless Chrome By Eric Bidelman Engineer @ Google working on web tooling: Headless Chrome, Puppeteer, Lighthouse
Headless Chrome在Chrome59中發(fā)布,用于在headless環(huán)境中運(yùn)行Chrome瀏覽器劫侧,也就是在非Chrome環(huán)境中運(yùn)行Chrome。它將Chromium和Blink渲染引擎提供的所有現(xiàn)代Web平臺(tái)功能引入命令行。
它有什么用處呢叨橱?
headless瀏覽器是自動(dòng)測(cè)試和服務(wù)器環(huán)境的絕佳工具呢铆,您不需要可見(jiàn)的UI shell朗恳。例如,針對(duì)真實(shí)的網(wǎng)頁(yè)進(jìn)行測(cè)試叨襟,創(chuàng)建網(wǎng)頁(yè)的PDF,或者只是檢查瀏覽器如何呈現(xiàn)URL幔荒。
0. 開(kāi)始
最簡(jiǎn)單的開(kāi)始使用headless模式的方法是從命令行打開(kāi)Chrome糊闽。如果你已經(jīng)安裝了Chrome59+的版本,可以使用 --headless 標(biāo)簽:
chrome \
--headless \ # 在headless模式運(yùn)行Chrome
--disable-gpu \ # 在Windows上運(yùn)行時(shí)需要--remote-debugging-port=9222 \
https://www.chromestatus.com # 打開(kāi)URL. 默認(rèn)為about:blank
注意:若在Windows中運(yùn)行爹梁,則需要在命令行添加 --disable-gpu 右犹。
chrome 命令需要指向Chrome的安裝路徑。(即在Chrome的安裝路徑下運(yùn)行)
1. 命令行功能
在某些情況下姚垃,您可能不需要以編程方式編寫(xiě)Headless Chrome腳本傀履。下面是一些有用的命令行標(biāo)志來(lái)執(zhí)行常見(jiàn)任務(wù)。
1.1 打印DOM --dump-dom
將 document.body.innerHTML 在stdout打印出來(lái):
chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/
1.2 創(chuàng)建PDF --print-to-pdf :
chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/
演示:在chrome安裝目錄下運(yùn)行 chrome --headless --disable-gpu --print-to-pdf https://www.baidu.com/
生成PDF文件:C:\Program Files (x86)\Google\Chrome\Application\69.0.3497.81\output.pdf
1.3 截屏 --screenshot
chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/
# 標(biāo)準(zhǔn)屏幕大小
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/
運(yùn)行 --screenshot將會(huì)在當(dāng)前運(yùn)行目錄下生成一個(gè) screenshot.png 文件莉炉。若想給整個(gè)頁(yè)面的截圖钓账,那么會(huì)比較復(fù)雜。來(lái)自 David Schnurr 的一篇很棒的博文介紹了這一內(nèi)容絮宁。請(qǐng)查看 使用 headless Chrome 作為自動(dòng)截屏工具梆暮。
1.4 REPL模式(read-eval-print loop) --repl
在REPL模式運(yùn)行Headless,該模式允許通過(guò)命令行在瀏覽器中評(píng)估JS表達(dá)式:
$ 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模式時(shí)需要添加 --crash-dumps-dir 命令绍昂。
2. 在沒(méi)有瀏覽器界面情況下調(diào)試Chrome
當(dāng)使用 --remote-debugging-port=9222 運(yùn)行Chrome時(shí)啦粹,會(huì)啟用DevTools協(xié)議的實(shí)例偿荷。該協(xié)議用于與Chrome通信并且驅(qū)動(dòng)headless瀏覽器實(shí)例。除此之外唠椭,它還是一個(gè)類(lèi)似于 Sublime, VS Code, 和Node的工具跳纳,可用于遠(yuǎn)程調(diào)試一個(gè)應(yīng)用。
由于沒(méi)有瀏覽器UI來(lái)查看頁(yè)面贪嫂,因此需要在另一個(gè)瀏覽器中導(dǎo)航到http:// localhost:9222以檢查一切是否正常寺庄。這將看到一個(gè)可查看頁(yè)面的列表,可以在其中單擊并查看Headless正在呈現(xiàn)的內(nèi)容:
在這里力崇,你可以使用熟悉的DecTools功能來(lái)查看斗塘、調(diào)試、修改頁(yè)面亮靴。若以編程方式(programmatically)使用Headless馍盟,該頁(yè)面的功能更強(qiáng)大,可以用于查看所有的DecTools協(xié)議的命令茧吊,并與瀏覽器進(jìn)行通信贞岭。
3. 使用編程模式(Node)
3.1 Puppeteer
Puppeteer 由Chrome團(tuán)隊(duì)開(kāi)發(fā)的Node庫(kù)。它提供了控制headless Chrome的高階API搓侄。類(lèi)似于 Phantom 和 NightmareJS這樣的自動(dòng)測(cè)試庫(kù)瞄桨,但它只用于最新版本的Chrome。
除此之外休讳,Puppeteer還可用于截屏讲婚,創(chuàng)建PDF,頁(yè)面導(dǎo)航俊柔,以及獲取有關(guān)這些頁(yè)面的信息筹麸。如果需要快速進(jìn)行瀏覽器的自動(dòng)化測(cè)試,建議使用該庫(kù)雏婶。它隱藏了DevTools協(xié)議的復(fù)雜性物赶,并負(fù)責(zé)啟動(dòng)Chrome的調(diào)試實(shí)例等冗余任務(wù)。
安裝:
npm i --save puppeteer
例子-打印用戶(hù)代理信息:
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 文檔 學(xué)習(xí)Puppeteer的更多用法留晚。
3.2 CRI庫(kù)
相對(duì)于Puppeteer's API來(lái)說(shuō)酵紫,chrome-remote-interface 是一個(gè)低階的庫(kù),推薦使用它更接近底層地直接使用DevTools協(xié)議错维。
打開(kāi)Chrome
chrome-remote-interface不能打開(kāi)Chrome奖地,因此需要自己打開(kāi)Chrome。
在CLI部分赋焕,我們使用--headless --remote-debugging-port = 9222手動(dòng)打開(kāi)Chrome参歹。但是,要實(shí)現(xiàn)完全自動(dòng)化測(cè)試隆判,您可能希望從應(yīng)用程序中生成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) => {
...
});
但是如果你想要一個(gè)適用于多個(gè)平臺(tái)的可移植解決方案僧界,那么事情會(huì)變得棘手〕敉欤看看Chrome的硬編碼路徑吧:(
使用ChromeLaucher
Lighthouse 是測(cè)試web應(yīng)用質(zhì)量絕佳工具捂襟。用于啟動(dòng)Chrome的強(qiáng)大的模塊就是在Lighthouse中開(kāi)發(fā)的,現(xiàn)在可以單獨(dú)使用欢峰。 chrome-launcher NPM module 可以找到Chrome的安裝路徑葬荷,設(shè)置調(diào)試實(shí)例,打開(kāi)瀏覽器赤赊,并且當(dāng)程序運(yùn)行完成時(shí)關(guān)掉它闯狱。最棒的是煞赢,由于Node抛计,它可以跨平臺(tái)工作!
默認(rèn)情況下照筑,chrome-launcher會(huì)嘗試啟動(dòng)Chrome Canary(如果已安裝)吹截,但可以更改它以手動(dòng)選擇要使用的Chrome。要使用它凝危,首先從npm安裝:
npm i --save chrome-launcher
例子-使用 chrome-launcher 啟動(dòng)Headless模式
const chromeLauncher = require('chrome-launcher'); // 可選: 設(shè)置launcher的日志記錄級(jí)別以查看其輸出 // 安裝:: npm i --save lighthouse-logger // const log = require('lighthouse-logger'); // log.setLevel('info');
/**
* 啟動(dòng)Chrome的調(diào)試實(shí)例
* @param {boolean=} headless True (default) 啟動(dòng)headless模式的Chrome.
* False 啟動(dòng)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();
});
運(yùn)行此腳本并沒(méi)有太大作用波俄,但在任務(wù)管理器中應(yīng)該可以看到Chrome實(shí)例已啟動(dòng),內(nèi)容為 about:blank 蛾默。但是沒(méi)有瀏覽器界面懦铺。因?yàn)槭莌eadless模式。
要控制瀏覽器支鸡,我們需要DevTools協(xié)議冬念!
檢索有關(guān)頁(yè)面的信息
安裝:
npm i --save chrome-remote-interface
例子-打印用戶(hù)代理
const CDP = require('chrome-remote-interface');
...
launchChrome().then(async chrome => { const version = await CDP.Version({port: chrome.port});
console.log(version['User-Agent']);
});
結(jié)果類(lèi)似于: HeadlessChrome/60.0.3082.0
例子-檢查網(wǎng)站是否有應(yīng)用列表
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.
});
})();
例子-使用DOM API提取頁(yè)面的<title>
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.
});
})();
4. 使用Selenium,W??ebDriver和ChromeDriver
現(xiàn)在牧挣,Selenium打開(kāi)了一個(gè)完整地Chrome的實(shí)例急前,也就是說(shuō),換句話說(shuō)瀑构,它是一種自動(dòng)化解決方案裆针,但并非完全headless。但是寺晌,Selenium可以通過(guò)一些配置來(lái)運(yùn)行headless Chrome世吨。我建議使用headless Chrome運(yùn)行Selenium,若你還是想要如何自己設(shè)置的完整說(shuō)明呻征,我已經(jīng)在下面的一些例子中展示了如何讓你放棄耘婚。
使用ChromeDriver
ChromeDriver 2.32使用了Chrome61,并且在headless Chrome運(yùn)行的更好怕犁。
安裝:
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
WebDriverIO 是Selenium WebDriver之上的更高階的API边篮。
安裝:
npm i --save-dev webdriverio chromedriver
例子-chromestatus.com上的CSS filter功能
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();
})();
5. 更多資源
以下是一些有用的資源己莺,可幫助您入門(mén):
文檔:
- DevTools Protocol Viewer - API參考文檔
工具:
- chrome-remote-interface - 包裝DevTools協(xié)議的節(jié)點(diǎn)模塊
- Lighthouse - 用于測(cè)試Web應(yīng)用質(zhì)量的自動(dòng)化工具;大量使用協(xié)議
- chrome-launcher - 節(jié)點(diǎn)模塊戈轿,用于啟動(dòng)Chrome凌受,為自動(dòng)化做好準(zhǔn)備
演示:
- "The Headless Web" - Paul Kinlan關(guān)于使用Headless和api.ai的博客文章
6. FAQ
6.1 是否需要 --disable-gpu 命令?
僅Windows平臺(tái)需要思杯。其他平臺(tái)不需要胜蛉。--disable-gpu命令是一個(gè)臨時(shí)解決一些錯(cuò)誤的方案。在將來(lái)的Chrome版本中色乾,不再需要此命令誊册。有關(guān)更多信息,請(qǐng)參閱 crbug.com/737678暖璧。
6.2 是否需要 Xvfb案怯?
不需要。Headless Chrome不使用窗口澎办,因此不再需要像Xvfb這樣的顯示服務(wù)器嘲碱。沒(méi)有它,也可以愉快地運(yùn)行自動(dòng)化測(cè)試局蚀。
什么是Xvfb麦锯?Xvfb是一種用于類(lèi)Unix系統(tǒng)的內(nèi)存顯示服務(wù)器,它使您能夠運(yùn)行圖形應(yīng)用程序(如Chrome)而無(wú)需附加物理顯示設(shè)備琅绅。許多人使用Xvfb運(yùn)行早期版本的Chrome進(jìn)行“headless”測(cè)試扶欣。
6.3 如何創(chuàng)建運(yùn)行Headless Chrome的Docker容器?
看看lighthouse-ci千扶。它有一個(gè)示例 Dockerfile 料祠,它使用node:8-slim作為基本映像,在App Engine Flex上安裝+ 運(yùn)行Lighthouse 县貌。
6.4 Headless Chrome與PhantomJS有什么關(guān)系术陶?
Headless Chrome與PhantomJS等工具類(lèi)似。兩者都可用于headless環(huán)境中的自動(dòng)化測(cè)試煤痕。兩者之間的主要區(qū)別在于Phantom使用較舊版本的WebKit作為其渲染引擎梧宫,而Headless Chrome使用最新版本的Blink。
目前摆碉,Phantom還提供了比DevTools 協(xié)議更高級(jí)別的API塘匣。
6.5 在哪里提交bugs?
對(duì)于Headless Chrome的bugs巷帝,請(qǐng)?jiān)?a target="_blank" rel="nofollow">crbug.com上提交忌卤。
對(duì)于DevTools協(xié)議中的錯(cuò)誤,請(qǐng)將它們發(fā)送到github.com/ChromeDevTools/devtools-protocol楞泼。