利用浏览器进行数据抓取需要改在环境,反爬虫中chrome无头浏览器的几种检测与绕过方式介绍了修改属性,本文总结修改方法。
在浏览中包含各种浏览器对象与网页元素对象,环境修改就是对这两类对象进行修改。Js中属性修改要么是修改该对象的自身属性,要么是修改该对象原型属性。当访问一个属性时,如果对象不存在该属性,那么在原型中去查找。因此修改就很明确了,改对象属性或原型属性。如果通过构造器修改,那么重新定义构造器prototype上的方法属性,如果通过实例对象修改那么修改实例对象的__proto__属性,实例的该属性指向原型对象[1] 。
有时封会通过元素标签上的方法进行封禁,这时就需要进行改造。Javascript通过文档对象模型(DOM)来操作网页[1]。DOM中最小组成单位是节点(node),节点类型如下:
就是说页面中的每个标签都有一个具体的元素对象与之对应。例如页面标签元素都是Element的子元素。
HTMLCanvasElement 对应 标签,该对象有toDataURL()与toBlob()等方法 [3]
HTMLTextAreaElement 对应了 标签,该对象有focus()等方法。[4]
以textarea的focus方法为例创建一个textarea元素对象。
ta = document.createElement("textarea");
修改该元素构造器对象(HTMLTextAreaElement)原型对象的方法focus
Object.defineProperty(HTMLTextAreaElement.prototype,"focus",{
get : () => function() {alert("modifyTest")}
});
接着我们调用focus方法,会弹出一个对话框。因此该标签环境改造完毕。那么反爬在检测时我们就可以绕过。
ta.focus();
首先通过navigator.hasOwnProperty("webdriver”) 返回false可知webdriver不是直接定义在navigator对象上的属性,因此我们可以直接重新定义。
Object.defineProperty(navigator,"webdriver",{
get:() => false
});
接着运行navigator.webdriver,返回false。如前所述,属性不是自身属性,那么就是原型属性。navigator实例对象,我们通过__proto__获得原型对象。
接着检测属性navigator.proto.hasOwnProperty("webdriver”); 返回true 那么直接删除原型上的属性达到修改的目的。并且更切合真实浏览器的状态。
delete navigator.__proto__.webdriver
接着运行navigator.webdriver,返回 undefined 修改完毕。
当通过Object.defineProperty通过修改属性描述对象时,除了修改其值get或set方法,其它的枚举属性也要修改。例如网站在收集用户浏览器connection信息时有如下代码收集connection对象属性。
for(let a in navigator.connection) {
console.log(a);
}
VM207:2 effectiveType
VM207:2 rtt
VM207:2 downlink
VM207:2 saveData
VM207:2 addEventListener
VM207:2 removeEventListener
VM207:2 dispatchEvent
接着希望修改connection的rtt属性获取方式。
Reflect.defineProperty(navigator.connection,"rtt",{
get:()=> 101
});
若直接修改后,收集代码在遍历connection时,将无法获取rtt属性[7]。因为该属性不可枚举。因此修改时,也要修改可枚举属性。这样收集代码最终将获取该属性。
Reflect.defineProperty(navigator.connection,"rtt",{
get:()=> 101,
enumerable:true
});
通过这个例子说明修改属性描述对象要关注每个属性。
https://fingerprintjs.com/zh/ 这个网站点击生成指纹,可以在控制台打印出检测的数据,然后根据这些数据调整自己的浏览器信息。
环境改造就是对属性或方法修改,利用上述方法基本能完成对环境的重定义。上述内容设计的知识点总结如下。
DOM模型
标签元素对象继承关系
原型对象
原型链__proto__
[1]Javascript面向对象编程指南(第2版) 5.1.2,p176
[2]https://wangdoc.com/javascript/dom/general.html
[3]https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
[4] https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement
[5] 原生只读对象劫持,https://zhuanlan.zhihu.com/p/24342684
[6] 指纹在线检测,https://fingerprintjs.com/zh/
[7] 重定义属性描述符后对象不可遍历的问题,https://stackoverflow.com/questions/55757089/strange-behavior-of-object-defineproperty-in-javascript
let deviceInfo = {
"window.speed":!window.speed?(typeof window.speed) : window.speed,
"window.deviceorientation":!window.deviceorientation?(typeof window.deviceorientation):window.deviceorientation,
"connectionInfo":connectionInfo(),
"eval.toString().length":eval.toString().length,
"'ontouchstart' in window":('ontouchstart' in window),
"history" : {
"history.length":history.length
},
"navigatorData": {
"navigator.maxTouchPoints":1,
"navigator.mimeTypes":navigator.mimeTypes.length,
"navigator.vibrate":(typeof navigator.vibrate),
"navigator.onLine":navigator.onLine,
"navigator.userAgent": navigator.userAgent,
"navigator.languages":navigator.languages,
"navigator.language":navigator.language,
"navigator.vendor":navigator.vendor,
"navigator.appVersion":navigator.appVersion,
"navigator.hardwareConcurrency":navigator.hardwareConcurrency,
"navigator.devceMemory": !navigator.devceMemory ? "" : navigator.devceMemory,
"navigator.appName":navigator.appName,
"navigator.appCodeName":navigator.appCodeName,
"navigator.cookieEnabled":navigator.cookieEnabled,
"navigator.cpuClass": !navigator.cpuClass ? "" : navigator.cpuClass,
"navigator.doNotTrack":(navigator.doNotTrack ? navigator.doNotTrack : navigator.msDoNotTrack ? navigator.msDoNotTrack : window.doNotTrack ? window.doNotTrack : undefined),
"navigator.platform":navigator.platform,
"navigator.product":navigator.product,
"navigator.productSub":navigator.productSub,
"navigator.vendorSub":navigator.vendorSub,
"navigator.buildID":!navigator.buildID? "" : navigator.buildID,
"navigator.connection":navigator.connection,
"navigator.connection.effectiveType":navigator.connection ? navigator.connection.effectiveType : "",
"navigator.plugins":navigator.plugins
},
"ClientTimezoneOffset":(new Date).getTimezoneOffset(),
"window.chrome":!!window.chrome,
"window.chrome.webstore": (!!window.chrome ? !!window.chrome.webstore : null),
"window.CSS":!!window.CSS,
"screenData": {
"screen.width":screen.width,
"screen.height":screen.height,
"screen.availWidth":screen.availWidth,
"screen.availHeight":screen.availHeight,
"screen.colorDepth":screen.colorDepth,
"screen.pixelDepth":screen.pixelDepth,
"window.devicePixelRatio":window.devicePixelRatio,
"screen.width * (window.devicePixelRatio || 1)":screen.width * (window.devicePixelRatio || 1),
"screen.height * (window.devicePixelRatio || 1)":screen.height * (window.devicePixelRatio || 1)
},
"navigator.hardwareConcurrency":navigator.hardwareConcurrency,
"webglInfo":webglInfo(),
"adblockTest":adblockTest()
}
console.log(JSON.stringify(deviceInfo));
navigator.getBattery().then(function(battery) {
deviceInfo.batteryInfo = {
"battery.level":battery.level,
"battery.charging":battery.charging,
"battery.dischargingTime":battery.dischargingTime
}
//document.writeln(JSON.stringify(deviceInfo));
});
function connectionInfo() {
let nav = window.navigator;
if (!nav.connection)
return "";
var e = {};
if (typeof nav.connection == "object") {
for (var t in nav.connection) {
if (typeof nav.connection[t] !== "function") {
e[t] = nav.connection[t];
}
}
}
return e;
}
function adblockTest() {
var e = document.createElement("div");
e.innerHTML = " ",
e.className = "adsbox";
var t = !1;
try {
document.body && (document.body.appendChild(e),
t = 0 === document.getElementsByClassName("adsbox")[0].offsetHeight,
document.body.removeChild(e))
} catch (n) {
t = !1
}
return t
}
function webglInfo() {
let canvasEle = document.createElement("canvas");
let webglCtx = canvasEle.getContext("experimental-webgl");
let webglDebugRenderInfo = webglCtx.getExtension("WEBGL_debug_renderer_info");
let anisotropic = webglCtx.getExtension("EXT_texture_filter_anisotropic") || webglCtx.getExtension("WEBKIT_EXT_texture_filter_anisotropic") || webglCtx.getExtension("MOZ_EXT_texture_filter_anisotropic");
let anisotropicExt = webglCtx.getParameter(anisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
let maxVertexTextureImageUnits = webglCtx.getShaderPrecisionFormat ? webglCtx.getShaderPrecisionFormat(webglCtx.VERTEX_SHADER, webglCtx.MEDIUM_FLOAT).precision : 0;
let fragmentShaderBestPrecision = webglCtx.getShaderPrecisionFormat ? webglCtx.getShaderPrecisionFormat(webglCtx.FRAGMENT_SHADER, webglCtx.MEDIUM_FLOAT).precision : 0;
let fragmentShaderFloatIntPrecision = (webglCtx.getShaderPrecisionFormat(webglCtx.FRAGMENT_SHADER, webglCtx.HIGH_FLOAT).precision ? "highp/" : "mediump/") + (webglCtx.getShaderPrecisionFormat(webglCtx.FRAGMENT_SHADER, webglCtx.HIGH_INT).rangeMax ? "highp" : "lowp")
return {
"RENDERER": webglCtx.getParameter(webglCtx.RENDERER),
"VENDOR": webglCtx.getParameter(webglCtx.VENDOR),
"VERSION": webglCtx.getParameter(webglCtx.VERSION),
// 浏览器和手机一致不考虑
"UNMASKED_RENDERER_WEBGL": webglCtx.getParameter(webglDebugRenderInfo.UNMASKED_RENDERER_WEBGL),
"UNMASKED_VENDOR_WEBGL": webglCtx.getParameter(webglDebugRenderInfo.UNMASKED_VENDOR_WEBGL),
"STENCIL_TEST": webglCtx.isEnabled(webglCtx.STENCIL_TEST),
"SHADING_LANGUAGE_VERSION": webglCtx.getParameter(webglCtx.SHADING_LANGUAGE_VERSION),
"RED_BITS": webglCtx.getParameter(webglCtx.RED_BITS),
"GREEN_BITS": webglCtx.getParameter(webglCtx.GREEN_BITS),
"BLUE_BITS": webglCtx.getParameter(webglCtx.BLUE_BITS),
"ALPHA_BITS": webglCtx.getParameter(webglCtx.ALPHA_BITS),
"MAX_RENDERBUFFER_SIZE": webglCtx.getParameter(webglCtx.MAX_RENDERBUFFER_SIZE),
"MAX_COMBINED_TEXTURE_IMAGE_UNITS": webglCtx.getParameter(webglCtx.MAX_COMBINED_TEXTURE_IMAGE_UNITS),
"MAX_CUBE_MAP_TEXTURE_SIZE": webglCtx.getParameter(webglCtx.MAX_CUBE_MAP_TEXTURE_SIZE),
"MAX_FRAGMENT_UNIFORM_VECTORS": webglCtx.getParameter(webglCtx.MAX_FRAGMENT_UNIFORM_VECTORS),
"MAX_TEXTURE_IMAGE_UNITS": webglCtx.getParameter(webglCtx.MAX_TEXTURE_IMAGE_UNITS),
"MAX_TEXTURE_SIZE": webglCtx.getParameter(webglCtx.MAX_TEXTURE_SIZE),
"MAX_VARYING_VECTORS": webglCtx.getParameter(webglCtx.MAX_VARYING_VECTORS),
"MAX_VERTEX_ATTRIBS": webglCtx.getParameter(webglCtx.MAX_VERTEX_ATTRIBS),
"MAX_VERTEX_UNIFORM_VECTORS": webglCtx.getParameter(webglCtx.MAX_VERTEX_UNIFORM_VECTORS),
"ALIASED_LINE_WIDTH_RANGE": webglCtx.getParameter(webglCtx.ALIASED_LINE_WIDTH_RANGE),
"ALIASED_POINT_SIZE_RANGE": webglCtx.getParameter(webglCtx.ALIASED_POINT_SIZE_RANGE),
"MAX_VIEWPORT_DIMS": webglCtx.getParameter(webglCtx.MAX_VIEWPORT_DIMS),
//t.getParameter(n.MAX_DRAW_BUFFERS_WEBGL) // 手机也返回null
"anisotropicExt": anisotropicExt,
"maxVertexTextureImageUnits": maxVertexTextureImageUnits,
"MAX_VERTEX_TEXTURE_IMAGE_UNITS": webglCtx.getParameter(webglCtx.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
"fragmentShaderBestPrecision": fragmentShaderBestPrecision,
"DEPTH_BITS": webglCtx.getParameter(webglCtx.DEPTH_BITS),
"STENCIL_BITS": webglCtx.getParameter(webglCtx.STENCIL_BITS),
"getSupportedExtensions": webglCtx.getSupportedExtensions(),
"fragmentShaderFloatIntPrecision": fragmentShaderFloatIntPrecision
};
}