Scrapy入门到放弃04:下载器中间件,让爬虫更完美
前言
MiddleWare,顾名思义,中间件。主要处理请求(例如添加代理IP、添加请求头等)和处理响应
本篇文章主要讲述下载器中间件的概念,以及如何使用中间件和自定义中间件。
MiddleWare分类
依旧是那张熟悉的架构图。

从图中看,中间件主要分为两类:
- Downloader MiddleWare:下载器中间件
- Spider MiddleWare:Spider中间件
本篇文主要介绍下载器中间件,先看官方的定义:
下载器中间件是介于Scrapy的request/response处理的钩子框架。 是用于全局修改Scrapy request和response的一个轻量、底层的系统。
作用
如架构图中所描述的一样,下载器中间件位于engine和下载器之间。engine将未处理的请求发送给下载器的时候,会经过下载器中间件,这时候在中间件里可以包装请求,例如修改请求头信息(设置UA、cookie等)和添加代理IP。
当下载器将网站的响应发送给engine的时候,也会经过下载器中间件,这里我们就可以对响应内容进行处理。
内置下载器中间件
Scrapy内置了很多下载器中间件供开发者使用。当我们启动一个Scrapy爬虫时,Scrapy会自动帮助我们启用这些中间件。如图:

图中就是在启动Scrapy程序时控制台打印的日志信息,我们发现Scrapy帮我们启用了很多下载器中间件和Spider中间件。
这里,先看看这些内置的中间件是如何发挥作用的?
RetryMiddleware
其实,这些内置中间件是和settings中的配置配套使用的。这里就拿RetryMiddleware为例。它的作用主要是:当请求失败时,可以根据RETRY_ENABLED和RETRY_TIMES配置来启用重试策略以及决定重试次数。就酱!!
那么问题又来了,这么多中间件,我去哪里找这个settings配置和中间件的对应关系啊??
这里我的方法有两种:
- 去官方文档,上篇文章有链接
- 看源码注释,在scrapy包下的都有中间件对应的py文件

注释里面写的明明白白,代码中获取的参数也一览无余。
自定义中间件
有时候,内置的中间件满足不了自己的需求,所以我们就要自力更生,自定义中间件。所有的中间件都在middlewares.py中进行定义。
我们打开middlewares.py,发现里面已经自动生成了一个下载器中间件和Spider中间件。
先看自生成的下载器中间件模板:

可以看到里面主要有五个方法:
- from_crawler:类方法,用于初始化中间件
- process_request:每个request通过下载中间件时,都会调用该方法,对应架构图步骤4
- process_response:处理下载器返回的响应内容,对应架构图步骤7
- process_exception:当下载器或者处理请求异常时,调用此方法
- spider_opened:内置的信号量回调方法,这里先不关注,先不关注!
这里主要关注3,顺带了解一下4、5。
process_request()
此方法有两个参数:
- request:spider发起的需要处理的request
- spider:该request对应的spider,暂定信号量细讲这个对象
def process_request(self, request, spider):
# Called for each request that goes through the downloader middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
return None
这里主要是为了让大家看注释,看注释的目的是为了告诉大家:此方法必须返回值。
- None:基本上用的都是这个返回值。表示这个请求可以进去下一个中间件进行处理了。
- request:停止调用process_request方法,并重新将request放回队列重新调度
- response:不会调用其他的 process_request,直接返回response,执行process_response。
还有一个是raise抛出异常,其实基本上返回值都用None,其他的目前可以仅做了解,有兴趣的可以自己探索一下。
process_response()
此方法有三个参数:
- request:response所对应的request
- response:被处理的response
- spider:response所对应的spider
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
一样是看注释,返回值有两个:
- response:下载器返回的响应内容,在各个中间件的process_response处理
- request:停止调用process_response方法,响应不会到达spider,并重新将request放回队列重新调度
这里记住,只要return response就行。
process_exception()
def process_exception(self, request, exception, spider):
# Called when a download handler or a process_request()
# (from other downloader middleware) raises an exception.
# Must either:
# - return None: continue processing this exception
# - return a Response object: stops process_exception() chain
# - return a Request object: stops process_exception() chain
pass
此方法就是当上面两个方法抛出异常的时候就会进入此方法,返回值有三个,意思和上面的差不多,用None就行。
启用和禁用中间件
自定义的中间件,有时候会和内置中间件功能重复,也担心功能上互相覆盖。所以这里我们可以选择,在配置中关掉内置中间件。
我个人比较喜欢自定义User-Agent中间件,但是Scrapy内置UserAgentMiddleware中间件,这就冲突了。如果内置中间件执行优先级低,后执行的话,则内置的UA就会覆盖自定义的UA。所以,我们需要关掉这个内置中UA中间件。
DOWNLOADER_MIDDLEWARES参数用来设置下载器中间件。其中,Key为中间件路径,Value为中间件执行优先级,数字越小,越先执行,当Value为None时,表示禁用。
# settings.py
DOWNLOADER_MIDDLEWARES = {
# 禁用默认的useragent插件
'scrapy.downloadermiddleware.useragent.UserAgentMiddleware': None,
# 启用自定义的中间件
'ScrapyDemo.middlewares.VideospiderDownloaderMiddleware': 543,
}
这样,内置的UA中间件则被禁用。
调用优先级
其次我们要明确的是:中间件是链式调用,一个请求会根据中间件的优先级,先后经过每个中间件,响应也是。

