用Selenium做自动化测试或者爬虫的朋友应该都遇到过这样的问题:明明代码写得没问题,但目标网站就是能识别出你在用Selenium,然后直接拒绝访问或者返回假数据。这种情况在爬取一些大型电商平台、社交媒体网站时特别常见。
我刚开始用Selenium时也踩过不少坑。记得有一次爬某电商网站,明明浏览器能正常访问,但用Selenium就是打不开页面。后来才发现是因为网站检测到了navigator.webdriver这个属性。普通浏览器这个属性是undefined,但Selenium会把它设为true,相当于直接告诉网站"我是自动化工具"。
除了navigator.webdriver,网站还会通过很多其他特征来检测Selenium,比如:
这就是为什么我们需要stealth.min.js这样的工具。它能帮我们把这些"自动化特征"隐藏起来,让Selenium看起来就像普通浏览器一样。原理其实很简单,就是通过JavaScript的Proxy和Reflect API来拦截和修改这些特征值。
stealth.min.js的核心技术就是JavaScript的Proxy对象。Proxy可以理解为对象的"中间人",它能拦截对目标对象的所有操作。比如我们想隐藏navigator.webdriver属性,就可以这样写:
javascript复制const navigatorProxy = new Proxy(navigator, {
get(target, prop) {
if (prop === 'webdriver') {
return undefined // 隐藏webdriver属性
}
return Reflect.get(...arguments) // 其他属性正常返回
}
})
这段代码创建了一个navigator的代理对象。当有人访问navigator.webdriver时,代理会拦截这个操作并返回undefined,就像普通浏览器一样。而对于其他属性访问,则通过Reflect API正常返回。
stealth.min.js不只是隐藏webdriver这么简单,它还模拟了大量浏览器特征:
WebGLRenderingContext.prototype.getParameter方法,返回与普通浏览器一致的渲染器信息navigator.hardwareConcurrency返回合理的CPU核心数(通常是4或8)navigator.languages返回的值,避免暴露异常的语言偏好navigator.plugins中的异常插件信息这些修改都是通过Proxy和Reflect API动态完成的,不会直接修改原生对象,因此很难被检测到。
要在Selenium中使用stealth.min.js,我们需要做几件事:
bash复制npx extract-stealth-evasions
python复制from selenium import webdriver
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option("useAutomationExtension", False)
# 其他推荐配置
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-popup-blocking")
chrome_options.add_argument("--disable-default-apps")
driver = webdriver.Chrome(options=chrome_options)
# 注入stealth.min.js
with open('stealth.min.js', 'r') as f:
stealth_js = f.read()
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": stealth_js
})
driver.get("https://target-website.com")
这些Chrome选项不是随便加的,每个都有特定作用:
--disable-blink-features=AutomationControlled:禁用Blink引擎的自动化控制特征excludeSwitches: ["enable-automation"]:隐藏"Chrome正受到自动测试软件控制"的提示useAutomationExtension: False:禁用自动化扩展我建议把这些配置都加上,因为它们能消除最明显的自动化特征。不过要注意,不同版本的Chrome可能需要不同的配置组合,有时候需要根据实际情况调整。
最方便的检测方法是访问专门的反爬检测页面,比如:
如果stealth.min.js工作正常,这些页面应该显示你是普通浏览器。我经常用sannysoft的检测页面做测试,它能检查几十种不同的浏览器特征。
除了用现成的检测网站,我们也可以自己写检测脚本:
javascript复制// 检查webdriver属性
if(navigator.webdriver) {
console.log('检测到自动化工具!')
}
// 检查插件数量
if(navigator.plugins.length === 0) {
console.log('插件列表异常!')
}
// 检查语言设置
if(!navigator.languages || navigator.languages.length === 0) {
console.log('语言设置异常!')
}
把这些脚本放到目标网站中执行,就能知道哪些特征可能暴露我们。在实际项目中,我建议先手动访问目标网站,用开发者工具执行这些检测脚本,了解网站的检测机制,然后再针对性地调整stealth.min.js的配置。
虽然stealth.min.js提供了很好的默认配置,但有时候我们需要更精细的控制。比如,不同地区的用户通常有不同的语言设置和时区。我们可以修改stealth.min.js,让这些信息更"自然":
javascript复制// 修改语言设置
const languages = ['zh-CN', 'zh', 'en-US', 'en']
navigator.__defineGetter__('languages', () => languages)
// 修改时区
const timezone = 'Asia/Shanghai'
Intl.DateTimeFormat().resolvedOptions().timeZone = timezone
一些高级的反爬系统会通过WebSocket检测自动化工具。普通浏览器的WebSocket连接会有特定的头信息和握手过程,而Selenium的WebSocket实现可能不太一样。这时候我们需要额外的工作:
python复制# 修改WebSocket行为
driver.execute_cdp_cmd("Network.enable", {})
driver.execute_cdp_cmd("Network.setExtraHTTPHeaders", {
"headers": {
"User-Agent": "Mozilla/5.0...",
"Accept-Language": "zh-CN,zh;q=0.9"
}
})
虽然stealth.min.js很强大,但也不要过度依赖它。我见过有人把所有反反爬技术都堆上去,结果反而因为行为太"完美"而被识别。实际上,普通用户的行为是有很多随机性和不完美之处的。建议:
记住,最好的隐藏不是完全消失,而是融入环境。就像变色龙不是变得透明,而是变得和周围环境一样。