[译] 如何检测 Chrome Headless(无头浏览器)?

原文链接:https://antoinevastel.com/bot%20detection/2017/08/05/detect-chrome-headless.html

原文标题:Detecting Chrome Headless

原文作者:Antoine Vastel

译者注:自从 Chrome Headless 发布后,各种基于 Chrome Headless 的自动化测试工具、爬虫等项目层出不穷。比起已经停止更新且容易被针对的 PhantomJS、Selenium 等经典自动化测试程序,Chrome Headless 几乎是完美的。然而,比起 Chrome 的普通模式,Chrome Headless 仍然有一些细微的差别,使其会被抓住小辫子……

编者注:我创建了一个库,可以使用浏览器指纹来检测识别机器人与爬虫。虽然这个库仍在开发中,但您已经可以开始使用它了。Github 地址为:https://github.com/antoinevastel/fpscanner

什么是无头浏览器(headless browser)?

无头浏览器就是一种没有图形用户界面的浏览器。它可以被程序控制,从而自动执行某些任务,例如进行网页自动化测试或网页截图。

为什么要检测无头浏览器?

除了上文提到的那两个人畜无害的例子,无头浏览器还可用于自动执行一些恶意的任务。例如,无头浏览器经常被用于网页爬取,增加广告展示次数或寻找网站漏洞中。

到目前为止,最受欢迎的无头浏览器之一是 PhantomJS。不过,因为它是基于 Qt 框架构建的,所以与大多数流行的浏览器相比,它在许多地方都有所不同。正如这篇文章所述,我们可以使用一些基于浏览器指纹的识别技术来检测它。

在 Chrome 59 之后,Google 发布了 Chrome 浏览器的 Headless 版本。与 PhantomJS 不同,它基于普通的 Chrome 而不是外部框架,这使得它更难被检测出来。

接下来,我们将介绍几种可用于区分普通 Chrome 浏览器和 Chrome Headless 的技术。

检测 Chrome Headless

编者注:我们仅在四台设备上做过测试(2 台 Linux 系统,2 台 Mac),这意味着可能还有其他一些方法能够检测 Chrome Headless。

通过「User Agent」检测

我们首先从 User Agent 开始,这是一种常用于检测操作系统的以及用户所用浏览器的属性。在 Linux 系统下的 Chrome Headless 中,User Agent 的值为:“Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/59.0.3071.115 Safari/537.36”

因此,我们可以用这种方式来检测 Chrome 无头浏览器:


if (/HeadlessChrome/.test(window.navigator.userAgent)) {
    console.log("Chrome headless detected");
}

User Agent 也可以从 HTTP 头部字段中获得。不过,用这两种方式获取的 User Agent 实际上都很容易被伪装。

通过「浏览器插件」检测

navigator.plugins 会返回一个数组,其中包含浏览器当前使用的插件。通常来说,在 Chrome 上我们会找到一些默认集成的插件,比如 Chrome PDF 查看器或 Google Native Client。与此相反,在 Headless 模式下,返回的数组中将不包含任何插件。


if(navigator.plugins.length == 0) {
    console.log("It may be Chrome headless");
}

通过「浏览器语言」检测

在 Chrome 中,有两个 Javascript 属性可以获取用户使用的语言:navigator.language 和 navigator.languages。第一个属性表示浏览器用户界面的语言,第二个属性则是一个字符串类型的数组,其中包含用户的所有首选语言。但是,在 Headless
模式下,navigator.languages 将返回一个空字符串。


if(navigator.languages == "") {
    console.log("Chrome headless detected");
}

借助「WebGL API 获得的参数信息」检测

WebGL 是一种在 HTML Canvas 中中执行 3D 渲染操作的 API。通过 WebGL API,可以查询到绘图驱动程序的供应商(vendor)以及绘图驱动程序的渲染器(renderer)。

当使用 Linux 下的普通 Chrome 时,我获得的渲染器和供应商为:“Google SwiftShader” 和 “Google Inc.”。在无头模式下,我获得的渲染器值为 “Mesa OffScreen”,这是一种不渲染任何窗口的技术。供应商则为 “Brian Paul”,这个项目孵化了开源图形库 Mesa。

并非所有的 Chrome Headless 都将有相同的供应商和渲染器值。其他的一些值也可能出现在非无头版本的 Chrome 中。不过,“Mesa Offscreen” 和 “Brian Paul” 是肯定能表示无头版本的。


var canvas = document.createElement('canvas');
var gl = canvas.getContext('webgl');

var debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
var vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
var renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);

if(vendor == "Brian Paul" && renderer == "Mesa OffScreen") {
    console.log("Chrome headless detected");
}

通过「判断浏览器是否支持某些功能」进行检测

Modernizr 库可以测试浏览器中是否支持各种 HTML 和 CSS 功能。我们在 Chrome 和 Chrome Headless 之间只找到了一个区别,就是后者不支持 hairline 功能,所以我们可以通过检测浏览器是否支持 hidpi / retina hairlines 来判断 Chrome 是否正处于 Headless 模式下。


if(!Modernizr["hairline"]) {
    console.log("It may be Chrome headless");
}

借助「加载失败的图片」检测

我们的最后一个发现与 Chrome 中那些无法加载的图片的尺寸有关,这似乎也是最有力的一个检测方法。

对于普通的 Chrome,这些加载失败的图片仍然具有宽度和高度,其具体值取决于浏览器的缩放情况,总之肯定不会是零。但是,在 Chrome Headless 中,这种图片的宽度和高度全部都为零。


var body = document.getElementsByTagName("body")[0];
var image = document.createElement("img");
image.src = "http://iloveponeydotcom32188.jg";
image.setAttribute("id", "fakeimage");
body.appendChild(image);
image.onerror = function(){
    if(image.width == 0 && image.height == 0) {
        console.log("Chrome headless detected");
    }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注