上面也说了,每个中间件都会设置一个执行优先级,数字越小越先执行。例如中间件1的优先级设置为200,中间件2的优先级设置为300。
当spider发起一个请求时,request会先经过中间件1的process_request进行处理,然后到达中间件2的此方法进行处理,当经过所有的中间件的此方法处理之后,最后到达下载器进行网站请求,然后返回响应内容。
process_response就是逆序处理,先到达中间件2的此方法,再到达中间件1,最后响应返回spider中,由开发者处理。
实践
这里我们自定义一个下载器中间件,来添加User-Agent。
自定义中间件
在middlewares.py中定义一个中间件:
class CustomUserAgentMiddleWare(object):
def process_request(self, request, spider):
request.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36'
return None
def process_response(self, request, response, spider):
print(request.headers['User-Agent'])
return response
启用中间件
为了直观,我们不修改settings.py全局配置,依旧使用代码内局部配置。
import scrapy
class DouLuoDaLuSpider(scrapy.Spider):
name = 'DouLuoDaLu'
allowed_domains = ['v.qq.com']
start_urls = ['https://v.qq.com/detail/m/m441e3rjq9kwpsc.html']
custom_settings = {
'DOWNLOADER_MIDDLEWARES': {
# 禁用默认的useragent插件
'scrapy.downloadermiddleware.useragent.UserAgentMiddleware': None,
# 启用自定义的中间件
'ScrapyDemo.middlewares.CustomUserAgentMiddleWare': 400
}
}
def parse(self, response):
pass
这里首先禁用了默认的UA中间件,然后启用了自定义的UA中间件。并且我在最后一行打上断点,Debug看UA是否设置成功。
测试结果
Debug模式启动程序,这里先把自定义的UA中间件禁用。

如图,request的UA是Scrapy。我们将注释去掉,启动UA中间件,再次启动程序测试。

如图,request的UA已经变成我在中间件中设置的UA了。
设置代理IP
依旧是在process_request方法中设置代理IP。
代码如下:
request.meta["proxy"] = 'http://ip:port'
结语
下载器中间件主要的功能还是包装请求,我个人自定义下载器中间件都是用来动态设置UA和实时检测更换代理IP。至于其他的场景需求,内置的下载器中间件基本上够用。
当然,不去学习下载器中间件这一块的知识同样可以开发Scrapy爬虫,但是下载器中间件会让你的爬虫更加完美。
本来想把下载器中间件和Spider中间件写在一篇中,但是知识点太碎,不好排版,而且还容易混淆,所以Spider中间件就留在下一篇写,期待下一次相遇。
95后小程序员,写的都是日常工作中的亲身实践,置身于初学者的角度从0写到1,详细且认真。文章会在公众号 [入门到放弃之路] 首发,期待你的关注。

