如何在Scrapy中实现对请求Header的动态随机化处理_自定义Middleware重写request对象

2026-06-03 28945 日常编程

直接在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 解析出上一级域名或路径后拼接
  • 不要覆盖 AcceptAccept-Encoding 等 Scrapy 默认设置好的 header,否则可能影响 gzip 解压或响应解析

中间件里修改 request.headers 的正确写法

错误示范:request.headers = {'User-Agent': 'xxx'} —— 这会清空所有默认 header(包括 AcceptConnection 等),导致请求异常;正确做法是只更新需要变动的字段:

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 Forbiddenresponse.headers 里没 User-Agent 怎么查

这说明你的中间件根本没生效——常见原因有三个:

  • 中间件类没在 settings.pyDOWNLOADER_MIDDLEWARES 字典里注册,或优先级写成字符串而非整数(如 'myproject.middlewares.RandomHeaderMiddleware': '543' 错了,必须是 543
  • 中间件类名或路径写错,Python 导入失败但 Scrapy 默认静默忽略(可在中间件 __init__ 里加 print("loaded") 验证)
  • 请求对象在生成时显式传入了 headers 参数,且该字典里已有 User-Agent,而你用了 request.headers['User-Agent'] = ... 强制覆盖——这会破坏大小写敏感性(Scrapy 内部 header key 是 title-case),应统一用 setdefaultdel request.headers['User-Agent']; request.headers['User-Agent'] = ...

真正难调试的是 header 大小写和键是否存在之间的微妙差异,别依赖肉眼检查日志,用 print(dict(request.headers)) 看实际内容。

如何修复Python aiohttp请求时出现的ClientPayloadError?

ClientPayloadError表明服务端提前关闭连接,非客户端代码错误;常见于服务端超时、拒绝大响应或代理截断,需通过抓包、检查响应头、服务端日志及添加读取超时和异常处理来定位解决。 ClientPayloadError通常意味着服务端提前关闭了连接 这个错误不是客户端代码写错了,而是aiohttp在读取响应体时发现服务端已经断开了连接。常见于服务端超时、主动拒绝大响应、或代理/网关(如Ng...

如何在Python 3中正确判断一个对象是否为某个类的实例?

应优先使用isinstance()而非type()做类型判断,因isinstance()遵循MRO、支持继承与ABC,体现鸭子类型哲学;type()仅判别直接类型,适用极少数需严格身份校验的场景。 用isinstance()而不是type() 直接比较type(obj)==SomeClass在绝大多数场景下是错的——它无法识别子类实例。比如isinstance(42,int)返回True,但typ...

如何在Python中自定义NumPy的打印格式以方便调试?

np.set_printoptions()可直接控制NumPy数组显示格式;默认格式因小数位不一致、科学计数法混用及自动截断而影响调试效率;关键参数需协同设置,如precision控制有效位数、suppress禁用科学计数法、threshold避免省略、linewidth防换行混乱、floatmode='fixed'统一小数位;调试时应按目标切换组合,并注意全局生效与及时还原。 直接用np.set...

如何使用Python在TensorFlow中实现自定义优化器算法?

应避免直接继承tf.keras.optimizers.Optimizer,因其抽象基类设计依赖私有机制;推荐用tf.GradientTape+assign_sub手动实现更新逻辑,确保可控性与兼容性。 为什么不能直接继承tf.keras.optimizers.Optimizer? TensorFlow2.10+已将tf.keras.optimizers.Optimizer设为抽象基类,但它的子类(...

如何使用Python在TensorFlow中实现自定义的评估指标?

TensorFlow2.x自定义metric必须继承tf.keras.metrics.Metric类并实现__init__、update_state、result和reset_state方法,状态变量须用self.add_weight创建并在reset_state中手动清零,且必须传实例而非类名注册。 TensorFlow2.x中自定义metric类必须继承tf.keras.metrics.Met...

如何根据前一个连续段的值动态修改数值序列

本文介绍如何在pandas中识别数值连续段(streak),并基于前一段的值条件性地修改当前段——例如,将紧随“1”段之后的所有“2”段统一替换为1。 本文介绍如何在pandas中识别数值连续段(streak),并基于前一段的值条件性地修改当前段——例如,将紧随“1”段之后的所有“2”段统一替换为1。 在数据清洗与特征工程中,常需对具有上下文依赖的连续序列进行逻辑修正。本例的核心需求是:当某段连续...

Python中如何安全地执行动态字符串代码_使用ast.literal_eval代替eval

不能直接用eval,因为它会执行任意Python表达式,包括危险操作如os.system;ast.literal_eval仅安全解析str、int、list等无副作用字面量,不支持函数调用或运算符,且对语法格式极其严格。 为什么不能直接用eval 因为eval会执行任意Python表达式,包括调用函数、访问全局变量、甚至执行os.system("rm-rf/")这类危险操作。哪怕输入看起来“只是个...

Django 自定义认证后端未生效的排查与解决

Django中request.user始终为AnonymousUser,即使登录成功且session已存用户ID,通常是因为自定义认证后端未被正确调用——根本原因在于AUTHENTICATION_BACKENDS配置顺序错误,导致Django优先使用默认ModelBackend而跳过了你的邮箱登录逻辑。 django中`request.user`始终为`anonymoususer`,即使登录成功且...