必须在Downloader Middleware中动态设置随机User-Agent,通过自定义中间件重写process_request方法,用random.choice从UA池选取并覆盖request.headers['User-Agent'],确保启用顺序早于RetryMiddleware。

Downloader Middleware里怎么加随机UA
Scrapy默认用固定UA(Scrapy/2.x.x),目标站一查就封。必须在下载器中间件里动态替换request.headers['User-Agent'],而不是只改settings.py里的静态配置。
实操建议:
- 写一个自定义中间件类,重写
process_request方法,在里面覆盖request.headers['User-Agent'] - UA池别硬编码——用列表或从文件读,推荐用
random.choice()选,别用random.randint()手动索引(容易越界) - 务必检查
if request.headers.get('User-Agent')是否已存在,避免重复覆盖导致调试时误以为没生效 - 中间件启用顺序很重要:
DOWNLOADER_MIDDLEWARES里数值越小越先执行,确保你的UA中间件在RetryMiddleware之前(否则重试时UA又变回默认)
为什么不能只靠settings.py的USER_AGENT
USER_AGENT配置项是全局静态值,所有请求共用同一个字符串。反爬系统只要看到连续多个请求UA完全一致,立刻打上“脚本流量”标签。
常见错误现象:
立即学习“Python免费学习笔记(深入)”;
- 明明写了
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',但抓几页就被封——因为没换 - 开了
ROTATING_PROXY却忘了换UA,代理轮着换,UA原地不动,照样被识别 - 把UA写死在
start_requests里,结果follow出来的Request不带UA,回落到默认值
根本原因:Scrapy的Request对象初始化时若未显式传headers,就继承DEFAULT_REQUEST_HEADERS或全局USER_AGENT,而这两者都是静态的。
随机UA中间件的三个易错点
很多人写了中间件但还是被封,问题常出在细节上:
-
process_request里没写return None(或直接不写return),导致Scrapy误判为“该请求已被处理完毕”,后续中间件和下载器全跳过 - UA字符串里混入不可见字符(比如从网页复制来的空格、全角符号),发出去后HTTP头格式错误,目标站直接返回400
- 用了过于冷门或版本陈旧的UA(如
Opera/9.80),触发WAF的“异常客户端指纹”规则,比不用UA还容易被拦 - 没控制UA更新频率——比如每秒换10次,但实际UA池只有5个,很快开始重复,失去随机意义
简单可用的中间件示例
以下代码可直接粘贴到middlewares.py,注意替换你的UA列表:
class RandomUserAgentMiddleware:
def __init__(self):
self.user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36',
]
def process_request(self, request, spider):
ua = random.choice(self.user_agents)
request.headers['User-Agent'] = ua
然后在settings.py里启用:
DOWNLOADER_MIDDLEWARES = {'myproject.middlewares.RandomUserAgentMiddleware': 543}
数值543只是示例,只要比scrapy.downloadermiddlewares.useragent.UserAgentMiddleware(默认500)小就行,确保它先执行。
真实项目里UA池至少20+条,且应包含移动端、主流浏览器最新版,别凑数。换UA只是基础动作,真要稳定爬,还得配IP轮换、请求间隔、Referer模拟——但那是另一层的事了。










