如何用JavaScript检测当前浏览器是无头浏览器


Posted in Javascript onApril 27, 2021

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

无头浏览器是指可以在图形界面情况下运行的浏览器。我可以通过编程来控制无头浏览器自动执行各种任务,比如做测试,给网页截屏等。

为什么叫“无头”浏览器?

“无头”这个词来源于最初的“无头计算机(Headless computer)”。维基百科关于的“无头计算机”词条:

无头系统(headless system)是指已配置为无须显示器(即“头”)、键盘和鼠标操作的计算机系统或设备。无头系统通常通过网络连接控制,但也有部分无头系统的设备需要通过RS-232串行连接进行设备的管理。服务器通常采用无头模式以降低运作成本。

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

除了之前提到的两种无害的使用案例,无头浏览器可以被用来自动执行恶意任务。最常见的形式是做网络爬虫,或伪装访问量,或探测网站漏洞。

一个非常流行的无头浏览器是Phantomjs,因为它是基于 Qt框架,所以跟我们常见的浏览器相比有很多不同的特征,因此有很多方法判断出它。

但是,从chrome 59开始,谷歌发布了一款无头谷歌浏览器。它跟Phantomjs不同,它是基于正统的谷歌浏览器开发出来的,不是基于其它的框架,这让程序很难区分出它是正常浏览器还是无头浏览器。

下面,我们将介绍几种判断程序是运行在普通浏览器还是无头浏览器里的方法。

检测无头浏览器

注意:这些方法只是在四种设备 (2 Linux, 2 Mac) 里测试过,也就是说, 肯定还有其他很多方法检测无头浏览器。

User agent

先介绍使用做最常见的一种判断浏览器种类的方法,检查User agent。在linux计算机里Chrome version 59无头浏览器的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 headers 里获取。然而,这两种情况都很容易伪造。

插件 Plugins

navigator.plugins 会返回一个数组,里面是当前浏览器里的插件信息。通常,普通Chrome浏览器有一些缺省插件,比如 Chrome PDF viewer 或 Google Native Client。相反,在无头模式里,没有任何插件,返回的是个空数组。

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

语言

在谷歌浏览器里,有两个JavaScript属性可以获取当前浏览器的语言设置: navigator.language 和 navigator.languages。头一个是指浏览器界面的语言,后一个返回的是个数组,里面存储的是浏览器用户的所有次选语言。然而,在无头模式里,navigator.languages 返回的是个空字符串。

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

WebGL

WebGL 提供了一组能在htmlcanvas 里执行3D渲染的API。通过这些API,我们可以查询出图形驱动的 vendor 和 renderer 。

在linux上的普通谷歌浏览器里,我们获得的 renderer 和 vendor 值为: “Google SwiftShader” 和 “Google Inc.”。

而在无头模式里,我们获得的一个是 “Mesa OffScreen”——它是没有使用任何 window 系统的渲染技术的名称,和 “Brian Paul” ——开源 Mesa 图形库的最初的程序。

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");
 }

并不是所有版本的无头浏览器都有同样的这两个值。然而目前在无头浏览器里是“Mesa Offscreen” 和 “Brian Paul” 这两个值

浏览器特征

Modernizr 可以探测出当前浏览器对HTML和css各种特性的支持程度。我发现,普通Chrome和无头Chrome里唯一的区别是,无头模式下没有 hairline 特征,它是用来检测是否支持 hidpi/retina hairlines的

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

加载失败的图片

最后,我发现的最后一个方法,也是看起来最有效的方法,切入点是检查浏览器里不能正常加载的图片的高和宽。

在正常的Chrome里,未成功加载的图片的大小跟浏览器的zoom有关,但肯定不是零。而在无头Chrome浏览器里,这种图片的宽和高都是0。

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");
	}
}

