Selenium webdriver:修改 navigator.webdriver 标志以防止硒检测



我正在尝试使用硒和铬在网站上自动执行一项非常基本的任务,但不知何故,该网站会检测到铬何时由硒驱动并阻止每个请求。我怀疑该网站依赖于像这样暴露的 DOM 变量 https://stackoverflow.com/a/41904453/648236 来检测硒驱动的浏览器。

我的问题是,有没有办法使navigator.webdriver标志为假?我愿意在修改后尝试重新编译硒源,但我似乎无法在存储库中的任何位置找到 NavigatorAutomationInformation 源 https://github.com/SeleniumHQ/selenium

任何帮助都非常感谢

PS:我也尝试了以下 https://w3c.github.io/webdriver/#interface

Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});

但它仅在初始页面加载后更新属性。我认为该站点在我的脚本执行之前检测到变量。

首先更新1

execute_cdp_cmd(): 随着execute_cdp_cmd(cmd, cmd_args)命令的可用性,现在您可以使用Selenium轻松执行google-chrome-devtools命令。使用此功能,您可以轻松修改navigator.webdriver以防止检测到硒。

<小时 />

预防检测2

为了防止检测到Selenium驱动的WebDriver,利基方法将包括以下所有步骤:

  • 添加参数 --禁用闪烁功能=自动化控制

    from selenium import webdriver
    options = webdriver.ChromeOptions() 
    options.add_argument('--disable-blink-features=AutomationControlled')
    driver = webdriver.Chrome(options=options, executable_path=r'C:WebDriverschromedriver.exe')
    driver.get("https://www.website.com")
    

