78

我正在尝试使用 selenium 和 chrome 在网站中自动执行一项非常基本的任务,但是网站以某种方式检测到 chrome 何时由 selenium 驱动并阻止每个请求。我怀疑该网站依赖于暴露的 DOM 变量,例如https://stackoverflow.com/a/41904453/648236来检测硒驱动的浏览器。

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

任何帮助深表感谢

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

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

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

4

15 回答 15

103

首先更新1

execute_cdp_cmd():现在有了可用的命令,您可以使用Seleniumexecute_cdp_cmd(cmd, cmd_args)轻松执行 命令。使用此功能,您可以轻松修改以防止 Selenium 被检测到。navigator.webdriver


防止检测 2

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

  • 添加参数--disable-blink-features=AutomationControlled

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

您可以在Selenium can't open a second page中找到相关的详细讨论

  • 通过命令轮换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'})
    
  • 将for webdriver的属性值更改为undefinednavigator

    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:\WebDrivers\chromedriver.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 truefalse

更远,

Navigator includes NavigatorAutomationInformation;

需要注意的是:

NavigatorAutomationInformation 接口不应在WorkerNavigator上公开。

NavigatorAutomationInformation 接口定义为:

interface mixin NavigatorAutomationInformation {
    readonly attribute boolean webdriver;
};

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

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

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


更新(2019 年 11 月 6 日)

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

  • enable-automation排除开关集合
  • 关掉useAutomationExtension

通过ChromeOptions如下实例:

  • Java 示例:

    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/");
    
  • Python 示例

    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:\path\to\chromedriver.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 客户端。

于 2018-10-29T07:44:19.980 回答
70

铬驱动程序

终于用一个简单的标志找到了这个简单的解决方案!:)

--disable-blink-features=AutomationControlled

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

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

于 2020-02-25T21:46:51.570 回答
27

不要使用 cdp 命令更改 webdriver 的值,因为它会导致不一致,以后可以使用它来检测 webdriver。使用下面的代码,这将删除 webdriver 的任何痕迹。

options.add_argument("--disable-blink-features")
options.add_argument("--disable-blink-features=AutomationControlled")
于 2020-05-06T08:21:46.290 回答
23

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

> 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-06-17T16:09:24.537 回答
17

自 2020 年 4 月起,排除 2019 年 11 月 6 日更新中提到的启用自动化开关集合不再起作用。相反,我收到以下错误:

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

以下是截至 2020 年 4 月 6 日 Chrome 80 的运行情况。

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

> navigator.webdriver
true

Python 示例:

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

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

> navigator.webdriver
undefined
于 2020-04-06T17:04:06.550 回答
15

现在,您可以使用 cdp 命令完成此操作:

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

driver.get(some_url)

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

于 2019-12-17T05:08:26.200 回答
7

最后这解决了 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);
于 2020-02-26T02:35:34.953 回答
4

python的简单破解:

options = webdriver.ChromeOptions()    
options.add_argument("--disable-blink-features=AutomationControlled")
于 2021-02-04T11:37:29.067 回答
3

由于这个问题与硒有关,因此跨浏览器的覆盖解决方案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 的通信协议的实现,但可以从头开始实现。

于 2021-10-11T23:38:55.300 回答
2

如上述评论中所述 - https://stackoverflow.com/a/60403652/2923098以下选项对我完全有效在Java中) -

ChromeOptions options = new ChromeOptions();
options.addArguments("--incognito", "--disable-blink-features=AutomationControlled");
于 2020-10-17T12:13:06.397 回答
2

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

于 2020-12-08T11:04:50.573 回答
1

我想为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。

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

于 2020-02-01T22:14:24.537 回答
0

如果您使用远程 Webdriver,下面的代码将设置navigator.webdriverundefined.

适用于ChromeDriver 81.0.4044.122

Python 示例:

    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)
于 2020-04-23T07:55:29.630 回答
0

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))

有点慢,但我会在不工作时慢慢来。

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

于 2021-11-22T16:17:42.153 回答
0

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

于 2021-03-05T14:29:19.103 回答