由于这个问题与硒有关,因此跨浏览器的覆盖解决方案navigator.webdriver
很有用。这可以通过在目标页面的任何 JS 运行之前修补浏览器环境来完成,但不幸的是,除了 chromium 之外,没有其他浏览器允许在文档加载后和任何其他 JS 运行之前评估任意 JavaScript 代码(firefox 与Remote Protocol接近)。
在修补之前,我们需要检查默认浏览器环境的外观。在更改属性之前,我们可以看到它的默认定义Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptor(navigator, 'webdriver');
// undefined
所以通过这个快速测试,我们可以看到webdriver
属性没有在navigator
. 它实际上定义在Navigator.prototype
:
Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver');
// {set: undefined, enumerable: true, configurable: true, get: ƒ}
更改拥有它的对象的属性非常重要,否则可能会发生以下情况:
navigator.webdriver; // true if webdriver controlled, false otherwise
// this lazy patch is commonly found on the internet, it does not even set the right value
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
navigator.webdriver; // undefined
Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get.apply(navigator);
// true
一个不那么天真的补丁会首先针对正确的对象并使用正确的属性定义,但深入挖掘我们会发现更多的不一致:
const defaultGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
defaultGetter.toString();
// "function get webdriver() { [native code] }"
Object.defineProperty(Navigator.prototype, 'webdriver', {
set: undefined,
enumerable: true,
configurable: true,
get: () => false
});
const patchedGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
patchedGetter.toString();
// "() => false"
一个完美的补丁不会留下任何痕迹,而不是替换 getter 函数,如果我们可以拦截对它的调用并更改返回值会很好。JavaScript 通过代理apply
处理程序具有本机支持:
const defaultGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
defaultGetter.apply(navigator); // true
defaultGetter.toString();
// "function get webdriver() { [native code] }"
Object.defineProperty(Navigator.prototype, 'webdriver', {
set: undefined,
enumerable: true,
configurable: true,
get: new Proxy(defaultGetter, { apply: (target, thisArg, args) => {
// emulate getter call validation
Reflect.apply(target, thisArg, args);
return false;
}})
});
const patchedGetter = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get;
patchedGetter.apply(navigator); // false
patchedGetter.toString();
// "function () { [native code] }"
现在唯一的不一致是函数名,不幸的是,无法覆盖以本机toString()
表示形式显示的函数名。{ [native code] }
但即便如此,它也可以传递通用正则表达式,通过查找字符串表示的末尾来搜索欺骗的浏览器本机函数。要消除这种不一致,您可以修补Function.prototype.toString
并使其为您修补的所有本机函数返回有效的本机字符串表示形式。
总而言之,在 selenium 中,它可以应用于:
chrome.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {'source': """
Object.defineProperty(Navigator.prototype, 'webdriver', {
set: undefined,
enumerable: true,
configurable: true,
get: new Proxy(
Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver').get,
{ apply: (target, thisArg, args) => {
// emulate getter call validation
Reflect.apply(target, thisArg, args);
return false;
}}
)
});
"""})
playwright项目维护了一个 Firefox 和 WebKit 的fork以添加浏览器自动化功能,其中一个相当于Page.addScriptToEvaluateOnNewDocument
,但没有针对 Python 的通信协议的实现,但可以从头开始实现。