20 KiB
Headless Chrome 入门
摘要
Headless Chrome 在 Chrome 59 中开始搭载。这是一种在 headless 环境下运行 Chrome 浏览器的方式。从本质上来说,就是不用 chrome 来运行 Chrome!它将 Chromium 和 Blink 渲染引擎提供的所有现代 Web 平台的功能都带入了命令行。
它为什么有用?
Headless 浏览器对于自动化测试和不需要可视化 UI 界面的服务器环境是一个很好的工具。例如,你可能需要对真实的网页运行一些测试,创建一个 PDF,或者只是检查浏览器如何呈现 URL。
开启 Headless 模式(命令行界面)
开启 headless 模式最简单的方法是从命令行打开 Chrome 二进制文件。如果你已经安装了 Chrome 59 以上的版本,请使用 --headless
标志启动 Chrome:
chrome \
--headless \ # Runs Chrome in headless mode.
--disable-gpu \ # Temporarily needed for now.
--remote-debugging-port=9222 \
https://www.chromestatus.com # URL to open. Defaults to about:blank.
chrome
应该指向你安装 Chrome 的位置。确切的位置会因平台差异而不同。当前我在 Mac 上操作,所以我为安装的每个版本的 Chrome 都创建了方便使用的别名。
如果您使用 Chrome 的稳定版,并且无法获得测试版,我建议您使用 chrome-canary
:
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"
在这里下载 Chrome Cannary。
命令行的功能
在某些情况下,你可能不需要以编程方式在 Headless Chrome 中执行脚本。可以使用一些有用的命令行标志来执行常见的任务。
打印 DOM
--dump-dom
标志将打印 document.body.innerHTML
到标准输出:
chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/
创建一个 PDF
--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/
使用 --screenshot
标志运行将在当前工作目录中生成一个名为 screenshot.png
的文件。如果你正在寻求整个页面的截图,那么会涉及到很多事情。来自 David Schnurr 的一篇博文已经介绍了这一内容。请查看 Using headless Chrome as an automated screenshot tool 。
REPL 模式 (read-eval-print loop)
--repl
标志可以使 Headless 运行在一个你可以使用浏览器评估 JS 表达式的模式下。执行下面的命令:
$ chrome --headless --disable-gpu --repl 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
$
在没有浏览器界面的情况下调试 Chrome
当你使用 --remote-debugging-port=9222
运行 Chrome 时,它会启动一个开启 DevTools 协议的实例。该协议用于与 Chrome 进行通信,并且驱动 headless 浏览器实例。它也是一个类似 Sublime、VS Code 和 Node 的工具,可用于应用程序的远程调试。#协同效应
由于你没有使用浏览器用户界面来查看网页,请在另一个浏览器中输入 http://localhost:9222
,以检查一切是否正常。你将会看到一个可检查页面的列表,可以点击它们来查看 Headless 正在呈现的内容:
从这里,你就可以像往常一样使用熟悉的 DevTools 来检查、调试和调整页面了。如果你以编程方式使用 Headless,这个页面也是一个功能强大的调试工具,用于查看所有穿过电线,与浏览器交互的原始 DevTools 协议命令。
使用编程模式 (Node)
Puppeteer 库 API
Puppeteer 是一个由 Chrome 团队开发的 Node 库。它提供了一个高层次的 API 来控制 headless(或 full) Chrome。它与其他自动化测试库,如 Phantom 和 NightmareJS 相类似,但是只适用于最新版本的 Chrome。
除此之外,Puppeteer 还可用于轻松截取屏幕截图,创建 PDF,导航页面以及获取有关这些页面的信息。如果你想快速地自动化测试浏览器,我建议使用该库。它隐藏了 DevTools 协议的复杂性,并可以处理诸如启动 Chrome 调试实例等冗余的任务。
安装:
yarn add puppeteer
例子 - 打印用户代理
const puppeteer = require('puppeteer');
(async() => {
const browser = await puppeteer.launch();
console.log(await browser.version());
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: 'networkidle'});
await page.pdf({path: 'page.pdf', format: 'A4'});
browser.close();
})();
查看 Puppeteer 的文档,了解完整 API 的更多信息。
CRI 库
chrome-remote-interface 是一个比 Puppeteer API 更低层次的库。如果你想要更接近原始信息和更直接地使用 DevTools 协议。
启动 Chrome
chrome-remote-interface 不会为你启动 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 的硬编码路径:
使用 ChromeLauncher
Lighthouse 是一个奇妙的网络应用质量的测试工具。Lighthouse 内部开发了一个强大的用于启动 Chrome 的模块,现在已经被提取出来,可以单独使用。chrome-launcher
NPM 模块 可以找到 Chrome 的安装位置,设置调试实例,启动浏览器和在程序运行完之后将其杀死。它最好的一点是可以跨平台工作,感谢 Node!
默认情况下,chrome-launcher
会尝试启动 Chrome Canary(如果已经安装),但是你也可以更改它,手动选择使用的 Chrome 版本。要想使用它,首先从 npm 安装:
yarn add chrome-launcher
例子 - 使用 chrome-launcher
启动 Headless
const chromeLauncher = require('chrome-launcher');
// Optional: set logging level of launcher to see its output.
// Install it using: yarn add lighthouse-logger
// const log = require('lighthouse-logger');
// log.setLevel('info');
/**
* Launches a debugging instance of Chrome.
* @param {boolean=} headless True (default) launches Chrome in headless mode.
* False launches a full version of 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 协议!
检索有关页面的信息
我们来安装该库:
yarn add 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
例子 - 检查网站是否有 Web 应用程序清单
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 提取页面 <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.
});
})();
使用 Selenium、WebDriver 和 ChromeDriver
现在,Selenium 开启了 Chrome 的完整实例。换句话说,这是一个自动化的解决方案,但不是完全 headless 的。但是,Selenium 只需要进行小小的配置即可运行 headless Chrome。如果你想要关于如何自己设置的完整说明,我建议你使用 Headless Chrome 来运行 Selenium,你可以从下面的一些示例开始。
使用 ChromeDriver
ChromeDriver 2.3.0 支持 Chrome 59 及更新版本,可与 headless Chrome 配合使用。在某些情况下,你可能需要 Chrome 60 以解决 bug。例如,Chrome 59 中屏幕截图已知存在问题。
安装:
yarn add selenium-webdriver chromedriver
例子:
const fs = require('fs');
const webdriver = require('selenium-webdriver');
const chromedriver = require('chromedriver');
// This should be the path to your Canary installation.
// I'm assuming Mac for the example.
const PATH_TO_CANARY = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
const chromeCapabilities = webdriver.Capabilities.chrome();
chromeCapabilities.set('chromeOptions', {
binary: PATH_TO_CANARY // Screenshots require Chrome 60\. Force Canary.
'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 WebDrive 上构建的更高层次的 API.
安装:
yarn add webdriverio chromedriver
例子:过滤 chromestatus.com 上的 CSS 功能
const webdriverio = require('webdriverio');
const chromedriver = require('chromedriver');
// This should be the path to your Canary installation.
// I'm assuming Mac for the example.
const PATH_TO_CANARY = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary';
const PORT = 9515;
chromedriver.start([
'--url-base=wd/hub',
`--port=${PORT}`,
'--verbose'
]);
(async () => {
const opts = {
port: PORT,
desiredCapabilities: {
browserName: 'chrome',
chromeOptions: {
binary: PATH_TO_CANARY // Screenshots require Chrome 60\. Force Canary.
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();
})();
更多资源
以下是一些可以带你入门的有用资源:
文档
- DevTools Protocol Viewer - API 参考文档
工具
-
chrome-remote-interface - 基于 DevTools 协议的 node 模块
-
Lighthouse - 测试 Web 应用程序质量的自动化工具;大量使用了协议
-
chrome-launcher - 用于启动 Chrome 的 node 模块,可以自动化
样例
- "The Headless Web" - Paul Kinlan 发布的使用了 Headless 和 api.ai 的精彩博客
常见问题
我需要 --disable-gpu
标志吗?
目前是需要的。--disable-gpu
标志在处理一些 bug 时是需要的。在未来版本的 Chrome 中就不需要了。查看 https://crbug.com/546953#c152 和 https://crbug.com/695212 获取更多信息。
所以我仍然需要 Xvfb 吗?
不。Headless Chrome 不使用窗口,所以不需要像 Xvfb 这样的显示服务器。没有它你也可以愉快地运行你的自动化测试。
什么是 Xvfb?Xvfb 是一个用于类 Unix 系统的内存显示服务器,可以让你运行图形应用程序(如 Chrome),而无需附加的物理显示。许多人使用 Xvfb 运行早期版本的 Chrome 进行 “headless” 测试。
如何创建一个运行 Headless Chrome 的 Docker 容器?
查看 lighthouse-ci。它有一个使用 Ubuntu 作为基础镜像的 Dockerfile 示例,并且在 App Engine Flexible 容器中安装和运行了 Lighthouse。
我可以把它和 Selenium / WebDriver / ChromeDriver 一起使用吗?
是的。查看 Using Selenium, WebDrive, or ChromeDriver。
它和 PhantomJS 有什么关系?
Headless Chrome 和 PhantomJS 是类似的工具。它们都可以用来在 headless 环境中进行自动化测试。两者的主要不同在于 Phantom 使用了一个较老版本的 WebKit 作为它的渲染引擎,而 Headless Chrome 使用了最新版本的 Blink。
目前,Phantom 提供了比 DevTools protocol 更高层次的 API。
我在哪儿提交 bug?
对于 Headless Chrome 的 bug,请提交到 crbug.com。
对于 DevTools 洗衣的 bug,请提交到 github.com/ChromeDevTools/devtools-protocol。
作者简介
Eric Bidelman 谷歌工程师,Lighthouse 开发,Web 和 Web 组件开发,Chrome 开发
via: https://developers.google.com/web/updates/2017/04/headless-chrome
作者:Eric Bidelman 译者:firmianay 校对:校对者ID