以上就是如何用JavaScript检测当前浏览器是无头浏览器的详细内容,更多关于JavaScript的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
javascript+xml实现简单图片轮换(只支持IE)
Dec 23 Javascript
js日期、星座的级联显示代码
Jan 23 Javascript
Jquery 点击按钮自动高亮实现原理及代码
Apr 25 Javascript
js实现商品抛物线加入购物车特效
Nov 18 Javascript
javascript获取select标签选中的值
Jun 04 Javascript
js微信应用场景之微信音乐相册案例分享
Aug 11 Javascript
微信小程序实现购物车代码实例详解
Aug 29 Javascript
NProgress显示顶部进度条效果及使用详解
Sep 21 Javascript
layer.open组件获取弹出层页面变量、函数的实例
Sep 25 Javascript
JS实现灯泡开关特效
Mar 30 Javascript
vue props default Array或是Object的正确写法说明
Jul 30 Javascript
vue3语法糖内的defineProps及defineEmits
Apr 14 Vue.js
如何利用js在两个html窗口间通信
Apr 27 #Javascript
如何使JavaScript休眠或等待
Apr 27 #Javascript
JavaScript 实现页面滚动动画
如何用JS实现网页瀑布流布局
分享几个JavaScript运算符的使用技巧
Apr 24 #Javascript
JavaScript 防篡改对象的用法示例
Apr 24 #Javascript
jquery插件实现悬浮的菜单
You might like
ubuntu下编译安装xcache for php5.3 的具体操作步骤
2013/06/18 PHP
PHP中的排序函数sort、asort、rsort、krsort、ksort区别分析
2014/08/18 PHP
Laravel路由设定和子路由设定实例分析
2016/03/30 PHP
jquery 事件对象属性小结
2010/04/27 Javascript
javascript学习笔记(十九) 节点的操作实现代码
2012/06/20 Javascript
jQuery中使用Ajax获取JSON格式数据示例代码
2013/11/26 Javascript
JavaScript中如何通过arguments对象实现对象的重载
2014/05/12 Javascript
当滚动条滚动到页面底部自动加载增加内容的js代码
2014/05/13 Javascript
node.js中的console.dir方法使用说明
2014/12/10 Javascript
JavaScript中的console.profile()函数详细介绍
2014/12/29 Javascript
Jquery幻灯片特效代码分享--鼠标点击按钮时切换(1)
2015/08/15 Javascript
JS瀑布流实现方法实例分析
2016/12/19 Javascript
Jquery EasyUI Datagrid右键菜单实现方法
2016/12/30 Javascript
springMVC + easyui + $.ajaxFileUpload实现文件上传注意事项
2017/04/23 Javascript
AngularJS service之select下拉菜单效果
2017/07/28 Javascript
React-Router如何进行页面权限管理的方法
2017/12/06 Javascript
深入浅析nuxt.js基于ssh的vue通用框架
2019/05/21 Javascript
webpack的tree shaking的实现方法
2019/09/18 Javascript
JS实现简易日历效果
2021/01/25 Javascript
Python多线程编程(五):死锁的形成
2015/04/05 Python
python的pytest框架之命令行参数详解(上)
2019/06/27 Python
Python 使用threading+Queue实现线程池示例
2019/12/21 Python
Python处理mysql特殊字符的问题
2020/03/02 Python
英国领先的鞋类零售商和顶级品牌的官方零售商:Wynsors
2020/02/17 全球购物
自我评价怎么写正确呢?
2013/12/02 职场文书
新员工入职感言
2014/02/01 职场文书
女娲补天教学反思
2014/02/05 职场文书
2014年十一国庆向国旗敬礼寄语
2014/04/11 职场文书
村主任“四风”问题个人整改措施
2014/10/04 职场文书
2014年体检中心工作总结
2014/12/23 职场文书
新年祝酒词大全
2015/08/11 职场文书
一篇文章带你了解Python和Java的正则表达式对比
2021/09/15 Python
通过Python把学姐照片做成拼图游戏
2022/02/15 Python
MySQL数据库之内置函数和自定义函数 function
2022/06/16 MySQL
Win2008系统搭建DHCP服务器
2022/06/25 Servers
MySQL中的 inner join 和 left join的区别解析(小结果集驱动大结果集)
2023/05/08 MySQL