2023年Selenium绕过网站检测的实战指南:从指纹伪装到行为模拟

发布时间:2026/7/1 23:33:36
2023年Selenium绕过网站检测的实战指南:从指纹伪装到行为模拟 1. 项目概述为什么Selenium绕过检测在2023年依然是刚需如果你正在用Selenium做自动化测试或者数据采集大概率遇到过这个头疼的问题脚本运行得好好的突然就被目标网站识别并拦截了。页面弹出一个验证码或者直接返回一个空白页告诉你“检测到自动化工具”。这感觉就像你精心训练的士兵刚上战场就被对方的雷达锁定了。我最近在做一个电商价格监控的项目就频繁踩进这个坑里网站的反爬策略几乎每周都在更新。所以今天我们不聊基础的Selenium怎么用那些教程满大街都是。我们深入聊聊2023年那些真正能“隐身”、让Selenium脚本像真人一样浏览网页的最新方法和底层原理。简单说这个内容就是一份针对Selenium被网站检测问题的“反侦察手册”。它适合所有被此问题困扰的开发者无论是做自动化测试的QA工程师还是需要稳定采集数据的爬虫工程师。核心目标就一个让你的Selenium驱动浏览器时留下的“机器指纹”降到最低从而绕过大多数基于客户端JavaScript的检测。我们会从环境配置的细节开始一直深入到修改浏览器驱动WebDriver的底层参数把整个链条上的风险点都覆盖到。2. 核心思路拆解网站是如何发现你是Selenium的在动手之前我们必须先搞清楚“敌人”的侦察手段。网站检测Selenium主要不是看你的IP也不是看你的请求频率那是服务器端风控的事而是在你的浏览器环境里找“破绽”。这些破绽就是非人类浏览的痕迹我们称之为“指纹”。2.1 常见的检测指纹点根据我最近的测试和社区讨论2023年常见的检测点主要集中在以下几个方面WebDriver属性这是最经典的检测点。通过navigator.webdriver这个属性在普通浏览器里它是undefined或false但在Selenium控制的浏览器里它会被设置为true。很多检测脚本第一行就是查这个。浏览器特征与插件Selenium启动的浏览器其user-agent虽然可以改但一些只读属性如navigator.plugins插件列表、navigator.languages可能和真人环境有细微差别。特别是无头Headless模式会缺失很多属性。窗口属性与屏幕分辨率window.outerWidth和window.outerHeight在自动化脚本中可能被设置为非标准值或者与screen对象的信息不匹配。JavaScript执行痕迹一些底层的JavaScript对象和方法在自动化环境下其行为或属性可能与真实浏览器不同。例如某些浏览器原生函数如toString()的返回值可能包含“WebDriver”字样。Chrome DevTools Protocol (CDP) 痕迹当使用Selenium 4及以上版本时其底层通信会通过CDP。虽然功能强大但也会留下一些可以被检测的痕迹。行为模式这属于高级检测比如鼠标移动轨迹是否过于线性、点击速度是否恒定不变、页面停留时间是否过于规律等。我们今天主要解决前五点的环境指纹问题。理解了这些我们的绕过策略就有了明确的目标不是“打败”检测而是“伪装”成一个毫无破绽的普通浏览器。3. 环境配置从源头减少指纹泄露很多人一上来就急着改代码其实环境配置是基础基础不牢后面的技巧效果会大打折扣。3.1 浏览器选择与启动参数优化不要使用浏览器自带的、为测试准备的二进制文件如很多教程里直接调用的chrome。最好下载一个独立的、完整的Chrome或Firefox安装包进行安装。启动时通过Selenium的Options添加启动参数来禁用一些自动化特征和不需要的功能。以Chrome为例使用selenium 4.x的写法from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options Options() # 核心禁用“正受到自动测试软件控制”的提示栏并尝试隐藏webdriver属性 chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 重要使用常规的用户数据目录避免使用临时/干净的profile这会让浏览器看起来更“真实” # chrome_options.add_argument(r--user-data-dirC:\Users\YourName\AppData\Local\Google\Chrome\User Data) # chrome_options.add_argument(--profile-directoryDefault) # 其他优化参数减少特征 chrome_options.add_argument(--no-sandbox) # 仅限Linux环境Windows可省略 chrome_options.add_argument(--disable-dev-shm-usage) chrome_options.add_argument(--disable-gpu) # 无头模式下有时需要 chrome_options.add_argument(--disable-extensions) # 禁用扩展避免干扰但某些场景下保留扩展更真实 chrome_options.add_argument(--disable-popup-blocking) # 如果要启用无头模式建议使用较新的--headlessnew # chrome_options.add_argument(--headlessnew) # 初始化驱动 driver webdriver.Chrome(optionschrome_options) # 执行CDP命令覆盖navigator.webdriver等属性Selenium 4推荐方式 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); Object.defineProperty(navigator, plugins, { get: () [1, 2, 3, 4, 5] }); Object.defineProperty(navigator, languages, { get: () [zh-CN, zh, en] }); })注意--disable-blink-featuresAutomationControlled这个参数是近两年比较关键的一个它直接告诉Blink引擎Chrome内核不要添加自动化控制的相关特征。excludeSwitches和useAutomationExtension也是配套使用的经典组合。3.2 用户代理User-Agent与语言环境设置一个固定的、非常见的User-Agent本身就是个指纹。最好能随机或使用一个常见的、更新版本的UA。同时设置合理的语言和时区。import random # 准备一个常见UA列表 user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, ] selected_ua random.choice(user_agents) chrome_options.add_argument(f--user-agent{selected_ua}) # 通过CDP设置更彻底的locale和timezone driver.execute_cdp_cmd(Emulation.setTimezoneOverride, {timezoneId: Asia/Shanghai}) driver.execute_cdp_cmd(Emulation.setLocaleOverride, {locale: zh-CN})3.3 关于无头Headless模式的特别说明无头模式因为不渲染图形界面缺失了很多WebGL、字体等属性非常容易被检测。在2023年如果你必须用无头模式请务必使用Chrome 109版本支持的--headlessnew模式它比旧的--headless模式更接近真实浏览器。但即便如此对于反爬严格的网站仍建议优先使用非无头模式并配合虚拟显示框架如Linux下的Xvfb来隐藏窗口。4. 驱动修改与CDP深度利用隐藏核心指纹环境配置是“化妆”那么修改驱动和利用CDPChrome DevTools Protocol就是“整容”直接从底层修改浏览器行为。这是绕过高级检测的关键。4.1 使用undetected-chromedriver(uc)这是一个在社区中广受好评的第三方库它本质上是对官方ChromeDriver的一个包装自动集成了许多绕过技巧包括自动下载匹配的驱动、修补webdriver属性等。对于不想深究细节的开发者这是最快见效的方案。pip install undetected-chromedriverimport undetected_chromedriver as uc driver uc.Chrome(headlessFalse, use_subprocessTrue) driver.get(https://nowsecure.nl) # 一个著名的反爬测试网站uc库在内部做了大量工作比如在页面加载前注入JavaScript来覆盖指纹修改CDP返回值等。它的use_subprocessTrue参数能进一步降低被关联的风险。但它的缺点是不够透明如果它失效了排查起来比较困难。4.2 手动深度CDP注入对于追求极致控制或uc库失效的场景我们需要手动通过CDP进行深度注入。Selenium 4提供了driver.execute_cdp_cmd方法让我们能在页面加载前执行任意JavaScript代码这是修改只读属性的唯一可靠方法。上面我们已经演示了覆盖navigator.webdriver。但还不够一些更隐蔽的检测会检查window.chrome对象、document对象的属性甚至Notification.permission等。下面是一个更全面的CDP注入脚本示例可以在创建驱动后、访问任何页面之前执行def inject_stealth_js(driver): stealth_script // 1. 覆盖webdriver属性 Object.defineProperty(navigator, webdriver, { get: () undefined, configurable: true }); // 2. 覆盖plugins和languages使其看起来更丰富 Object.defineProperty(navigator, plugins, { get: () [{ 0: {type: application/x-google-chrome-pdf, description: Portable Document Format}, 1: {type: application/pdf, description: Portable Document Format} }], configurable: true }); Object.defineProperty(navigator, languages, { get: () [zh-CN, zh, en-US, en], configurable: true }); // 3. 模拟Chrome runtime仅限Chrome浏览器环境 window.chrome { runtime: {}, loadTimes: function(){}, csi: function(){}, app: {} }; // 4. 覆盖permissions API的查询结果使其看起来像用户已授权 const originalQuery window.navigator.permissions.query; window.navigator.permissions.query (parameters) ( parameters.name notifications ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) ); // 5. 修改WebGL Vendor和Renderer高级检测点 const getParameter WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter function(parameter) { if (parameter 37445) { // UNMASKED_VENDOR_WEBGL return Intel Inc.; } if (parameter 37446) { // UNMASKED_RENDERER_WEBGL return Intel Iris OpenGL Engine; } return getParameter(parameter); }; // 6. 覆盖console.debug防止某些检测脚本通过console.log输出标记 console.debug () {}; // 7. 删除“$cdc_”等Selenium内部对象标记旧版方法现代检测已不常见但可做 Object.defineProperty(document, hidden, { value: false }); driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, {source: stealth_script})这个脚本覆盖了从基础属性到图形渲染的多个层面。请注意第5点修改WebGL参数需要谨慎最好与你伪装的操作系统和硬件环境匹配。这些注入必须在每个新文档创建时执行所以使用Page.addScriptToEvaluateOnNewDocument是正确的方法。4.3 处理Canvas指纹可选高级项Canvas指纹是一种非常强大的跟踪技术。网站通过让你在隐藏的Canvas画布上渲染文字或图形由于不同硬件、操作系统、显卡驱动的渲染引擎存在极细微的差异生成的图像数据会不同从而生成一个唯一指纹。完全欺骗Canvas指纹极其困难但我们可以增加其噪声使其不稳定。一种思路是重写HTMLCanvasElement.prototype.toDataURL和HTMLCanvasElement.prototype.getContext方法在输出图像数据前加入微小的随机噪声。但这属于“军备竞赛”的范畴且可能影响页面正常功能普通场景不建议使用。对于绝大多数基于JavaScript属性检测的反爬做到前面几步已经足够。5. 行为模拟让脚本“像人一样”操作即使环境指纹完美僵化的操作行为也会出卖你。这部分是很多教程忽略的但恰恰是长期稳定运行的关键。5.1 随机化等待与操作间隔绝对不要使用固定的time.sleep(2)。人类的操作间隔是随机的。使用random.uniform(a, b)来生成随机等待时间。import random, time from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def human_like_delay(min_sec1.0, max_sec3.0): time.sleep(random.uniform(min_sec, max_sec)) # 使用示例 driver.get(https://example.com) human_like_delay(2, 5) # 等待页面加载时间在2-5秒之间 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, search-box)) ) # 点击前稍作停顿模拟人的犹豫 human_like_delay(0.5, 1.2) element.click()5.2 模拟非线性的鼠标移动Selenium的ActionChains可以模拟鼠标移动但默认是直线瞬间移动。我们可以将其分解为多段带有随机偏移的移动模拟人类鼠标的轨迹。from selenium.webdriver import ActionChains import math def human_like_mouse_move(driver, element): 将鼠标从当前位置移动到目标元素位置模拟人类轨迹 actions ActionChains(driver) # 获取元素位置 location element.location size element.size target_x location[x] size[width] / 2 target_y location[y] size[height] / 2 # 获取当前鼠标位置假设从屏幕中心开始 start_x, start_y 960, 540 # 示例起始点 # 将移动路径分成多段 num_steps random.randint(10, 20) points_x [start_x (target_x - start_x) * (i / num_steps) for i in range(num_steps 1)] points_y [start_y (target_y - start_y) * (i / num_steps) for i in range(num_steps 1)] # 为每个点添加随机扰动 for i in range(1, len(points_x)): points_x[i] random.uniform(-5, 5) points_y[i] random.uniform(-5, 5) # 执行移动 for x, y in zip(points_x, points_y): actions.move_by_offset(x - actions.w3c_actions.pointer_inputs[0].x, y - actions.w3c_actions.pointer_inputs[0].y) actions.pause(random.uniform(0.01, 0.05)) # 每步之间微小停顿 actions.perform() human_like_delay(0.1, 0.3) # 移动到后稍作停留再点击 actions.click().perform()这个函数比较复杂实际项目中你可能不需要如此精细但理解这个思路很重要人类的操作是带有噪声和延迟的。5.3 模拟滚动与视线停留在阅读长页面时人类会滚动屏幕且滚动速度不均匀。可以模拟随机滚动。def human_like_scroll(driver, scroll_pixels500): 模拟人类滚动可能一次滚不到底会停顿 current_height 0 total_height driver.execute_script(return document.body.scrollHeight) while current_height total_height: # 每次滚动一个随机距离 scroll_step random.randint(200, 800) driver.execute_script(fwindow.scrollBy(0, {scroll_step});) current_height scroll_step human_like_delay(0.5, 2.0) # 滚动后停顿一下像是在阅读 # 有小概率向上回滚一点 if random.random() 0.1: driver.execute_script(fwindow.scrollBy(0, {-random.randint(50, 200)});) human_like_delay(0.3, 1.0)6. 实战集成与完整代码示例让我们把上面的所有技巧整合到一个实战示例中目标是访问一个检测严格的网站以nowsecure.nl为例并成功通过检测。import random import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def create_stealth_driver(): 创建一个经过多重伪装的Selenium WebDriver chrome_options Options() # 1. 基础隐身参数 chrome_options.add_argument(--disable-blink-featuresAutomationControlled) chrome_options.add_experimental_option(excludeSwitches, [enable-automation]) chrome_options.add_experimental_option(useAutomationExtension, False) # 2. 随机User-Agent user_agents [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, ] chrome_options.add_argument(f--user-agent{random.choice(user_agents)}) # 3. 禁用一些可能暴露的特征 chrome_options.add_argument(--disable-gpu) chrome_options.add_argument(--disable-extensions) chrome_options.add_argument(--disable-popup-blocking) # chrome_options.add_argument(--headlessnew) # 谨慎使用无头 # 4. 创建驱动 driver webdriver.Chrome(optionschrome_options) # 5. 执行深度CDP隐身脚本 stealth_script // 综合隐身脚本 Object.defineProperty(navigator, webdriver, { get: () undefined }); Object.defineProperty(navigator, plugins, { get: () [1, 2, 3, 4, 5], }); Object.defineProperty(navigator, languages, { get: () [zh-CN, zh, en-US, en], }); window.chrome { runtime: {}, app: {} }; driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, {source: stealth_script}) # 6. 设置时区和语言 driver.execute_cdp_cmd(Emulation.setTimezoneOverride, {timezoneId: Asia/Shanghai}) driver.execute_cdp_cmd(Emulation.setLocaleOverride, {locale: zh-CN}) # 7. 删除Selenium可能遗留的全局变量旧版驱动 driver.execute_script( Object.keys(window).forEach(key { if (key.includes($cdc_) || key.includes($wdc_)) { delete window[key]; } }); ) return driver def human_like_delay(min_sec1.0, max_sec3.0): time.sleep(random.uniform(min_sec, max_sec)) def main(): driver None try: print(正在创建隐身浏览器...) driver create_stealth_driver() human_like_delay(1, 2) print(访问测试网站...) test_url https://nowsecure.nl # 反爬测试站 driver.get(test_url) human_like_delay(3, 6) # 给予充足加载和检测时间 # 等待页面加载出特定元素如果成功加载则说明绕过检测 wait WebDriverWait(driver, 15) # nowsecure.nl 成功加载后会有特定文本或元素 success_element wait.until( EC.presence_of_element_located((By.XPATH, //*[contains(text(), you are not a robot) or contains(text(), passed)])) ) if success_element: print(*** 成功绕过检测页面正常加载。 ***) # 可以截图保存证据 driver.save_screenshot(success_bypass.png) page_title driver.title print(f页面标题: {page_title}) else: print(可能未完全通过检测页面元素未找到。) # 模拟人类浏览行为 print(模拟人类滚动浏览...) for _ in range(3): driver.execute_script(fwindow.scrollBy(0, {random.randint(300, 700)});) human_like_delay(1.5, 4) except Exception as e: print(f*** 操作过程中出现错误: {e} ***) # 如果被检测到页面可能会跳转到验证码或错误页这里可以截图分析 if driver: driver.save_screenshot(error_state.png) finally: if driver: human_like_delay(2, 4) # 最后停留一下 driver.quit() print(浏览器已关闭。) if __name__ __main__: main()这个脚本集成了环境配置、驱动修改、CDP注入和行为模拟。运行它观察是否能成功访问nowsecure.nl并看到“you are not a robot”之类的成功提示而不是验证码。7. 常见问题排查与进阶技巧即使按照上面的步骤做了有时仍然会被检测到。这时候就需要系统性地排查。7.1 诊断与排查流程检查navigator.webdriver属性在页面加载后在浏览器开发者工具的控制台输入navigator.webdriver看是否是undefined。如果显示true说明CDP注入失败。检查User-Agent和插件在控制台输入navigator.userAgent和navigator.plugins.length看看是否和你设置的一致。使用检测网站进行测试除了nowsecure.nl还可以用pixelscan.net或antoinevastel.com/bots这类专业的浏览器指纹检测网站它们会详细列出你的浏览器暴露了哪些自动化特征。对比真实浏览器用同一个版本的Chrome手动打开一个页面在控制台里记录下window、navigator、document等对象的一系列属性值。然后用你的Selenium脚本打开同一个页面对比两者的差异。差异点就是需要修补的地方。查看网络请求有些检测脚本会通过WebDriver特有的CDP接口发送特殊的网络请求。在开发者工具的Network面板中过滤XHR或Fetch请求看看有没有可疑的、指向检测服务的请求。7.2 进阶技巧与“军备竞赛”当基础方法失效时你可能需要进入更深的层面使用Puppeteer-extra-plugin-stealth如果项目允许可以考虑直接使用PuppeteerNode.js配合其stealth插件。这个插件维护得非常活跃集成了当前最全的绕过技巧。你可以用pyppeteerPuppeteer的Python端口来在Python环境中使用它但生态相对弱一些。修改Chromium源码重新编译这是终极方案直接修改Chromium或ChromeDriver的源代码从根本上移除所有自动化标记。但这需要极高的技术门槛和持续维护成本仅适用于大型、长期且预算充足的项目。轮换浏览器指纹结合像browser-fingerprint这样的库每次启动都生成一套全新的、自洽的浏览器指纹包括Canvas、WebGL、AudioContext等并通过CDP全部设置进去。这属于动态伪装成本很高。识别检测代码并反制有些网站会加载特定的检测JavaScript文件如distil、perimeterx、akamai等。你可以尝试在页面加载前通过CDP拦截并修改这些检测脚本的响应内容使其失效。这需要逆向工程能力。7.3 最重要的心得保持更新与适度原则没有一劳永逸的方法反爬与绕过的对抗是动态的。今天有效的方法下个月可能就失效了。关键在于理解原理建立自己的排查和更新流程。不要过度伪装过度的、不合理的伪装比如在Windows系统上报告Mac的WebGL渲染器本身就会成为一个异常指纹。保持环境属性系统、语言、时区、屏幕分辨率的自洽性比堆砌技巧更重要。成本与收益平衡对于大多数项目使用undetected-chromedriver加上基础的行为模拟已经能解决80%的问题。是否需要投入精力实现极致隐身取决于目标网站的价值和反爬强度。尊重robots.txt与法律法规技术讨论仅限于学习与合法合规的自动化测试场景。用于数据采集时务必遵守网站的robots.txt协议和相关法律法规控制访问频率避免对目标网站造成负担。绕过检测的本质是一场关于“信任”的博弈。我们的脚本需要向网站证明“我是一个合法的、真实的用户浏览器。” 通过从环境、驱动到行为的层层伪装我们尽可能地将这个谎言编得圆满。这个过程充满挑战但也正是技术乐趣所在。希望这篇基于2023年最新实践的长文能为你提供一套清晰、可操作的思路和工具。