无头 Chrome 使用入门

要点

无头 Chrome 将在 Chrome 59 中发布。这是在无头环境中运行 Chrome 浏览器的一种方式。本质上,就是在不使用 Chrome 的情况下运行 Chrome!它可将 Chromium 和 Blink 渲染引擎提供的所有现代网络平台功能引入到命令行中。

这有什么用?

无头浏览器非常适合自动化测试和服务器环境,在这些环境中,您不需要可见的界面 Shell。例如,您可能需要针对真实网页运行一些测试、创建该网页的 PDF 副本,或者仅检查浏览器如何呈现网址。

启动无头模式 (CLI)

若要开始使用无头模式,最简单的方法是从命令行打开 Chrome 二进制文件。如果您已安装 Chrome 59 或更高版本,请使用 --headless 标志启动 Chrome:

chrome \
--headless \                   # Runs Chrome in headless mode.
--disable-gpu \                # Temporarily needed if running on Windows.
--remote-debugging-port=9222 \
https://www.chromestatus.com   # URL to open. Defaults to about:blank.

chrome 应指向您安装的 Chrome。确切位置因平台而异。由于我使用的是 Mac,因此为自己安装的每个 Chrome 版本创建了便捷的别名。

如果您使用的是 Chrome 的稳定渠道,并且无法获取 Beta 版,建议您使用 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 Canary 版。

命令行功能

在某些情况下,您可能不需要以编程方式编写脚本来使用无头 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 撰写了一篇非常实用的博文,可供您参考。请参阅将无头 Chrome 用作自动截图工具

REPL 模式(读取-求值-输出循环)

--repl 标志会以无头模式运行,您可以在命令行中直接在浏览器中评估 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
$

如何在没有浏览器界面的情况下调试 Chrome?

使用 --remote-debugging-port=9222 运行 Chrome 时,它会启动一个启用了 DevTools 协议的实例。该协议用于与 Chrome 通信并驱动无头浏览器实例。Sublime、VS Code 和 Node 等工具也使用该协议来远程调试应用。#synergy

由于您没有浏览器界面来查看该页面,因此请在其他浏览器中前往 http://localhost:9222,检查一切是否正常运行。您会看到可检查的页面列表,您可以点击这些页面,查看 Headless 正在呈现的内容:

开发者工具远程
DevTools 远程调试界面

然后,您可以使用熟悉的 DevTools 功能像往常一样检查、调试和调整页面。如果您以编程方式使用无头模式,此页面还可用作强大的调试工具,用于查看通过网络传输并与浏览器通信的所有原始 DevTools 协议命令。

以程序化方式使用(Node)

木偶操作师

Puppeteer 是由 Chrome 团队开发的 Node 库。它提供了高级 API 来控制无头(或完整)Chrome。它与 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();
})();

如需详细了解完整的 API,请参阅 Puppeteer 文档

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 进行安装:

npm i --save chrome-launcher

示例 - 使用 chrome-launcher 启动无头

const chromeLauncher = require('chrome-launcher');

// Optional: set logging level of launcher to see its output.
// Install it using: npm i --save 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();
});

运行此脚本不会执行太多操作,但您应该会在任务管理器中看到一个已加载 about:blank 的 Chrome 实例启动。请注意,系统不会显示任何浏览器界面。我们是无头的。

如需控制浏览器,我们需要使用 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.
});

})();

示例 - 使用 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 实例。换句话说,这是一种自动化解决方案,但并非完全无头。不过,只需稍加配置,Selenium 即可运行无头 Chrome。如果您想详细了解如何自行进行设置,建议您参阅通过无头 Chrome 运行 Selenium。不过,我已在下方添加了一些示例,以便您快速上手。

使用 ChromeDriver

ChromeDriver 2.32 使用 Chrome 61,可与无头 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

WebDriverIO 是基于 Selenium WebDriver 的更高级别 API。

安装:

npm i --save-dev webdriverio chromedriver

示例:在 chromestatus.com 上过滤 CSS 功能

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();

})();

更多资源

以下是一些实用资源,可帮助您上手使用:

文档

工具

演示

  • The Headless Web”(无头 Web)- Paul Kinlan 撰写的一篇出色的博文,介绍了如何将 Headless 与 api.ai 搭配使用。

常见问题解答

我需要 --disable-gpu 标志吗?

仅限 Windows。其他平台不再需要此属性。--disable-gpu 标志是对某些 bug 的临时解决方法。在 Chrome 的未来版本中,您将不需要此标志。如需了解详情,请访问 crbug.com/737678

所以我仍然需要 Xvfb?

不需要。无头 Chrome 不使用窗口,因此不再需要 Xvfb 等显示服务器。您无需它即可顺利运行自动化测试。

什么是 Xvfb?Xvfb 是适用于类 Unix 系统的内存显示服务器,可让您在未连接实体显示屏的情况下运行图形应用(例如 Chrome)。许多人使用 Xvfb 运行较低版本的 Chrome 来进行“无头”测试。

如何创建运行无头 Chrome 的 Docker 容器?

请查看 lighthouse-ci。该示例包含一个 Dockerfile 示例,该示例使用 node:8-slim 作为基础映像,在 App Engine Flex 上安装并运行 Lighthouse

我可以将此 API 与 Selenium / WebDriver / ChromeDriver 搭配使用吗?

可以。请参阅使用 Selenium、WebDriver 和 ChromeDriver

这与 PhantomJS 有何关系?

无头 Chrome 类似于 PhantomJS 等工具。这两种方式都可以用于在无头环境中进行自动化测试。这两者之间的主要区别在于,Phantom 使用较低版本的 WebKit 作为其渲染引擎,而无头 Chrome 使用最新版本的 Blink。

目前,Phantom 还提供比 DevTools 协议更高级别的 API。

在哪里可以报告 bug?

如需报告与无头 Chrome 相关的 bug,请访问 crbug.com

如需报告 DevTools 协议中的 bug,请访问 github.com/ChromeDevTools/devtools-protocol 进行报告。