爬虫漫游指南:浏览器指纹

爬虫漫游指南

浏览器指纹

最近深入阅读了瑞数的源码,发现瑞数收集了很多浏览器的特征信息,这部分源码对了解浏览器具有很高的学习价值,为此整理了这些特征信息,并尽可能还原了可读可用的代码来获取这些信息。本文测试环境为Chrome、Firefox和IE。

基础信息

这里的基础信息,指直接能获取到的浏览器属性,如navigator.userAgent这样最基础的信息。

navigator下的基础信息
  • navigator.languages:浏览器的语言环境,一个数组,形如[“zh-CN”];
  • navigator.hardwareConcurrency:当前浏览器环境所拥有的CPU核心数,准确的说是线程数,而非核数,比如6核12线程的i5-10500显示的值是12;
  • navigator.cpuClass:同样是cpu信息,形如"x86",只在IE中发现了这一属性,Chrome和Firefox中均为undefined;
  • navigator.oscpu:操作系统信息,形如"Windows NT 10.0; Win64; x64",只在Firefox中发现了这一属性,Chrome和IE中均为undefined;
  • navigator.platform:用户的操作系统信息,比如Mac、Intel、Win32、Linux x86_64等;
  • navigator.appName:测试中3个浏览器均为"Netscape";
  • navigator.productSub:Chrome浏览器中该值为"20030107",IE无此属性;
  • navigator.product:测试中3个浏览器均为"Gecko";
  • navigator.webdriver:在早几年的版本中,只有Chrome的无头浏览器puppeteer中有这一属性,当时只要检测navigator.webdriver == true就可判断为模拟浏览器,正常使用的Chrome中是undefined,在后来的更新中正常启动的Chrome也有了这一属性,值为false;
  • navigator.getBattery(): 这一方法Chrome支持、IE不支持、Firefox默认禁用,但可以启用,这一方法的检测代码是"getBattery" in "navigator"
window下的基础信息

前面的部分是开胃小菜,从这里开始要上硬菜了。

_$Rn = [
    "AudioTrackList",
    "defaultStatus",
    "Object.setPrototypeOf",
    "taobrowser_Event",
    "webkitRequestFileSystem",
    "onoperadetachedviewchange",
    "Path2D.prototype.addPath",
    "SourceBuffer.prototype.changeType",
    "weatherBridge",
    "chrome.csi",
    "password_manager_enabled",
    "document.body.x-ms-acceleratorkey",
    "external.AddFavorite",
    "SogouLoginUtils",
    "SourceBuffer",
    "showModalDialog",
    "document.selection.typeDetail",
    "SVGPatternElement.SVG_UNIT_TYPE_OBJECTBOUNDINGBOX",
    "document.onselectionchange",
    "document.body.style.backgroundBlendMode",
    "document.documentElement.onresize",
    "CanvasRenderingContext2D.prototype.webkitGetImageDataHD",
    "UCWebExt",
    "CDATASection.prototype.remove",
    "BlobDownloadCallback",
    "_WXJS",
    "document.msCapsLockWarningOff",
    "CSSCharsetRule",
    "document.scrollingElement.style.fontVariantNumeric",
    "Function.prototype.bind",
    "chrome.app.InstallState",
    "isNodeWhitespace",
    "Object.seal",
    "document.defaultCharset",
    "__firefox__",
    "onmessage",
    "__sogou_secure_input",
    "CloseEvent.prototype.initCloseEvent",
    "getMatchedCSSRules",
    "Notification",
    "HTMLFrameSetElement.prototype.hasPointerCapture",
    "document.body.onmouseenter",
    "OffscreenCanvasRenderingContext2D",
    "chrome",
    "Object.prototype.__defineSetter__",
    "document.fileCreatedDate",
    "webkitAudioContext.prototype.close",
    "GetPerfTests",
    "MediaController",
    "external.IsSearchProviderInstalled",
    "TextTrackList.prototype.getTrackById",
    "document.selection",
    "document.body.style.lineBreak",
    "document.body.style.textAlignLast",
    "ScreenOrientation",
    "document.body.style.minWidth",
    "SpeechSynthesisUtterance",
    "onerror",
    "WebKitFlags",
    "ReaderModeArticlePage",
    "__opera",
    "PerformancePaintTiming",
    "performance",
    "document.body.style.msTextSizeAdjust",
    "document.body.onpage",
    "SVGGraphicsElement.prototype.mozRequestPointerLock",
    "ClickData",
    "MediaEncryptedEvent",
    "__$_qihoo360_$__",
    "document.onmousemove",
    "BeforeInstallPromptEvent.prototype.KEYUP",
    "HTMLFrameSetElement.prototype.webkitRequestFullScreen",
    "external"
]

也不多,73个,逐一分析的话肝不够用,直接上瑞数的检测源码了,稍微翻译了一下,可读性已经比原来的代码强一百倍了。

_$S7 = []
function HV22(a, b) {
    try {
        return a[b];
    } catch (_e) {
        return false;
    }
}

