Python requests UA 配置:4 类常见问题与修正
做数据采集时,requests 库的 User-Agent 配置看似是最简单的一步——往 headers 字典里塞一个浏览器 UA 字符串就完事了。但实际跑起来,很多人会发现:明明 UA 设了,目标站点还是返回 403;或者跑了没几轮就被限制访问;又或者同一套代码换台机器结果不一样。
问题往往不在 UA 字符串本身,而在 UA 与请求头其他字段的协同、UA 的轮换策略、以及 UA 与底层连接指纹之间的关系。下面拆 4 类最高频的问题,逐个给修正方案。
UA 字符串与请求头其他字段不一致
这是最容易被忽略的问题。很多教程只教你设 User-Agent,但浏览器发请求时,Accept、Accept-Language、Accept-Encoding、sec-ch-ua 等字段会跟着一起走。如果 UA 声称是 Chrome 120,但请求头里 Accept-Language 缺失、Accept 还停留在 */*,目标站点的风控规则很容易判定这不是真实浏览器行为。
典型场景:电商选品采集,目标页面是商品详情页。代码里只设了 UA,没带其他请求头,采集几十条后开始返回验证页面。
修正方法:不要只设 UA,把整套浏览器请求头一起带上。核心字段包括:
import requests
headers = {
"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",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
}
resp = requests.get("https://example.com/product/123", headers=headers)
关键点:这些字段之间要保持逻辑一致。如果 UA 声称是 Chrome,sec-ch-ua 系列字段也要对应;如果 UA 是移动端浏览器,Accept 里就不该出现 application/xhtml+xml 这种桌面端特征。
单一固定 UA 反复使用
第二个常见问题:写好一个 UA 字符串,然后所有请求、所有时间都用同一个。这在低频场景下可能没问题,但一旦采集量上去——比如舆情监测场景下要同时抓几十个站点的数据——同一个 UA 在短时间内发起大量请求,行为特征非常明显。
很多站点的访问频率控制不仅看 IP,也会看 UA+IP 的组合。同一个 UA 从同一个出口 IP 发起 500 次请求,比 10 个不同 UA 轮换发 500 次请求更容易触发限制。
修正方法:建一个 UA 池,每次请求随机轮换。基础写法:
import random
ua_pool = [
# Chrome Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
# Chrome macOS
"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",
# Firefox Windows
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
# Edge Windows
"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",
# Safari macOS
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
]
headers = {
"User-Agent": random.choice(ua_pool),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
}
resp = requests.get(url, headers=headers)
但要注意:轮换 UA 时,对应的请求头也应该跟着 UA 走。如果这次选了 Firefox 的 UA,Accept-Encoding 里出现 br 就不太合理(Firefox 支持 br,但格式细节和 Chrome 有差异)。实际操作中,更稳妥的做法是把 UA 和它配套的请求头打包成一组,轮换时整组替换,而不是只换 UA 字符串。
UA 轮换无序导致行为特征异常
第三个问题藏得更深。有了 UA 池、开始轮换了,但轮换方式是 random.choice()——纯随机。表面上看每次请求 UA 不同,但如果短时间内的请求序列是:Chrome → Firefox → Safari → Chrome → Edge → Firefox,这个序列本身就是异常信号。
真实用户不会在 2 秒内用 5 个不同浏览器访问同一个网站。纯随机轮换在低频时有效,在高频采集时反而暴露了自动化特征。
修正方法:给轮换加一层策略控制。核心思路是让 UA 在一段时间内保持稳定,定期切换而非逐请求切换:
import random
import time
class UARotator:
def __init__(self, ua_pool, switch_interval=50):
self.ua_pool = ua_pool
self.switch_interval = switch_interval
self.current_ua = random.choice(ua_pool)
self.request_count = 0
def get_ua(self):
self.request_count += 1
if self.request_count >= self.switch_interval:
self.current_ua = random.choice(self.ua_pool)
self.request_count = 0
return self.current_ua
rotator = UARotator(ua_pool, switch_interval=30)
# 每 30 次请求换一次 UA,而不是每次都换
for url in url_list:
headers = {"User-Agent": rotator.get_ua()}
resp = requests.get(url, headers=headers)
time.sleep(random.uniform(1.0, 3.0))
这种做法模拟的是”一个用户用浏览器访问了 30 个页面,然后换了台设备继续”的行为模式,比纯随机更像真人。配合合理的请求间隔(1-3 秒随机),整体行为特征会自然很多。
忽略 UA 与底层连接指纹的协同
最后一个问题最隐蔽。就算 UA 完美、请求头完整、轮换策略合理,有些站点还是会识别出这是脚本。原因是:requests 库底层使用 urllib3,它的 TLS 握手指纹(JA3 指纹)和真实浏览器不同。目标站点如果用 TLS 指纹检测,光改 UA 字符串是没用的——UA 说自己是 Chrome,但 TLS 握手特征暴露了 urllib3。
这个问题的体感:换了 UA、换了 IP,访问还是被挡,而且用浏览器手动打开同一页面完全正常。
修正方法分两步:
第一步,确认是不是 TLS 指纹问题。用浏览器和 requests 分别请求同一 URL,对比返回状态。如果浏览器 200、requests 403,且 UA 和请求头都对齐过,基本可以判断是连接层面的问题。
第二步,换用 TLS 指纹更接近浏览器的 HTTP 客户端。最直接的方案是改用 curl_cffi 库,它的 TLS 握手指纹可以模拟 Chrome 或 Firefox:
from curl_cffi import requests as cffi_requests
# impersonate 参数模拟 Chrome 的 TLS 指纹
resp = cffi_requests.get(
"https://example.com/data",
impersonate="chrome120",
headers=headers,
)
如果不想换库,也可以用 requests + requests-toolbelt 配合自定义 TLS 配置,但效果不如 curl_cffi 直接。需要注意的是,换库之后 UA 和请求头仍然要保持一致——TLS 指纹解决了连接层的问题,应用层的请求头一致性仍然不能丢。
小结
UA 配置不是填一个字符串,而是一套需要多层协同的工程:
- UA 要和请求头其他字段保持逻辑一致,不能只设 UA 不设 Accept / Accept-Language
- 高频采集必须建 UA 池轮换,但轮换要有策略,不能纯随机
- 底层 TLS 指纹是 UA 之外的第二道坎,requests 原生指纹和浏览器不同,必要时换用 TLS 模拟能力更强的客户端
把这四层理清,requests 的 UA 配置才真正算”配好了”。
常见问题
requests 设置了 UA 还是返回 403,怎么排查?
按三层顺序排查:先检查 UA 是否带了配套请求头(Accept、Accept-Language 等),只设 UA 不设其他字段是最高频原因;再确认 UA 字符串是否过期,Chrome 大版本每 4-6 周迭代一次,用过老的版本号会被部分站点风控规则判定为异常;最后用浏览器手动访问同一 URL,如果浏览器也 403,说明是 IP 层面的问题而非 UA。
UA 池需要多大?多少个够用?
5-10 个不同浏览器+操作系统组合足够应对大多数场景。池太小(1-2 个)轮换没意义,池太大(50+)维护成本高且多数 UA 实际行为特征接近,收益递减。比池大小更重要的是轮换策略和请求间隔——30 个 UA 纯随机每秒切一次,不如 5 个 UA 每 30 次请求切一次。
curl_cffi 和 requests 能混用吗?
可以。常见做法是 requests 打底处理大部分请求,遇到 TLS 指纹检测的站点时切换到 curl_cffi。两者 API 接口接近,迁移成本低。需要注意的是 curl_cffi 的 session 管理和 requests 略有差异,建议封装一层统一接口,避免在业务代码里到处做条件判断。
UA 配置好后还需要配代理吗?
两者解决的是不同层面的问题。UA 和请求头管的是应用层身份,代理管的是网络层出口。高频采集场景下,UA 池+代理通常是配合使用的——单 IP 配多 UA 可以分散应用层特征,但 IP 层面仍然集中在同一出口,访问频率控制还是会生效。具体是否需要代理取决于目标站点的限制策略和采集量级。