1. 项目概述当Selenium遇上“反爬”高墙如果你做过Web自动化测试或者数据采集大概率对Selenium又爱又恨。爱的是它功能强大能模拟真人操作浏览器几乎无所不能恨的是现在稍微有点规模的网站都能轻易识别出你是“机器人”。你精心编写的脚本可能刚跑起来页面就弹出一个验证码或者直接返回一个空白页告诉你“访问过于频繁”。这感觉就像你穿着夜行衣去执行任务结果一进门所有的警报器都响了保安拿着手电筒直接照在你脸上。这就是我们今天要聊的核心问题如何让Selenium驱动的浏览器在目标网站眼里变得“隐形”。我最近在一个需要模拟真实用户行为进行大规模数据验证的项目里就深陷这个泥潭。常规的Selenium脚本被目标站的风控系统识别率高达90%以上测试流程寸步难行。直到我系统地研究和应用了Selenium Stealth这套理念和技术组合才真正解决了问题。这不是某一个具体的、叫做“Stealth”的库虽然有一个同名的Python包而是一整套让自动化浏览器指纹“隐身”的实战策略。简单来说Selenium Stealth的目标就是通过一系列技术手段修改或掩盖那些能被网站检测到的、属于自动化程序的“非人类”特征使其指纹与普通用户通过Chrome、Firefox等浏览器手动访问时几乎一致。这不仅仅是加个user-agent那么简单它涉及到WebDriver属性、JavaScript变量、浏览器特性、行为模式等数十个维度的对抗。接下来我将结合我的实战经验把这套“隐形术”从原理到操作掰开揉碎了讲清楚。2. 核心需求解析为什么你的Selenium脚本会被“发现”在深入技术细节前我们必须先弄明白对手是如何工作的。网站检测自动化脚本主要依赖于“浏览器指纹”技术。指纹就像你的网络身份证由浏览器暴露给网站的众多属性信息组合而成。自动化工具会无意中留下一些独特的“指纹”。2.1 关键检测点剖析网站通常从以下几个层面进行检测WebDriver对象属性这是最直接的证据。通过navigator.webdriver这个属性普通浏览器返回undefined或false而由Selenium控制的浏览器会返回true。这是许多风控系统的首要检查项。插件与语言列表navigator.plugins插件列表和navigator.languages语言列表在自动化环境中可能为空、格式异常或与声称的浏览器版本不匹配。屏幕分辨率与色彩深度自动化脚本有时会使用非标准的窗口大小或者色彩深度screen.colorDepth,screen.pixelDepth与常见设备不符。HTTP请求头特征虽然Selenium可以设置User-Agent但其他头部信息如Accept-Language,Sec-CH-UA用户代理客户端提示等可能缺失或格式不正确无法完美模拟特定浏览器版本。JavaScript执行环境差异一些原生JavaScript对象的属性或方法如window.chrome,document.hidden在自动化环境下的行为可能有细微差别。行为模式异常这是更高级的检测。人类的操作有随机延迟、不精确的鼠标移动轨迹、不规律的滚动速度。而自动化脚本的点击、输入、滚动往往过于精准和迅速像是机器在“瞬移”。2.2 不同场景下的需求差异你的“隐形”需求强度取决于目标网站的风控等级基础内容抓取与测试对于个人博客、技术文档等无风控或弱风控站点可能只需要处理webdriver属性即可。电商价格监控、社交媒体数据收集这类网站通常有反爬机制需要处理核心的JavaScript环境变量和请求头。金融数据、高级别平台自动化这是“地狱难度”。需要综合运用所有技术并模拟人类行为模式任何一环出错都可能导致失败。我的项目属于第二种目标是几个主流电商和内容平台因此需要一套中等偏上强度的隐身方案。3. 技术方案选型从“补丁”到“系统工程”面对这些检测点我们有不同层次的技术方案可以选择。我将其分为三个层级3.1 层级一基础属性覆盖治标不治本这是最初级的做法也是很多人唯一知道的做法修改User-Agent使用options.add_argument(user-agent...)。移除“自动化控制”提示options.add_experimental_option(excludeSwitches, [enable-automation])和options.add_experimental_option(useAutomationExtension, False)。执行CDP命令通过Chrome DevTools Protocol (CDP) 执行Page.addScriptToEvaluateOnNewDocument来覆盖navigator.webdriver等属性。为什么这不够因为这些修改是“打补丁”只覆盖了最明显的几个点。现代风控系统会进行多维度、交叉验证。例如你声称自己是Chrome 120但你的navigator.plugins列表是空的这立刻就会暴露。3.2 层级二专用隐身库快速上手但有局限这就是标题中提到的selenium-stealth(Python包)。它是一个对层级一技术的封装提供了一键式解决方案。from selenium_stealth import stealth # ... 初始化driver后 stealth(driver, languages[en-US, en], vendorGoogle Inc., platformWin32, webgl_vendorIntel Inc., rendererIntel Iris OpenGL Engine, fix_hairlineTrue, )它的优点方便开箱即用覆盖了十多个常见检测点。它的缺点更新可能滞后浏览器和检测技术都在更新库的更新可能跟不上。灵活性不足参数是预设的如果你想模拟一台MacBook上的Safari可能需要自己修改源码。可能被特征识别大量用户使用相同的selenium-stealth配置其修改指纹的模式本身可能成为新的检测特征。3.3 层级三自定义CDP与行为模拟终极方案这是最彻底、最灵活的方法。核心是直接使用CDP命令在页面加载前注入JavaScript精确地、按需地修改浏览器环境。同时结合ActionChains等工具模拟人类操作。为什么我最终选择了层级三在项目中期使用selenium-stealth后识别率从90%降到了50%但仍然不够。分析日志发现目标网站增加了一些新的、冷门的检测点。selenium-stealth没有覆盖到。我需要能够动态调整策略针对特定网站进行“外科手术式”的指纹修改。层级三方案给了我完全的掌控权。4. 实战构建你自己的“隐形”Selenium驱动下面我将以Chrome浏览器为例详细拆解如何从零开始构建一个高度隐身的WebDriver。这套方案是我在项目中反复调试后总结的你可以直接复制使用并根据需要调整。4.1 环境准备与基础配置首先确保你安装了最新版的Selenium和浏览器驱动。pip install selenium webdriver-manager使用webdriver-manager可以自动管理驱动版本避免兼容性问题。初始化Driver时我们就要开始添加“隐身”配置from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options def create_stealth_driver(): 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. 模拟真实用户代理和语言示例为Windows上的Chrome chrome_options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36) chrome_options.add_argument(accept-languageen-US,en;q0.9) # 3. 其他实用参数减少特征 chrome_options.add_argument(--disable-gpu) # 某些虚拟环境可能需要 chrome_options.add_argument(--no-sandbox) # Linux环境常用 chrome_options.add_argument(--disable-dev-shm-usage) # 解决共享内存问题 chrome_options.add_argument(--disable-blink-featuresAutomationControlled) # 以无头模式运行时这个参数很重要 # chrome_options.add_argument(--headlessnew) # 如果需要无头模式使用new headless # 4. 禁用自动化密码保存提示等 prefs { credentials_enable_service: False, profile.password_manager_enabled: False } chrome_options.add_experimental_option(prefs, prefs) # 使用webdriver-manager自动获取驱动 service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionschrome_options) return driver注意--headlessnew是Chrome较新版本的无头模式比旧的--headless更隐蔽但并非完全无法检测。对于高风控网站能不用无头尽量不用。4.2 核心使用CDP进行深度指纹修改这是隐身技术的核心。我们通过CDP在文档创建前执行JS覆盖关键属性。def apply_stealth_js(driver): # 这段JavaScript是隐身的关键它在页面任何脚本执行前就修改了环境。 stealth_js // 1. 覆盖webdriver属性 Object.defineProperty(navigator, webdriver, { get: () undefined }); // 2. 覆盖plugins模拟常见的插件 Object.defineProperty(navigator, plugins, { get: () [1, 2, 3, 4, 5] }); // 3. 覆盖languages Object.defineProperty(navigator, languages, { get: () [en-US, en] }); // 4. 覆盖chrome对象仅针对Chrome // 注意这里我们模拟了chrome对象的存在但移除了其runtime属性自动化特征 window.chrome { runtime: {}, // 可以添加其他chrome属性 }; // 5. 覆盖permissions API const originalQuery window.navigator.permissions.query; window.navigator.permissions.query (parameters) ( parameters.name notifications ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters) ); // 6. 处理iframes中的检测 Object.defineProperty(HTMLIFrameElement.prototype, contentWindow, { get: function() { return window; } }); console.log(Stealth JS injected); # 使用CDP命令执行 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: stealth_js })如何使用在driver.get(url)之前调用apply_stealth_js(driver)。这样在目标页面加载时我们的修改已经生效。4.3 模拟人类行为模式即使指纹完美机械式的操作也会出卖你。我们需要让脚本“像人一样”。随机延迟不要在操作间使用固定的time.sleep(2)。import random import time def human_delay(min_s1, max_s3): 生成一个随机的延迟时间 time.sleep(random.uniform(min_s, max_s)) # 使用示例 driver.find_element(...).click() human_delay(1, 2) # 等待1到2秒之间的一个随机时间 driver.find_element(...).send_keys(text)模拟鼠标移动使用ActionChains进行非直线的移动。from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By element driver.find_element(By.ID, some-button) actions ActionChains(driver) # 将鼠标先移动到页面某个随机位置再移动到目标元素 actions.move_by_offset(random.randint(-50, 50), random.randint(-50, 50)).pause(0.5) actions.move_to_element(element).pause(0.2) # 在元素上稍作停顿 actions.click() actions.perform()随机滚动不要一次性滚动到底。def human_like_scroll(driver, scroll_to_heightNone): total_height scroll_to_height or driver.execute_script(return document.body.scrollHeight) current_position 0 scroll_step random.randint(200, 500) # 每次滚动200-500像素 while current_position total_height: # 随机决定这次滚动多少 this_step min(scroll_step, total_height - current_position) this_step random.randint(int(this_step*0.8), this_step) # 加入随机性 driver.execute_script(fwindow.scrollBy(0, {this_step});) current_position this_step human_delay(0.5, 1.5) # 滚动后随机停顿5. 进阶技巧与疑难排坑在实际项目中你会遇到各种各样奇怪的问题。下面是我踩过坑后总结的“排雷手册”。5.1 如何检测自己的隐身效果你不能盲目相信代码必须验证。有以下几种方法使用检测网站访问像pixelscan.net、browserleaks.com这样的浏览器指纹检测站。用你的隐身Driver打开它们查看检测报告。重点关注“WebDriver”、“自动化”等项目的检测结果。本地JS检测在页面中执行一些检测脚本。driver.get(about:blank) test_js return { webdriver: navigator.webdriver, plugins: navigator.plugins.length, languages: navigator.languages, chrome: typeof window.chrome, } result driver.execute_script(test_js) print(检测结果:, result) # 理想结果webdriver应为undefined或falseplugins0chrome为object但可能没有runtime5.2 常见问题与解决方案问题现象可能原因解决方案navigator.webdriver仍为trueCDP脚本注入失败或顺序不对确保在driver.get()前执行execute_cdp_cmd。检查Chrome版本与CDP兼容性。网站提示“请使用现代浏览器”User-Agent或HTTP头信息不完整/错误使用更真实的UA字符串并通过CDP或selenium-wire等库设置完整的请求头如Accept,Sec-CH-UA。仅在无头模式下被检测到无头模式有额外特征尝试使用--headlessnew。如果不行考虑使用“有头但最小化”模式chrome_options.add_argument(--window-size1920,1080)并配合虚拟帧缓冲xvfb在服务器运行。操作太快被封IP行为模式异常与指纹无关严格遵守“人类行为模拟”增加随机延迟和错误操作如偶尔点击错位置再纠正。使用代理IP池轮换请求。部分iframe内内容仍检测到自动化iframe是独立上下文我们的JS未注入在切换到iframe后重新在该iframe的上下文中执行CDP命令注入JS。5.3 关于“免费”与可持续性标题中提到“亲测免费”这里的免费指的是Selenium Stealth技术本身是开源的无论是理念还是selenium-stealth库都无需付费。核心解决方案CDP是浏览器原生能力不需要购买任何第三方服务。但是请注意隐身是一场持续的“军备竞赛”。今天有效的方法明天可能就失效了。因此最宝贵的“免费”资源是你的调试能力和知识储备。你需要保持更新关注Chrome/WebDriver的版本更新它们可能会引入或移除某些特征。建立测试用例为你的目标网站编写简单的检测脚本定期运行确保隐身策略依然有效。理解原理而非死记代码明白每个CDP命令和JS片段是在修改什么为什么这样改。这样当出现新检测点时你才能快速找到对策。6. 项目实战一个完整的隐身数据采集流程示例假设我们需要从某个电商网站我们称其为SiteX列表页采集商品名称和价格该网站有基础的反爬。import random import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options class StealthScraper: def __init__(self, use_headlessFalse): self.driver self._create_stealth_driver(use_headless) self._apply_stealth() def _create_stealth_driver(self, use_headless): options Options() if use_headless: options.add_argument(--headlessnew) # 使用新的无头模式 # 基础隐身与配置 options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...) service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice, optionsoptions) driver.maximize_window() # 最大化窗口更符合用户习惯 return driver def _apply_stealth(self): 应用深度隐身JS js Object.defineProperty(navigator, webdriver, {get: () undefined}); // ... 此处包含上一节4.2中的所有JS代码 ... self.driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, {source: js}) def _human_delay(self, min_t1, max_t3): time.sleep(random.uniform(min_t, max_t)) def scrape_sitex(self, url): print(f正在访问: {url}) self.driver.get(url) self._human_delay(2, 4) # 初始页面加载等待 data [] page_num 1 while True: print(f正在处理第 {page_num} 页...) # 1. 模拟人类随机滚动阅读 self._human_like_scroll() # 2. 等待商品元素加载 try: WebDriverWait(self.driver, 10).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, .product-item)) ) except: print(未找到商品元素可能页面结构已变或已被拦截。) break products self.driver.find_elements(By.CSS_SELECTOR, .product-item) for product in products: # 3. 模拟鼠标悬停随机选择部分商品 if random.random() 0.7: # 30%的概率执行悬停 actions ActionChains(self.driver) actions.move_to_element(product).pause(random.uniform(0.5, 1.5)).perform() # 4. 提取数据 try: name_elem product.find_element(By.CSS_SELECTOR, .product-name) price_elem product.find_element(By.CSS_SELECTOR, .product-price) # 模拟人类阅读速度逐个元素查看 self._human_delay(0.1, 0.3) name name_elem.text.strip() price price_elem.text.strip() if name and price: data.append({name: name, price: price}) except: continue # 某个元素提取失败跳过该商品 # 5. 寻找下一页按钮并模拟人类点击 try: next_btn self.driver.find_element(By.CSS_SELECTOR, .next-page) # 点击前随机延迟 self._human_delay(1, 2) # 滚动到按钮位置 self.driver.execute_script(arguments[0].scrollIntoView({behavior: smooth, block: center});, next_btn) self._human_delay(0.5, 1) next_btn.click() page_num 1 self._human_delay(2, 4) # 等待新页面加载 except: print(已到达最后一页或找不到下一页按钮。) break return data def _human_like_scroll(self): 模拟人类滚动 scroll_pause_time random.uniform(0.5, 1.5) scroll_step random.randint(300, 700) current_scroll 0 max_scroll self.driver.execute_script(return document.body.scrollHeight) - self.driver.execute_script(return window.innerHeight) while current_scroll max_scroll: # 每次滚动距离随机 this_step min(scroll_step, max_scroll - current_scroll) this_step random.randint(int(this_step*0.7), this_step) self.driver.execute_script(fwindow.scrollBy(0, {this_step});) current_scroll this_step time.sleep(scroll_pause_time) # 随机决定是否进行一小段回滚模仿人类犹豫 if random.random() 0.9 and current_scroll 100: rollback random.randint(-50, -10) self.driver.execute_script(fwindow.scrollBy(0, {rollback});) current_scroll rollback time.sleep(random.uniform(0.3, 0.8)) def close(self): self.driver.quit() # 使用示例 if __name__ __main__: scraper StealthScraper(use_headlessFalse) # 高风控站建议先用有头模式调试 try: data scraper.scrape_sitex(https://www.example-sitex.com/products) print(f共采集到 {len(data)} 条商品数据。) for item in data[:5]: # 打印前5条看看 print(item) finally: scraper.close()这个示例融合了之前所有的知识点深度指纹修改、人类行为模拟、健壮的错误处理。它不是一个万能脚本但为你提供了一个高度可定制和可调试的框架。7. 总结与个人心得让Selenium“隐形”不是调用一个魔法函数就能完成的它是一个系统工程涉及浏览器指纹、网络协议和行为模式三个层面的对抗。我的经验是分步实施逐步加强不要一开始就上最复杂的方案。先用基础配置和selenium-stealth测试如果被识别再逐步添加CDP深度修改和行为模拟。用检测网站作为你的“试金石”。理解高于复制网上有很多代码片段但如果不明白Object.defineProperty在做什么不明白CDP命令的执行时机当脚本失效时你将束手无策。花时间阅读MDN上关于Web API的文档理解navigator、screen、window这些对象。环境隔离与代理管理对于大规模任务一定要使用干净的浏览器环境可以考虑每次启动新用户目录和高质量的代理IP。一个被标记的IP地址会让任何隐身技术失效。保持敬畏合法使用技术是把双刃剑。这套方法旨在帮助测试工程师更好地完成自动化测试工作或研究人员在遵守robots.txt和网站条款的前提下进行合规的数据分析。请勿将其用于恶意爬取、攻击或干扰网站正常运行。最后再分享一个调试小技巧在开发隐身脚本时我通常会打开Chrome的开发者工具options.add_argument(--auto-open-devtools-for-tabs)在Console里实时查看navigator.webdriver等属性的值这比任何外部检测都更直接。隐身是一场猫鼠游戏而你的耐心和对细节的把握是赢得这场游戏的关键。