当自动化脚本或爬虫频繁访问目标站点时,最容易暴露身份的信息往往来自请求头中的User-Agent字符串。服务器通过它可以快速判断客户端是真实浏览器还是脚本程序。但仅在请求头中轮换UA已经无法应对中等强度的反爬机制,因为网站还会进一步采集Canvas绘制结果、WebGL渲染参数、字体列表、屏幕尺寸和时区等浏览器指纹信息。这些指纹组合起来可以形成高维度的设备标识,即使更换IP和UA,只要指纹特征一致,依然会被识别为同一客户端。本文从这两个核心层面展开,先分析User-Agent轮换的可行性与局限,再给出浏览器指纹伪装的具体实现,最后介绍结合代理池的完整方案。

一、反爬检测为什么同时关注User-Agent和浏览器指纹
User-Agent字符串是HTTP请求头中的一个基础字段,它包含了浏览器名称、版本号、渲染引擎以及操作系统等信息。服务器解析UA后,可以迅速识别出异常客户端,例如某些脚本工具会默认发送类似python-requests/2.31.0的UA,这类请求一旦出现就会被直接拦截。因此,很多初级反爬系统只需要做一次UA格式校验,就能过滤掉大部分低质量爬虫。但这种方式并不牢固,因为UA可以任意伪造,只要从真实浏览器的请求头中复制一份即可绕过。
浏览器指纹则是一个完全不同的检测维度。它不依赖用户主动发送的标识,而是通过浏览器在页面中执行JavaScript来采集运行环境的诸多特征,比如Canvas绘图结果的像素差异、WebGL渲染器信息、安装的字体列表、屏幕分辨率、时区、语言偏好、插件枚举结果等。这些特征组合后具有高度唯一性,就像人的指纹一样,即使清理了Cookie或更换IP,只要浏览器环境不变,指纹依然能关联到同一客户端。因此,高级反爬系统会把UA校验和指纹校验结合起来,形成双重验证。
一个典型的检测逻辑是:先判断UA声明的浏览器类型,再与Canvas指纹、屏幕属性等进行匹配。例如UA声明为Windows上的Chrome 120,但屏幕分辨率却是1024x768,触控点数量为5,或者WebGL渲染器返回了与AMD显卡不同的结果,这时就会被标记为可疑。更严格的情况下,站点会记录某个指纹的历史访问行为,一旦发现同一指纹在短时间内更换了多个UA和IP,就会触发更高级的风控策略。这说明仅靠UA轮换无法真正解决问题,必须同步进行指纹伪装。
二、User-Agent轮换的实现方法与注意事项
对于基于requests库的轻量级爬虫,UA轮换的实现非常简单:维护一个真实UA列表,每次请求时随机选取一个,并填入headers字典即可。这样做可以应对那些只对UA做静态检查的站点,但要注意列表中的UA必须来自当前主流浏览器版本,不能使用已经停更的旧版本,否则反而会显得异常。同时最好一并设置Accept-Language、Accept-Encoding等常见头字段,让请求头整体看起来更像真实浏览器。
import random
import requests
ua_list = [
'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 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
]
headers = {
'User-Agent': random.choice(ua_list),
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
resp = requests.get('https://httpbin.org/headers', headers=headers, timeout=10)
print(resp.text)
如果使用Playwright、Selenium这类真实浏览器自动化工具,UA轮换就不能只改请求头了。以Playwright为例,可以创建不同的浏览器上下文,并在上下文中设置user_agent参数,这样浏览器发出的所有请求都会自动带上指定的UA。同时建议配套设置viewport、locale和timezone_id,让浏览器环境与UA声称的设备类型保持一致。否则一旦出现UA是桌面端但viewport却是手机尺寸的情况,反而更容易被识别。
from playwright.sync_api import sync_playwright
import random
ua_list = [
'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 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15'
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
user_agent=random.choice(ua_list),
viewport={'width': 1366, 'height': 768},
locale='zh-CN',
timezone_id='Asia/Shanghai'
)
page = context.new_page()
page.goto('https://browserleaks.com/ua')
print(page.title())
browser.close()
UA轮换的注意事项还包括:轮换频率不宜过快,不要每次请求都换一个新UA,否则会被识别为明显的自动化行为。更好的做法是模拟真实用户会话,在一个会话内保持UA不变,会话结束后再切换。此外,UA列表应与目标站点的实际用户分布相匹配,例如面向国内用户的网站,其主流UA为Chrome、Edge和Safari,而某些小众浏览器反而容易引起关注。
三、浏览器指纹伪装的核心维度与代码示例
浏览器指纹包含的维度非常多,常见的包括:Canvas指纹,通过绘制隐藏的图形并读取像素数据来区分显卡和浏览器差异;WebGL指纹,返回GPU厂商和渲染器信息;navigator对象中的language、platform、hardwareConcurrency、deviceMemory等属性;屏幕的宽高和颜色深度;系统字体列表;音频指纹;以及时区和插件列表。这些维度中哪怕只有一项与UA不一致,都可能被反爬系统判定为异常。
伪装浏览器指纹的基本思路是在页面加载前注入JavaScript,覆盖navigator对象上相关属性的getter返回值,或者修改Canvas相关的原型方法,使其返回统一或带随机噪声的结果。下面这段代码演示了如何通过Playwright的init_script功能,在页面脚本执行之前修改navigator.webdriver、languages和platform等属性。navigator.webdriver是自动化浏览器的一个明显特征,将它设置为undefined可以隐藏自动化痕迹。
from playwright.sync_api import sync_playwright
stealth_js = """
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'] });
Object.defineProperty(navigator, 'platform', { get: () => 'Win32' });
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters)
);
"""
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
page = context.new_page()
page.add_init_script(stealth_js)
page.goto('https://browserleaks.com/javascript')
print(page.title())
browser.close()
手动注入脚本虽然灵活,但要覆盖所有指纹维度非常困难,而且过度修改也可能造成新的异常。例如将Canvas指纹设置为固定值,但如果所有访客都返回同一个值,反而会形成新的聚类特征。因此在实际项目中,更推荐使用成熟的自动化伪装库,如undetected-chromedriver、playwright-stealth、puppeteer-extra-plugin-stealth等。这些工具会接管浏览器启动参数、修改CDP命令并自动处理大部分已知的指纹检测点。不过这些库也需要定期更新,因为反爬检测规则一直在演进。
四、UA轮换与指纹伪装结合代理池的实战流程
完整的反反爬方案不能只靠UA和指纹,IP地址的多样性同样重要。如果所有请求都来自同一个IP,即使UA和指纹各不相同,也很快会被目标站点封禁。因此,一个稳定的自动化采集系统通常会包含以下流程:先从一个代理池中获取可用IP,然后随机选择一个UA,再创建带有适配指纹的浏览器上下文,最后执行访问任务。任务完成后主动关闭上下文并释放代理,避免复用。
下面这个示例展示了如何从代理列表中随机选取一个代理IP,并结合UA和viewport创建一个Playwright上下文。代理格式通常为http://用户名:密码@IP:端口,在这里使用本地代理端口进行演示。实际使用时需要替换为真实可用的代理地址,并确保代理本身支持目标站点的并发访问。
import random
from playwright.sync_api import sync_playwright
proxy_list = ['http://user:pass@127.0.0.1:7890', 'http://user:pass@127.0.0.1:7891']
ua_list = [
'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 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15'
]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
proxy = random.choice(proxy_list)
context = browser.new_context(
user_agent=random.choice(ua_list),
proxy={'server': proxy},
viewport={'width': 1366, 'height': 768},
locale='zh-CN',
timezone_id='Asia/Shanghai'
)
page = context.new_page()
page.goto('https://ipinfo.io/json')
print(page.content())
browser.close()
在任务执行过程中,还需要建立异常检测与重试机制。当页面出现验证码、访问被重定向到登录或拦截页面、响应码异常(如403、429)时,应立即终止当前会话,更换代理IP和UA,并重新初始化浏览器上下文。可以设置一个失败上限,例如连续重试三次仍然失败则暂停该目标域名一段时间,避免触发更长时间的风控封禁。重试间隔加入随机延迟,可以让访问节奏更接近真实用户。
最后需要提醒的是,绕过反爬技术可能违反目标网站的服务条款,甚至触及法律风险。本文介绍的方法仅用于学习研究、测试自有系统或合法授权的数据采集场景。如果目标平台提供了官方API,应优先使用API获取数据,这样既稳定又合规。在正式部署任何自动化采集任务前,请务必确认目标网站允许此类行为,并控制请求频率,保护对方服务器资源。

