Selenium无头浏览器:如何使用Selenium绕过Cloudflare检测

33
希望专家能帮我解决一个Selenium/Cloudflare的难题。在普通的(非无头)Selenium中可以加载网站,但是无论我尝试什么,都无法在无头模式下加载它。我已经按照StackOverflow文章中的建议如有没有不可检测的 Selenium WebDriver 版本?等进行了操作。我还查看了windowwindow.navigator对象的所有属性,并修复了无头和非无头之间的所有差异,但是无头模式仍然被检测到。现在,我非常好奇 Cloudflare 如何可能发现这种差异。谢谢您的时间!
from selenium import webdriver
import time

# Replace this with the path to your html file
FULL_PATH_TO_HTML_FILE = 'file:///Users/simplepineapple/html/url_page.html'

def visit_website(browser):
    browser.get(FULL_PATH_TO_HTML_FILE)
    time.sleep(3)

    links = browser.find_elements_by_xpath("//a[@href]")
    links[0].click()
    time.sleep(10)

    # Switch webdriver focus to new tab so that we can extract html
    tab_names = browser.window_handles
    if len(tab_names) > 1:
        browser.switch_to.window(tab_names[1])

    time.sleep(1)
    html = browser.page_source
    print(html)
    print()
    print()

    if 'Charts' in html:
        print('Success')
    else:
        print('Fail')

    time.sleep(10)


options = webdriver.ChromeOptions()
# If options.headless = True, the website will not load
options.headless = False
options.add_argument("--window-size=1920,1080")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_argument('user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36')

browser = webdriver.Chrome(options = options)

browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
    "source": '''
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined
    });
    Object.defineProperty(navigator, 'plugins', {
            get: function() { return {"0":{"0":{}},"1":{"0":{}},"2":{"0":{},"1":{}}}; }
    });
    Object.defineProperty(navigator, 'languages', {
        get: () => ["en-US", "en"]
    });
    Object.defineProperty(navigator, 'mimeTypes', {
        get: function() { return {"0":{},"1":{},"2":{},"3":{}}; }
    });

    window.screenY=23;
    window.screenTop=23;
    window.outerWidth=1337;
    window.outerHeight=825;
    window.chrome =
    {
      app: {
        isInstalled: false,
      },
      webstore: {
        onInstallStageChanged: {},
        onDownloadProgress: {},
      },
      runtime: {
        PlatformOs: {
          MAC: 'mac',
          WIN: 'win',
          ANDROID: 'android',
          CROS: 'cros',
          LINUX: 'linux',
          OPENBSD: 'openbsd',
        },
        PlatformArch: {
          ARM: 'arm',
          X86_32: 'x86-32',
          X86_64: 'x86-64',
        },
        PlatformNaclArch: {
          ARM: 'arm',
          X86_32: 'x86-32',
          X86_64: 'x86-64',
        },
        RequestUpdateCheckStatus: {
          THROTTLED: 'throttled',
          NO_UPDATE: 'no_update',
          UPDATE_AVAILABLE: 'update_available',
        },
        OnInstalledReason: {
          INSTALL: 'install',
          UPDATE: 'update',
          CHROME_UPDATE: 'chrome_update',
          SHARED_MODULE_UPDATE: 'shared_module_update',
        },
        OnRestartRequiredReason: {
          APP_UPDATE: 'app_update',
          OS_UPDATE: 'os_update',
          PERIODIC: 'periodic',
        },
      },
    };
    window.navigator.chrome =
    {
      app: {
        isInstalled: false,
      },
      webstore: {
        onInstallStageChanged: {},
        onDownloadProgress: {},
      },
      runtime: {
        PlatformOs: {
          MAC: 'mac',
          WIN: 'win',
          ANDROID: 'android',
          CROS: 'cros',
          LINUX: 'linux',
          OPENBSD: 'openbsd',
        },
        PlatformArch: {
          ARM: 'arm',
          X86_32: 'x86-32',
          X86_64: 'x86-64',
        },
        PlatformNaclArch: {
          ARM: 'arm',
          X86_32: 'x86-32',
          X86_64: 'x86-64',
        },
        RequestUpdateCheckStatus: {
          THROTTLED: 'throttled',
          NO_UPDATE: 'no_update',
          UPDATE_AVAILABLE: 'update_available',
        },
        OnInstalledReason: {
          INSTALL: 'install',
          UPDATE: 'update',
          CHROME_UPDATE: 'chrome_update',
          SHARED_MODULE_UPDATE: 'shared_module_update',
        },
        OnRestartRequiredReason: {
          APP_UPDATE: 'app_update',
          OS_UPDATE: 'os_update',
          PERIODIC: 'periodic',
        },
      },
    };
    ['height', 'width'].forEach(property => {
        const imageDescriptor = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, property);

        // redefine the property with a patched descriptor
        Object.defineProperty(HTMLImageElement.prototype, property, {
            ...imageDescriptor,
            get: function() {
                // return an arbitrary non-zero dimension if the image failed to load
            if (this.complete && this.naturalHeight == 0) {
                return 20;
            }
                return imageDescriptor.get.apply(this);
            },
        });
    });

    const getParameter = WebGLRenderingContext.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (parameter === 37445) {
            return 'Intel Open Source Technology Center';
        }
        if (parameter === 37446) {
            return 'Mesa DRI Intel(R) Ivybridge Mobile ';
        }

        return getParameter(parameter);
    };

    const elementDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight');

    Object.defineProperty(HTMLDivElement.prototype, 'offsetHeight', {
        ...elementDescriptor,
        get: function() {
            if (this.id === 'modernizr') {
            return 1;
            }
            return elementDescriptor.get.apply(this);
        },
    });
    '''
})

