一.首先我们要了解的是:
请求出去的时候走:process_request
响应回来的时候走:process_response
引擎会按顺序经过所有中间件,每个中间件都能拦截、修改、重定向请求 / 响应。
二.详细介绍:process_request
process_request(请求出去时触发)
什么时候触发?
爬虫发起请求 → 还没发到下载器 → 先经过这个方法
它的返回值只有 3 种情况:
- 返回 None(最常用):return None
或者不写 return
✅ 意思:放行!
请求继续往下走,传给下一个中间件,最终交给下载器发请求。
- 返回 Response 对象:return Response("我是假响应")
❌ 意思:拦截!不发请求了!
直接自己造一个响应返回给爬虫,根本不会访问网站。
- 返回 Request 对象
return Request("新的URL")
🔁 意思:替换请求!
把当前请求扔掉,换成新请求,丢回调度器重新排队。 后面的中间件不再执行!
三、详细介绍:process_response
process_response(响应回来时触发)
什么时候触发?
下载器拿到网页响应 → 回传给爬虫前 → 经过这个方法
它的返回值只有 2 种情况:
- 返回 Response 对象:return response
✅ 意思:放行响应!
响应继续往下走,最终交给爬虫的 parse 函数处理。
- 返回 Request 对象:return Request("新URL")
🔁 意思:扔掉响应,重新发请求!
当前响应不用了,把新请求丢回调度器。 后面的中间件不再执行!
四.二者总结
process_request(请求出去)
return None → 放行,继续发请求
return Response → 拦截,直接返回假页面
return Request → 换个请求重新发
process_response(响应回来)
return Response → 放行,交给爬虫解析
return Request → 不想要这个响应,重新请求
五.拓展疑问
真实需求: 同一个中间件 有的爬虫要经过 有的爬虫不要经过 而且: 不能直接在中间件开头 return None,不然所有爬虫都跳过了! 我该怎么做呢???
解决方案: 用 spider.name 判断! 核心思路: 在中间件里,判断当前是哪个爬虫在运行
是需要的爬虫 → 执行中间件逻辑
是不需要的爬虫 → 直接 return None 跳过
代码示例:
class UserAgentDownloaderMiddleware:
USER_AGENTS_LIST = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
# ... 你的UA列表
]
def process_request(self, request, spider):
"""
spider 就是当前正在运行的爬虫!
"""
# 如果爬虫名字 不是 qingting → 直接跳过!
if spider.name != "qingting":
return None # 跳过中间件,直接放行
# 只有爬虫叫 qingting 的时候,才会执行到这里
user_agent = random.choice(self.USER_AGENTS_LIST)
request.headers['User-Agent'] = user_agent
return None
更灵活的写法:
def process_request(self, request, spider):
# 白名单:这些爬虫使用UA中间件
allowed_spiders = ["qingting", "music", "news"]
if spider.name not in allowed_spiders:
return None # 不在白名单,直接跳过
# 执行中间件逻辑...