由于这个问题与 selenium 有关,因此覆盖 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 通过Proxy apply handler 提供了原生支持:
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] }"
现在唯一的不一致是函数名,不幸的是no way 覆盖了原生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 的通信协议实现,但可以从头开始实现.