Scrapy入门到放弃04:下载器中间件,让爬虫更完美的更多相关文章
- Scrapy入门到放弃06:Spider中间件
前言 写一写Spider中间件吧,都凌晨了,一点都不想写,主要是也没啥用...哦不,是平时用得少.因为工作上的事情,已经拖更好久了,这次就趁着半夜写一篇. Scrapy-deltafetch插件是在S ...
- scrapy中的下载器中间件
scrapy中的下载器中间件 下载中间件 下载器中间件是介于Scrapy的request/response处理的钩子框架. 是用于全局修改Scrapy request和response的一个轻量.底层 ...
- Scrapy学习篇(十)之下载器中间件(Downloader Middleware)
下载器中间件是介于Scrapy的request/response处理的钩子框架,是用于全局修改Scrapy request和response的一个轻量.底层的系统. 激活Downloader Midd ...
- Scrapy下载器中间件用法示例
1.爬虫文件httpbin.py # -*- coding: utf-8 -*- import scrapy class HttpbinSpider(scrapy.Spider): name = 'h ...
- scrapy 下载器中间件 随机切换user-agent
下载器中间件如下列表 ['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware', 'scrapy.downloadermiddlewa ...
- scrapy入门到放弃02:整一张架构图,开发一个程序
前言 Scrapy开门篇写了一些纯理论知识,这第二篇就要直奔主题了.先来讲讲Scrapy的架构,并从零开始开发一个Scrapy爬虫程序. 本篇文章主要阐述Scrapy架构,理清开发流程,掌握基本操作. ...
- Scrapy入门到放弃03:理解settings配置,监控Scrapy引擎
前言 代码未动,配置先行.本篇文章主要讲述一下Scrapy中的配置文件settings.py的参数含义,以及如何去获取一个爬虫程序的运行性能指标. 这篇文章无聊的一匹,没有代码,都是配置化的东西,但是 ...
- Scrapy入门到放弃05:让Item在Pipeline中飞一会儿
前言 "又回到最初的起点,呆呆地站在镜子前". 本来这篇是打算写Spider中间件的,但是因为这一块涉及到Item,所以这篇文章先将Item讲完,顺便再讲讲Pipeline,然后再 ...
- Elasticsearch从入门到放弃:分词器初印象
Elasticsearch 系列回来了,先给因为这个系列关注我的同学说声抱歉,拖了这么久才回来,这个系列虽然叫「Elasticsearch 从入门到放弃」,但只有三篇就放弃还是有点过分的,所以还是回来 ...
随机推荐
- UnityWebRequest使用总结
using System.Collections; using UnityEngine; using UnityEngine.Networking; /// <summary> /// 网 ...
- 模拟windows10计算器的实现
用户界面部分: import java.awt.*; import java.awt.event.*; import java.io.*; import java.util.HashMap; impo ...
- 重新整理 .net core 实践篇————网关[三十六]
前言 简单整理一下网关. 正文 在介绍网关之前,介绍一下BFF,BFF全称是Backend For Frontend,它负责认证授权,服务聚合,目标是为前端提供服务. 说的通透一点,就是有没有见过这种 ...
- [心得体会]springmvc在requestbody注解下使用jackson转化日期格式
使用WebMvcConfigurer的方法将converter注入到项目中 @Configurationpublic class ConverterConfig implements WebMvcCo ...
- leetcode第157场周赛5213
当时居然没想到,我真菜,就当记录一下了. 思路分析: 分为两种: 第一种:走两步的,无论是奇或偶的位置,只要走两步,代价为0: 第二种:走一步的,就是偶数到奇数,或者奇数到偶数,代价为1: 那么实际上 ...
- docker起不来报错:Failed to start Docker Application Container Engine.
报错信息如下: [root@localhost localdisk]# systemctl restart docker Job for docker.service failed because t ...
- 第九章 身体质量指数BMI的python实现
身体质量指数BMI:对身体质量的刻画(Body Mass Index) 国际上常用的衡量人体肥胖和健康程度的重要标准,主要用于统计分析 定义: BMI=体重(kg)/身高^2(m2) 提出问题: 实例 ...
- WPF教程二:理解WPF的布局系统和常用的Panel布局
WPF的布局系统 了解元素的测量和排列方式是理解布局的第一步.在测量(measure)阶段容器遍历所有子元素,并询问子元素它们所期望的尺寸.在排列(arrange)阶段,容器在合适的位置放置子元素.理 ...
- netcore3.1 + vue (前后端分离)生成PDF(多pdf合并)返回前端打印
1.使用Adobe Acrobat XI Pro编辑pdf模板 2.公共类代码 3.service层调用 4.Controller层 5.前端(Vue) 因为print.js不支持宋体,所以打算用后台 ...
- mongodb常用查询语句(转)
1.查询所有记录 db.userInfo.find();相当于:select* from userInfo; 2.查询去掉后的当前聚集集合中的某列的重复数据db.userInfo.distinct(& ...