function foo(_$XP, _$6Y) {
    for (_$_$ = 0; _$_$ < _$6Y.length - 1; ++_$_$) {
        _$XP = HV22(_$XP, _$6Y[_$_$])
        if (!_$XP) {
            return false;
        }
    }

    try {
        return HV22(_$XP, _$6Y[_$6Y.length - 1]) || _$6Y[_$6Y.length - 1] in _$XP || _$XP["hasOwnProperty"](_$6Y[_$6Y.length - 1]);
    } catch (_e) {
        return false;
    }
}
for (_$ZN = 0; _$ZN < _$Rn.length; _$ZN++) {
    var l = _$Rn[_$ZN].split(".");
    var w = window;
    result = foo(w, l);
    _$S7.push(result ? 1 : 0);
}
_$S7.join("")

这坨代码会得到一个73位的01序列,每一位都都应上面数组里的每一个属性是否存在,是1非0,在3个浏览器中运行分别得到如下结果:

Chrome:
0110101101000010001110010000110010010001111110000110111111000110000101011
Firefox:
0010001100000010001110010000110010010001110010000110111111000110000101001
IE:
1110000000001011011000000010010011010100010011000100110101000011000001001

不要小看这串序列哦,每一位都是重要的信息,如Chrome中的第一位是0,即Chrome中没有window.AudioTrackList这一对象,只有IE才有,所以IE对应序列的第一位是1,我在Chrome中给这一对象赋值,使序列的第一位变成了1,然后这个请求直接被瑞数封了。

想来也合理,你其他所有信息都显示你是Chrome,但你偏偏有个window.AudioTrackList,这河里吗?这布河里。

进阶信息

navigator下的进阶信息

还是让navigator先来,navigator.plugins下有颇多信息,根据mozilla早先的API文档,它会返回当前所使用的浏览器安装的所有插件,但在后来的版本中,出于隐私考虑,navigator.plugins 数组的枚举可能会被限制。

获取plugins信息的代码如下

var _$6Y = [];
var _$h2 = navigator;
var _$XP = _$h2["plugins"];
_$7y = _$XP;

if (!_$7y) {} else {
  for (_$_$ = 0; _$_$ < _$XP.length; _$_$++) {
    _$UZ = _$XP[_$_$];
    _$Cg = [_$UZ["name"], _$UZ["description"], _$UZ["filename"], _$UZ["version"]].join(",");

    _$6Y.push(_$Cg);
  }
}
_$6Y

由于上述的隐私限制,plugins根本无法显示我安装的插件信息,我在装了一堆插件的Chrome和一个纯洁无瑕的Chrome上得到的值是一样的

["Chrome PDF Plugin,Portable Document Format,internal-pdf-viewer,", "Chrome PDF Viewer,,mhjfbmdgcfjbbpaeojofohoefgiehjai,", "Native Client,,internal-nacl-plugin,"]

但是这一属性仍能用于区分不同的浏览器,不同浏览器下跑这段代码必然是不同的,IE中肯定不会出现什么Chrome PDF Plugin

navigator下还有一个长的和plugins有点像的东西,叫mimeTypes,其中包含可被当前浏览器识别的MimeType对象的列表。MIME,就是媒体类型,Multipurpose Internet Mail Extensions。

代码也和上面的有些类似

var _$6Y = [];
var _$h2 = navigator;
var _$XP = _$h2["mimeTypes"];
_$7y = _$XP;

if (!_$7y) {} else {
    for (_$_$ = 0; _$_$ < _$XP.length; _$_$++) {
        _$UZ = _$XP[_$_$];

        _$6Y.push([_$UZ["type"], _$UZ["suffixes"], _$UZ["description"]].join(","));
    }
}
_$6Y

在我的测试环境中,Chrome包含4种,IE包含2种,Firefox包含0种。

window下的进阶信息

window.Intl.DateTimeFormat().resolvedOptions(),这个东西在不同浏览器下长的也不一样

var _$pb = ''
var _$M9 = window
var _$S7 = new _$M9["Intl"]["DateTimeFormat"]()["resolvedOptions"]();
[_$S7["calendar"] !== _$pb ? _$S7["calendar"] : '', _$S7["fractionalSecondDigits"] !== _$pb ? _$S7["fractionalSecondDigits"] : '', _$S7["locale"] !== _$pb ? _$S7["locale"] : '', _$S7["numberingSystem"] !== _$pb ? _$S7["numberingSystem"] : '', _$S7["timeZone"] !== _$pb ? _$S7["timeZone"] : '', _$S7["year"] !== _$pb ? _$S7["year"] : '', _$S7["month"] !== _$pb ? _$S7["month"] : '', _$S7.day !== _$pb ? _$S7.day : '']

IE中,timeZone是undefined,其它浏览器则是有值的"Asia/Shanghai",而locale也长的不太一样,虽然都是表示中文,但我的Chrome显示是zh-CN,IE则是zh-Hans-CN

老版IE专区

虽然不知道哪些可恶的用户还在用老版本的IE,但瑞数很“贴心”的对这部分也检测了。先贴一下代码

