直接在start_requests里改headers不行,因为该方法只执行一次且后续请求不经过它,导致所有请求共用同一组Header而易被识别为机器人;应使用Downloader Middleware的process_request动态设置User-Agent和Referer。
为什么直接在 start_requests 里改 headers 不行
因为 Scrapy 的请求调度是异步且复用的,start_requests 只在爬虫启动时执行一次,后续所有请求(包括 yield scrapy.Request(...)、response.follow()、重定向后的请求)都不会再走这里。硬编码或简单覆盖 headers 字典,会导致所有请求共用同一组 Header,极易被目标站点识别为机器人。
用 process_request 中间件动态注入随机 User-Agent 和 Referer
Scrapy 中间件的 process_request 是每个请求发出前的最后钩子,此时 request 对象已创建但尚未发送,可安全修改其 headers 属性。关键点在于:不能直接赋值新字典(会丢掉 Scrapy 默认 header),而要用 request.headers.setdefault() 或 .update() 增量覆盖。
-
User-Agent必须每次不同,建议从本地列表或第三方库(如fake-useragent)中随机取;注意fake-useragent在并发高时可能触发反爬或报错,生产环境更推荐预加载静态列表 -
Referer要与当前请求 URL 的上级路径匹配,否则某些站点会拒绝响应;可基于request.url解析出上一级域名或路径后拼接 - 不要覆盖
Accept、Accept-Encoding等 Scrapy 默认设置好的 header,否则可能影响 gzip 解压或响应解析
中间件里修改 request.headers 的正确写法
错误示范:request.headers = {'User-Agent': 'xxx'} —— 这会清空所有默认 header(包括 Accept、Connection 等),导致请求异常;正确做法是只更新需要变动的字段:
def process_request(self, request, spider):
ua_list = ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', ...]
request.headers.setdefault('User-Agent', random.choice(ua_list))
if 'Referer' not in request.headers:
parsed = urllib.parse.urlparse(request.url)
referer = f"{parsed.scheme}://{parsed.netloc}/"
request.headers.setdefault('Referer', referer)
注意:setdefault 只在 key 不存在时才设值,避免覆盖手动传入的 headers(比如你调用 scrapy.Request(url, headers={'X-Token': 'abc'}) 时希望保留该字段)。
遇到 403 Forbidden 且 response.headers 里没 User-Agent 怎么查
这说明你的中间件根本没生效——常见原因有三个:
- 中间件类没在
settings.py的DOWNLOADER_MIDDLEWARES字典里注册,或优先级写成字符串而非整数(如'myproject.middlewares.RandomHeaderMiddleware': '543'错了,必须是543) - 中间件类名或路径写错,Python 导入失败但 Scrapy 默认静默忽略(可在中间件
__init__里加print("loaded")验证) - 请求对象在生成时显式传入了
headers参数,且该字典里已有User-Agent,而你用了request.headers['User-Agent'] = ...强制覆盖——这会破坏大小写敏感性(Scrapy 内部 header key 是 title-case),应统一用setdefault或del request.headers['User-Agent']; request.headers['User-Agent'] = ...
真正难调试的是 header 大小写和键是否存在之间的微妙差异,别依赖肉眼检查日志,用 print(dict(request.headers)) 看实际内容。
就爱读