您可以在Selenium无法打开第二页中找到相关的详细讨论

  • 通过execute_cdp_cmd()命令轮换用户代理,如下所示:

    #Setting up Chrome/83.0.4103.53 as useragent
    driver.execute_cdp_cmd('Network.setUserAgentOverride', {"userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.53 Safari/537.36'})
    
  • Web 驱动程序navigator的属性值更改为未定义

    driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
    
  • 排除enable-automation开关的集合

    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    
  • 关闭useAutomationExtension

    options.add_experimental_option('useAutomationExtension', False)
    

示例代码3

将上述所有步骤和有效的代码块结合起来将是:

from selenium import webdriver
options = webdriver.ChromeOptions() 
options.add_argument("start-maximized")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=options, executable_path=r'C:WebDriverschromedriver.exe')
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
driver.execute_cdp_cmd('Network.setUserAgentOverride', {"userAgent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.53 Safari/537.36'})
print(driver.execute_script("return navigator.userAgent;"))
driver.get('https://www.httpbin.org/headers')
<小时 />

历史

根据W3C 编辑草案,当前的实现严格提及:

当用户代理处于远程控制下时,webdriver-active标志设置为true,该控制最初设置为false

进一步

Navigator includes NavigatorAutomationInformation;

需要注意的是:

NavigatorAutomationInformation接口不应在WorkerNavigator上公开。

NavigatorAutomationInformation接口定义为:

interface mixin NavigatorAutomationInformation {
readonly attribute boolean webdriver;
};

如果设置了webdriver-active标志,则返回true,否则返回 false。

最后,navigator.webdriver定义了协作用户代理通知文档它由WebDriver控制的标准方法,以便在自动化期间可以触发备用代码路径。

注意:更改/调整上述参数可能会阻止导航并检测到WebDriver实例。


更新(2019 年 11 月 6 日)

在当前的实现中,访问网页而不被检测到的理想方法是使用ChromeOptions()类添加几个参数:

  • 排除enable-automation开关的集合
  • 关闭useAutomationExtension

通过ChromeOptions实例,如下所示:

  • 爪哇示例:

    System.setProperty("webdriver.chrome.driver", "C:\Utility\BrowserDrivers\chromedriver.exe");
    ChromeOptions options = new ChromeOptions();
    options.setExperimentalOption("excludeSwitches", Collections.singletonList("enable-automation"));
    options.setExperimentalOption("useAutomationExtension", false);
    WebDriver driver =  new ChromeDriver(options);
    driver.get("https://www.google.com/");
    
  • 蟒蛇示例

    from selenium import webdriver
    options = webdriver.ChromeOptions()
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    driver = webdriver.Chrome(options=options, executable_path=r'C:pathtochromedriver.exe')
    driver.get("https://www.google.com/")
    
  • 拼音示例

    options = Selenium::WebDriver::Chrome::Options.new
    options.add_argument("--disable-blink-features=AutomationControlled")
    driver = Selenium::WebDriver.for :chrome, options: options
    
<小时 />

传奇

1:仅适用于Selenium的Python客户端。

2:仅适用于Selenium的Python客户端。

3:仅适用于Selenium的Python客户端。

ChromeDriver

终于发现了一个简单的标志的简单解决方案! :)

--disable-blink-features=AutomationControlled

navigator.webdriver=true将不再显示设置该标志。

有关您可以禁用的功能列表,请在此处查看

不要使用 cdp 命令更改 Web 驱动程序值,因为它会导致不一致,稍后可用于检测 Webdriver。使用以下代码,这将删除 Web 驱动程序的任何痕迹。

options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")

之前(在浏览器控制台窗口中):

> navigator.webdriver
true

变化(以硒为单位):

// C#
var options = new ChromeOptions();
options.AddExcludedArguments(new List<string>() { "enable-automation" });
// Python
options.add_experimental_option("excludeSwitches", ['enable-automation'])

之后(在浏览器控制台窗口中):

> navigator.webdriver
undefined

这不适用于ChromeDriver 79.0.3945.16及更高版本。在此处查看发行说明

排除 2019 年 11 月 6 日更新中提到的启用自动化开关集合,截至 2020 年 4 月,投票最高的答案不再有效。相反,我收到以下错误:

ERROR:broker_win.cc(55)] Error reading broker pipe: The pipe has been ended. (0x6D)

以下是截至 2020 年 4 月 6 日使用 Chrome 80 的工作情况。

之前(在 Chrome 控制台窗口中):

> navigator.webdriver
true

蟒蛇示例:

options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")

之后(在 Chrome 控制台窗口中):

> navigator.webdriver
undefined

现在你可以用cdp命令来完成这个:

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
driver.get(some_url)

顺便说一句,你想undefined返回,false是一个死赠品。

由于这个问题与硒有关,因此覆盖navigator.webdriver的跨浏览器解决方案很有用。这可以通过在目标页面的任何JS运行之前修补浏览器环境来完成,但不幸的是,除了chromium之外,没有其他浏览器允许在文档加载后和任何其他JS运行之前评估任意JavaScript代码(Firefox与远程协议接近)。

在修补之前,我们需要检查默认浏览器环境的外观。在更改属性之前,我们可以看到它的默认定义带有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并使其为您修补的所有本机函数返回有效的本机字符串表示形式。

综上所述,在硒中它可以应用于:

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

剧作家项目维护了 Firefox 和 WebKit 的一个分支来添加浏览器自动化的功能,其中一个相当于Page.addScriptToEvaluateOnNewDocument,但没有通信协议的 Python 实现,但可以从头开始实现。

最后,这解决了ChromeDriver的问题,Chrome大于v79。

ChromeOptions options = new ChromeOptions();
options.addArguments("--disable-blink-features");
options.addArguments("--disable-blink-features=AutomationControlled");
ChromeDriver driver = new ChromeDriver(options);
Map<String, Object> params = new HashMap<String, Object>();
params.put("source", "Object.defineProperty(navigator, 'webdriver', { get: () => undefined })");
driver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", params);

python的简单黑客:

options = webdriver.ChromeOptions()    
options.add_argument("--disable-blink-features=AutomationControlled")

如上述评论所述 - https://stackoverflow.com/a/60403652/2923098 以下选项完全适用于我(在 Java 中)-

ChromeOptions options = new ChromeOptions();
options.addArguments("--incognito", "--disable-blink-features=AutomationControlled");

Python

我尝试了这篇文章中提到的大部分内容,但我仍然面临问题。 现在拯救我的是 https://pypi.org/project/undetected-chromedriver

pip install undetected-chromedriver

import undetected_chromedriver.v2 as uc
from time import sleep
from random import randint

driver = uc.Chrome()
driver.get('www.your_url.here')
driver.maximize_window() 
sleep(randint(3,9))

有点慢,但我会慢一点,而不是不工作。

我想如果每个感兴趣的人都可以查看源代码,看看是什么提供了胜利。

我想在pguardiario提到的 cdp 命令方法中添加一个 Java 替代方案

Map<String, Object> params = new HashMap<String, Object>();
params.put("source", "Object.defineProperty(navigator, 'webdriver', { get: () => undefined })");
driver.executeCdpCommand("Page.addScriptToEvaluateOnNewDocument", params);

为了使其正常工作,您需要使用org.openqa.selenium.chromium.ChromiumDriver软件包中的ChromiumDriver。据我所知,该软件包不包含在Selenium 3.141.59中,所以我使用了Selenium 4 alpha。

此外,ExcludeSwitch/useAutomationExtension 实验选项似乎不再适用于 ChromeDriver 79 和 Chrome 79。

对于那些尝试过这些技巧的人,请确保还检查您正在使用的用户代理是否与您的爬虫要模拟的平台(移动/桌面/平板电脑)相对应。我花了一段时间才意识到这是我的致命弱点;)

如果您使用远程 Web 驱动程序,下面的代码会将navigator.webdriver设置为undefined

适用于ChromeDriver 81.0.4044.122

蟒蛇示例:

options = webdriver.ChromeOptions()
# options.add_argument("--headless")
options.add_argument('--disable-gpu')
options.add_argument('--no-sandbox')
driver = webdriver.Remote(
'localhost:9515', desired_capabilities=options.to_capabilities())
script = '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
driver.execute_script(script)

使用--disable-blink-features=AutomationControlled禁用navigator.webdriver

最新更新