var _$hR = window.ActiveXObject;
_$I6 = [];
_$S7 = [
    "ShockwaveFlash.ShockwaveFlash",
    "AcroPDF.PDF",
    "PDF.PdfCtrl",
    "QuickTime.QuickTime",
    "rmocx.RealPlayer G2 Control",
    "rmocx.RealPlayer G2 Control.1",
    "RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)",
    "RealVideo.RealVideo(tm) ActiveX Control (32-bit)",
    "RealPlayer",
    "SWCtl.SWCtl",
    "WMPlayer.OCX",
    "AgControl.AgControl",
    "Skype.Detection"
];

for (_$5z = 0; _$5z < _$S7.length; _$5z++) {
    try {
        new _$hR(_$S7[_$5z]);
        _$I6.push(_$S7[_$5z]);
    } catch (_$VC) {
        console.log(_$VC)
    }
}
_$I6

ActiveXObject,一个古董级的名字,在大部分前端眼里都直接和不支持三个字划等号。这个列表里的古董绝大部分在新版IE里都没得了,只有WMPlayer.OCX还能new出来,Chrome和Firefox就更不谈了,只能得到一个空值。

一些骚气的操作

H5中的Audio/Video组件各自都有一个canPlayType方法,该方法用于判断浏览器是否能播放指定的音频/视频类型,返回值有如下几种:

  • “probably” - 浏览器最可能支持该音频/视频类型
  • “maybe” - 浏览器也许支持该音频/视频类型
  • “” - (空字符串)浏览器不支持该音频/视频类型

所有浏览器都支持 canPlayType() 方法,但是在不同的浏览器下,能支持的文件类型是不同的。以audio组件为例:

function _$IJ(_$N0) {
    var _$S7 = typeof _$N0 === "function" && (_$N0 + '')["indexOf"]("[native code]") !== -1;
    return _$S7;
}

var _$vs = document;
var _$UZ = [];
var _$vE = "audio/ogg; codecs=\"vorbis\"|audio/wav; codecs=\"1\"|audio/mpeg;|audio/x-m4a;audio/aac;";
var _$7y = _$vs["createElement"]("audio");
_$kh = _$7y && _$7y["canPlayType"] && _$IJ(_$7y["canPlayType"]);

if (!_$kh) {} else {
    _$pX = _$vE["split"]("|");

    for (_$ZN = 0; _$ZN < _$pX.length; _$ZN++) {
        _$UZ.push(_$7y["canPlayType"](_$pX[_$ZN]));
    }
}
_$UZ

上述代码在Chrome中得到的返回值是[“probably”, “probably”, “probably”, “maybe”],而在Firefox中则稍有不同[ “probably”, “probably”, “maybe”, “maybe” ],IE则最垃圾["", “”, “maybe”, “maybe”],有两个空字符串。

如果这种方法还不够骚的话,那还有下面这种更骚的。

用try-catch获取抛出的异常,不同浏览器的异常报错是不一样的。

var _$6Y = [];

try {
    eval("$_config__.detail__ += 1");
} catch (_$XP) {
    _$6Y.push(_$XP["message"]);
    _$6Y.push(_$XP["lineNumber"]);
    _$6Y.push(_$XP["description"]);
    _$6Y.push("fileName" in _$XP);
}

try {
    new window["WebSocket"]("itsgonnafail");
} catch (_$XP) {
    _$6Y.push(_$XP["message"]);
}
_$6Y

IE的报错信息是:
["“$_config__”未定义", undefined, "“$_config__”未定义", false, "SyntaxError"]

Firefox是:
[ "$_config__ is not defined", 1, undefined, true, "An invalid or illegal string was specified" ]

Chrome是:
["$_config__ is not defined", undefined, undefined, false, "Failed to construct 'WebSocket': The URL 'itsgonnafail' is invalid."]

除此之外,瑞数中还有一些针对canvas、webgl啥的高级操作,太麻烦了,就不写了。

爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL可以通过链接分析、站点地图、搜索引擎等方式获取。 请求网页: 爬虫使用HTTP或其他协议向目标URL发起请求,获取网页的HTML内容。这通常通过HTTP请求库实现,如Python中的Requests库。 解析内容: 爬虫对获取的HTML进行解析,提取有用的信息。常用的解析工具有正则表达式、XPath、Beautiful Soup等。这些工具帮助爬虫定位和提取目标数据,如文本、图片、链接等。 数据存储: 爬虫将提取的数据存储到数据库、文件或其他存储介质中,以备后续分析或展示。常用的存储形式包括关系型数据库、NoSQL数据库、JSON文件等。 遵守规则: 为避免对网站造成过大负担或触发反爬虫机制,爬虫需要遵守网站的robots.txt协议,限制访问频率和深度,并模拟人类访问行为,如设置User-Agent。 反爬虫应对: 由于爬虫的存在,一些网站采取了反爬虫措施,如验证码、IP封锁等。爬虫工程师需要设计相应的策略来应对这些挑战。 爬虫在各个领域都有广泛的应用,包括搜索引擎索引、数据挖掘、价格监测、新闻聚合等。然而,使用爬虫需要遵守法律和伦理规范,尊重网站的使用政策,并确保对被访问网站的服务器负责。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值