visit_website(browser)

browser.quit()

你是在说“我正在遭受攻击模式”吗?那将运行一些JS测试,你将无法欺骗(例如计时绘制画布上的东西)。 - pguardiario
感谢您详细描述如何在非无头模式下使事情正常工作。我已经重现了您的实验,并获得了完全相同的行为。我没有回答您的问题,但也许您和我一样可以使用一些虚拟帧缓冲设备来模拟非无头模式。对我来说,Xvnc起作用了,我使用它是因为我想有机会观察这个过程。也许您可以使用更轻量级的Xvfb。 - abb
6个回答

31

如果您使用最新版本的Google Chrome v96.0,并获取用户代理,则以下浏览器的正在使用:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36
  • 而对于,使用以下

  • Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/96.0.4664.110 Safari/537.36
    

    在大多数情况下,存在附加的Headless字符串/参数/属性被拦截为,并且会阻止访问该网站。


    解决方案

    有不同的方法来规避Cloudflare检测,即使使用Chrome模式下,其中一些有效的方法如下:

    • 一个有效的解决方案是使用undetected-chromedriver来初始化Chrome Browsing Contextundetected-chromedriver是一个经过优化的Selenium Chromedriver补丁,不会触发像Distill Network/Imperva/DataDome/Botprotect.io这样的反机器人服务。它会自动下载驱动程序二进制文件并对其进行修补。

      • 代码块:

    import undetected_chromedriver as uc
    from selenium import webdriver
    
    options = webdriver.ChromeOptions() 
    options.headless = True
    options.add_argument("start-maximized")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    driver = uc.Chrome(options=options)
    driver.get('https://bet365.com')
    

    你可以在以下几个讨论中找到一些相关的详细讨论:

    • 最有效的解决方案是使用Selenium Stealth来初始化Chrome浏览上下文。selenium-stealth是一个Python包,用于防止检测。此程序试图使Python Selenium更加隐蔽。

      • 代码块:

    from selenium import webdriver
    from selenium_stealth import stealth
    
    options = webdriver.ChromeOptions()
    options.add_argument("start-maximized")
    options.add_argument("--headless")
    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")
    
    stealth(driver,
            languages=["en-US", "en"],
            vendor="Google Inc.",
            platform="Win32",
            webgl_vendor="Intel Inc.",
            renderer="Intel Iris OpenGL Engine",
            fix_hairline=True,
            )
    
    driver.get("https://bot.sannysoft.com/")
    

    您可以在以下讨论中找到一些相关详细的内容:


    谢谢,似乎Cloudflare检测到了无界面chrome并且在我的情况下标记了该站点,我已经更改了用户代理,尽管我更喜欢使用默认的用户代理。 - Richard Muvirimi
    对我来说,undetected_chromedriver起了作用。我不需要你提到的任何选项,比如'excludeSwitches'和'useAutomaticExtension'。selenium_stealth对我没有起作用。 - undefined

    1

    @undetected Selenium的答案与https://github.com/diprajpatra/selenium-stealth完美配合使用。

    如果您正在使用最新版本的selenium,您需要更改executable_path参数,因为它已被弃用。以下是示例代码:

    from selenium import webdriver
    from selenium.webdriver.chrome.service import Service
    from webdriver_manager.chrome import ChromeDriverManager
    from selenium.webdriver.common.by import By
    from selenium_stealth import stealth
    
    options = webdriver.ChromeOptions()
    options.add_argument("start-maximized")
    options.add_argument("--headless")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    
    s=Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=s, options=options)
    
    stealth(driver,
            languages=["en-US", "en"],
            vendor="Google Inc.",
            platform="Win32",
            webgl_vendor="Intel Inc.",
            renderer="Intel Iris OpenGL Engine",
            fix_hairline=True,
    )
    
    driver.get("https://bot.sannysoft.com/")
    
    print(driver.find_element(By.XPATH, "/html/body").text)
    
    driver.close()
    

    0
    你需要阅读最新版本的Chromium源代码。它在无头模式下删除了大量功能。Cloudflare的开发人员在做什么呢?他们正在寻找使用了“无头模式”的地方,并试图将无头和非无头对象的行为分离。目前在Chromium中有许多变通方法,使内部无头模式的检测变得容易。
    与此同时,我无法理解那些使用内部Chromium无头模式的人。你可以使用无头Wayland或无头X11模式,忘记这个问题。这将有助于集中精力处理更重要的事情。

    0
    我能提供的唯一建议是 - 改进您的插件和 MIME 类型,有时可以使用 typeof(navigator.plugins, 'PluginsArray') 属性来操作导航器。
    Object.defineProperty(navigator, 'plugins', {
        get: () => {
            var ChromiumPDFPlugin = {};
            var plugin = {
                ChromiumPDFPlugin,
                description: 'Portable Document Format',
                filename: 'internal-pdf-viewer',
                length: 1,
                name: 'Chromium PDF Plugin',
    
            };
            plugin.__proto__ = Plugin.prototype;
    
            var plugins = {
                0: plugin,
                length: 1
            };
            plugins.__proto__ = PluginArray.prototype;
            return plugins;
        },
    });
    
    Object.defineProperty(navigator, 'mimeTypes', {
        get: () => {
            var mimeType = {
                type: 'application/pdf',
                suffixes: 'pdf',
                description: 'Portable Document Format',
                enabledPlugin: Plugin
    
            };
            mimeType.__proto__ = MimeType.prototype;
    
            var mimeTypes = {
                0: mimeType,
                length: 1
            };
            mimeTypes.__proto__ = MimeTypeArray.prototype;
            return mimeTypes;
        },
    });
    

    检查无头模式出现问题的好网站是https://bot.sannysoft.com/

    您可以在无头模式下运行并创建页面快照,以检查是否全部通过

    P.S. 有时即使将navigator.webdriver设置为undefined,navigator仍然包含webdriver属性,您可以使用以下代码简单地删除:

    const newProto = navigator.__proto__;
    delete newProto.webdriver;
    navigator.__proto__ = newProto;
    

    -1

    Cloudflare保护IUAM主要用于避免DDoS攻击,因此它还可以保护网站免受自动化机器人的利用,所以无论您在客户端使用什么,Cloudflare服务器都会对您进行指纹识别。 之后,他们向客户端发送cf_clearance cookie,该cookie允许您在接下来的15分钟内连接。

    enter image description here


    我注意到cf_clearance cookie在验证后用于绕过CAPTCHA,但即使我在我的WebDriver脚本中重复使用这个cookie,它仍然要求我完成CAPTCHA,而在没有WebDriver的情况下,在Firefox中它仍然是一个有效的cookie。用户代理是相同的,所以他们可能在检查其他东西,也许是navigator.webdriver JavaScript变量? - baptx

    -2

    使用以下命令进行安装:pip install undetected-chromedriver

    您可以使用此模块。


    1
    你的回答可以通过提供更多支持信息来改进。请编辑以添加更多细节,例如引用或文档,以便他人可以确认您的回答是正确的。您可以在帮助中心中找到有关如何编写好答案的更多信息。 - Community
    一年半之前的最佳答案已经建议安装undetected-chromedriver。请不要重复回答。 - ChrisGPT was on strike

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接