首页 / 资讯中心 / 文章详情

Selenium被识别怎么办?浏览器指纹与反检测加固实战

Selenium被识别怎么办?浏览器指纹与反检测加固实战 ★ FEATURED ARTICLE
最近被问得最多的一个问题不是“selenium怎么定位元素”而是“我用selenium一打开就被识别怎么办”。说实话这个问题我前几年也踩过坑而且踩得特别深——不是代码写错了是浏览器本身在“告密”。selenium控制的浏览器和普通用户打开的浏览器在运行环境里有大量肉眼看不见的差异反爬体系只要扫描几个关键对象就能判断你到底是真人还是工具。这篇不聊玄学直接拆解selenium为什么会被识别、我实际用过的几套加固思路、以及每一步背后的原理。适合刚上手爬虫的开发者也适合做自动化测试时被站点风控误伤的朋友。提前说明所有方案只适用于合法合规的自动化场景合规边界在最后一节讲清楚。1. 被人一眼看穿的原因selenium的“指纹”到底藏在哪里很多人以为反爬是靠IP频率其实第一道关卡根本到不了IP。站点通常先做浏览器环境检测也就是常说的“指纹识别”。selenium启动的浏览器天然带着一套和普通浏览器不同的环境特征检测方只需要在页面里跑几行JavaScript就能识别。1.1 WebDriver标记最容易被查的一个马脚用selenium启动的浏览器window对象上会挂一个特殊的属性navigator.webdriver。普通浏览器里这个值应该是undefinedselenium环境下这个值通常为true。为什么会有这个属性这是WebDriver协议的标准行为目的是方便测试框架确认“当前浏览器是否由自动化工具控制”。但反爬系统恰好拿它当检测依据。你可以自己在浏览器控制台里验证一下console.log(navigator.webdriver);如果返回true基本上属于“裸奔”状态。很多老教程会让你用JavaScript把它覆写成undefinedfrom selenium import webdriver options webdriver.ChromeOptions() options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(optionsoptions)这段代码确实能去掉Chrome顶部的“正在受自动化软件控制”提示但navigator.webdriver这个属性有时候依然还在。我实测过不同版本、不同内核表现不一样不能只靠这一招。1.2 可被探测的JavaScript环境差异除了webdriver标记检测脚本还会扫一堆环境信息。常见的有window.chrome对象结构是否完整navigator.languages、navigator.plugins、navigator.platform是否存在Permissions API查询结果比如navigator.permissions.query({name: notifications})浏览器是否支持某些底层API以及调用时的返回值是否正常普通浏览器打开一个空白页面window.chrome对象会有一系列子对象和属性selenium控制下的浏览器有些子对象不存在或者顺序不同。检测脚本只要判断这些细节就能得出“大概率是自动化工具”的结论。还有一个容易忽略的点页面加载后会检查document.documentElement.getAttribute(webdriver)selenium环境下这个值可能是空字符串也可能是缺失视驱动版本而定。这类属性检测很难一次性根除需要后面讲的注入方案来处理。2. 基础加固改UA和去掉webdriver标记只是第一步先别急着上那些高大上的库把基础配置做对可以挡掉大约七成比较“粗糙”的检测。所谓粗糙是指对方只查UA、只查webdriver标记、只查无头模式。2.1 头信息设置与参数调整UAUser-Agent是最基本的但很多人设置得不全。如果UA设置成“Python-requests”或者“HeadlessChrome”那不用检测脚本服务端日志扫一眼UA就能把你过滤掉。我现在的习惯是用真实浏览器UA并且保证这些字段之间相互匹配from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() 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) options.add_argument(--langzh-CN) options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions)UA的版本最好跟实际浏览器内核版本接近。如果Chrome内核是120UA写一个Chrome/80对方对比JS中的navigator.userAgent和浏览器内核版本很容易看出偏差。2.2 用ChromeOptions清理常见暴露点ChromeOptions里有些参数是专门给自动化场景用的但也会留下痕迹。基础配置一般这么写options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) options.add_argument(--disable-blink-featuresAutomationControlled)这几个参数的作用excludeSwitches里的enable-automation会去掉Chrome提示条useAutomationExtension设为False禁用自动化扩展AutomationControlled这个blink特性是控制页面感知“自动化”标记的开关加了这些之后navigator.webdriver不一定消失但页面里很多基于“Chrome正在被自动化控制”的检测点会失效。实测中一些中低强度的风控系统到这一步就不再拦截了。还有一个小技巧可以固定浏览器的窗口大小避免默认的800x600或者奇怪的尺寸被识别options.add_argument(--window-size1920,1080)但这里有个细节窗口大小和屏幕分辨率最好保持一致否则通过screen.width、window.outerWidth这类API对比也能发现异常。3. 进阶方案让浏览器环境“长”得像普通用户基础配置只能对付“查一下UA、查一下webdriver标记”的初级风控。遇到真正上指纹检测的站点必须在页面加载前把环境变量“补齐”让浏览器环境看起来和普通用户完全一致。3.1 用CDP注入脚本在页面加载前修改环境先理解一个概念CDPChrome DevTools Protocol是Chromium内核提供的一套调试协议。selenium底层操作的正是这套协议。通过execute_cdp_cmd可以在页面还没执行任何JavaScript的时候预先注入一些代码把异常属性改掉。我的做法是这样的from selenium import webdriver options webdriver.ChromeOptions() options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, { get: () undefined }); Object.defineProperty(navigator, languages, { get: () [zh-CN, zh, en] }); Object.defineProperty(navigator, plugins, { get: () [1, 2, 3, 4, 5] }); })关键点是Page.addScriptToEvaluateOnNewDocument。它保证脚本在每一个新文档加载之前执行所以页面脚本读取navigator.webdriver时拿到的是undefined。不过这里有个坑有些检测脚本不直接读navigator.webdriver而是通过toString检测或者异步延迟读取。单纯的defineProperty方案可能被绕过。所以还要配合下面说的完整stealth思路。3.2 用成熟的反检测库补齐全部指纹自己手动改环境总会有遗漏。社区里已经有开源的浏览器环境修补库专门收集了常见检测点。我在项目里用的是stealth.min.js的思路它通过注入一段JavaScript把webdriver、plugins、languages、chrome对象、Permissions API等一系列容易被检测的点统一修复。用法很简单把js文件读取出来再通过上面的CDP注入from selenium import webdriver with open(stealth.min.js, r) as f: js f.read() options webdriver.ChromeOptions() options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions) driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, {source: js})这段代码的核心价值不是某一行属性覆写而是把所有已知的检测点一次性处理。比手动写几十个defineProperty要省心得多。3.3 指纹一致性时区、语言、字体、Canvas和WebGL环境变量改完之后下一个问题是指纹一致性。什么叫一致性一个真实用户他的时区、语言、字体、Canvas渲染结果、WebGL参数应该构成一个“自洽的整体”。如果你的浏览器请求头写着Windows 中文但JavaScript环境里的时区是UTC语言只有en-US这就成了矛盾点。slleselenium默认的时区通常是UTC而中文用户多是Asia/Shanghai这是个很典型的破绽。可以在启动参数里加上options.add_argument(--timezone-areaAsia/Shanghai)字体也不能忽略。Linux服务器上往往缺很多中文字体页面通过document.fonts.check(12px Microsoft YaHei)检测发现返回false就会判定环境异常。所以部署环境里最好安装常用中文字体包。Canvas和WebGL指纹属于更底层的检测。同一个浏览器版本在不同GPU环境下Canvas的渲染结果会有细微差异。检测方一般不会单独靠Canvas判定而是把Canvas、WebGL、AudioContext等多维信息组合成一个用户指纹。理想情况下同一套环境多次访问指纹保持一致如果每次启动浏览器指纹都变反而会被标记为异常。我的建议是不要在容器里频繁切换环境固定一个相对稳定的浏览器版本、固定启动参数配合固定的时区、语言配置。这样指纹的稳定性才会高。4. 行为模拟比填写数据更容易被发现的其实是“操作习惯”环境指纹过了关不代表万事大吉。很多站点会记录你的行为轨迹鼠标怎么移动、点击间隔多少、滚动速度多快、页面停留时间多长。真人操作和程序操作在这些维度上有明显差异。4.1 鼠标移动与点击轨迹selenium默认的click()方法是直接跳到元素中心点触发点击没有移动轨迹。真人不会有“瞬移点击”这种操作。反爬系统如果检测到鼠标坐标从A点瞬间跳到B点再触发点击就值得怀疑。可以模拟一个带轨迹的移动过程from selenium.webdriver.common.action_chains import ActionChains import random import time actions ActionChains(driver) actions.move_to_element(target_element) # 加入随机偏移 x_offset random.randint(-20, 20) y_offset random.randint(-20, 20) actions.move_by_offset(x_offset, y_offset) actions.click() actions.perform()更复杂的做法是记录多个中间坐标点用贝塞尔曲线模拟真实轨迹然后逐步移动。我这里不展开太多核心思路就是不要一秒钟之内完成移动和点击要做一些看似“多余”的动作。4.2 滚动、停留和事件顺序真人浏览页面会先看顶部然后慢慢往下滚中途停下来读内容偶尔回滚。程序一般直接execute_script滚动到底部这种操作特征太明显。建议把滚动拆成多步每一步之间随机停顿import random import time for i in range(1, 8): driver.execute_script(window.scrollTo(0, document.body.scrollHeight * %d / 8); % i) time.sleep(random.uniform(0.5, 1.5))这里的时间随机范围要根据目标页面内容量调整。如果页面内容很少滚动次数太多反而奇怪。另外页面加载完成后不要立即点击。真实用户会有认知延迟通常需要几百毫秒到几秒的思考时间。这个“思考时间”可以通过固定随机范围来模拟time.sleep(random.uniform(2, 5))4.3 验证码与异常弹窗的处理原则聊到验证码必须把话说明白我的建议是不去“打码绕过”平台的风控验证码更不推荐用各种打码平台暴力破解。正常自动化项目遇到验证码正确的处理方式是暂停操作或者切换网络出口策略降低频率而不是试图绕过。为什么会突然弹验证码通常是两个原因一是当前环境的指纹不够“真”二是访问频率太高。把前面几步做好验证码的出现率会大幅下降。如果验证码出现频率仍然很高说明你的访问节奏有问题应该降低并发、加长间隔。5. 实战中的常见问题与排查清单下面这些情况是我在实际项目中碰到过的整理成了一张速查表方便排查。现象可能原因处理思路打开页面后立刻被拦截navigator.webdriver为true或UA不对检查CDP注入是否生效更新UA版本能打开首页点击登录后被拦截缺少行为轨迹或指纹不一致加入鼠标移动、随机等待、固定时区语言无头模式访问基本正常有头模式就被拦截两个环境的指纹不一致保持相同的浏览器版本和启动参数同一环境第一次能过第二次就被拦访问频率过高IP被标记降低频率增加随机延迟更换网络出口报错chromedriver version mismatch浏览器版本和驱动版本不一致下载对应版本驱动或用驱动管理工具自动匹配页面元素明明存在但点击没反应页面可能有反自动化脚本用CDP注入修补环境后再刷新页面5.1 如何验证当前环境是否被识别调试时不要直接拿目标站点试容易把IP拖进黑名单。我建议先打开一个检测页面专门看各项指纹指标。常见的检测思路是打开浏览器控制台逐项检查navigator.webdriver navigator.plugins navigator.languages window.chrome也可以访问一些专门展示浏览器指纹信息的网站检查时区、Canvas、WebGL、字体等数据。如果这些信息和你设置的参数一致再考虑上目标站点测试。5.2 无头模式和有头模式的取舍很多教程为了节省资源默认使用headless模式。但实测下来headless模式在底层实现上和普通模式有差异比如部分GPU功能不加载、window.chrome对象结构不同。一些检测站点对headless的探测非常准。如果目标环境要求高优先使用有头模式配合虚拟显示器运行。如果实在需要headless至少使用新的headless模式Chrome 112之后headless和普通模式共用了更多内核逻辑。另外无头模式下要确认UA里没有“HeadlessChrome”字样。5.3 定期更新驱动和浏览器版本selenium、chromedriver和浏览器三者版本必须匹配。很多人排查半天结果只是驱动版本过期。我的习惯是写一个简单的环境检查脚本启动时自动打印当前驱动和浏览器版本减少这类低级问题。6. 最后说点实在的什么能碰、什么不能碰所有规避检测的手段本质上都是为了让自动化程序像一个正常用户。这套能力可以用在自动化测试、自己负责的站点数据采集、竞品公开数据监控等场景但绝不能用来突破权限、绕过法律保护的访问控制或者在高风险场景下暴力收集个人信息。我在实际项目中的体会是把环境配置做到位只是第一步。真正决定稳定性的是访问节奏。很多人喜欢开几十个线程并发抓取反爬系统最容易封的就是这种“高并发同指纹”的流量。宁可单线程慢跑配合随机延迟也比并发冲刺后被一锅端要强。另外不管代码写得多么精致都不建议在线上环境中使用同一个账号高频操作。风控系统有长期画像能力单次伪装得再好也扛不住长期行为异常。合理的做法是控制操作频率让每一轮访问都留有人工操作的余量。最后分享一个我自己的小技巧在每次访问前先请求一次目标站点的robots.txt确认你的自动化行为是否在对方允许范围内。这不仅是合规问题也是技术问题。尊重规则才能让自动化技术走得更远。
阅读完成 · 觉得有帮助?
咨询建站