中间件是Scrapy里面的一个核心概念。使用中间件能够在爬虫的请求发起以前或者请求返回以后对数据进行定制化修改,从而开发出适应不一样状况的爬虫。html
“中间件”这个中文名字和前面章节讲到的“中间人”只有一字之差。它们作的事情确实也很是类似。中间件和中间人都能在中途劫持数据,作一些修改再把数据传递出去。不一样点在于,中间件是开发者主动加进去的组件,而中间人是被动的,通常是恶意地加进去的环节。中间件主要用来辅助开发,而中间人却多被用来进行数据的窃取、伪造甚至攻击。java
在Scrapy中有两种中间件:下载器中间件(Downloader Middleware)和爬虫中间件(Spider Middleware)。python
这一篇主要讲解下载器中间件的第一部分。android
Scrapy的官方文档中,对下载器中间件的解释以下。redis
下载器中间件是介于Scrapy的request/response处理的钩子框架,是用于全局修改Scrapy request和response的一个轻量、底层的系统。数据库
这个介绍看起来很是绕口,但其实用容易理解的话表述就是:更换代理IP,更换Cookies,更换User-Agent,自动重试。json
若是彻底没有中间件,爬虫的流程以下图所示。小程序
使用了中间件之后,爬虫的流程以下图所示。bash
在爬虫开发中,更换代理IP是很是常见的状况,有时候每一次访问都须要随机选择一个代理IP来进行。cookie
中间件自己是一个Python的类,只要爬虫每次访问网站以前都先“通过”这个类,它就能给请求换新的代理IP,这样就能实现动态改变代理。
在建立一个Scrapy工程之后,工程文件夹下会有一个middlewares.py文件,打开之后其内容以下图所示。
Scrapy自动生成的这个文件名称为middlewares.py,名字后面的s表示复数,说明这个文件里面能够放不少个中间件。Scrapy自动建立的这个中间件是一个爬虫中间件,这种类型在第三篇文章会讲解。如今先来建立一个自动更换代理IP的中间件。
在middlewares.py中添加下面一段代码:
class ProxyMiddleware(object):
def process_request(self, request, spider):
proxy = random.choice(settings['PROXIES'])
request.meta['proxy'] = proxy
复制代码
要修改请求的代理,就须要在请求的meta里面添加一个Key为proxy,Value为代理IP的项。
因为用到了random和settings,因此须要在middlewares.py开头导入它们:
import random
from scrapy.conf import settings
复制代码
在下载器中间件里面有一个名为process_request()
的方法,这个方法中的代码会在每次爬虫访问网页以前执行。
打开settings.py,首先添加几个代理IP:
PROXIES = ['https://114.217.243.25:8118',
'https://125.37.175.233:8118',
'http://1.85.116.218:8118']
复制代码
须要注意的是,代理IP是有类型的,须要先看清楚是HTTP型的代理IP仍是HTTPS型的代理IP。若是用错了,就会致使没法访问。
中间件写好之后,须要去settings.py中启动。在settings.py中找到下面这一段被注释的语句:
# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'AdvanceSpider.middlewares.MyCustomDownloaderMiddleware': 543,
#}
复制代码
解除注释并修改,从而引用ProxyMiddleware。修改成:
DOWNLOADER_MIDDLEWARES = {
'AdvanceSpider.middlewares.ProxyMiddleware': 543,
}
复制代码
这其实就是一个字典,字典的Key就是用点分隔的中间件路径,后面的数字表示这种中间件的顺序。因为中间件是按顺序运行的,所以若是遇到后一个中间件依赖前一个中间件的状况,中间件的顺序就相当重要。
如何肯定后面的数字应该怎么写呢?最简单的办法就是从543开始,逐渐加一,这样通常不会出现什么大问题。若是想把中间件作得更专业一点,那就须要知道Scrapy自带中间件的顺序,如图下图所示。
数字越小的中间件越先执行,例如Scrapy自带的第1个中间件RobotsTxtMiddleware
,它的做用是首先查看settings.py中ROBOTSTXT_OBEY
这一项的配置是True
仍是False
。若是是True
,表示要遵照Robots.txt协议,它就会检查将要访问的网址能不能被运行访问,若是不被容许访问,那么直接就取消这一次请求,接下来的和此次请求有关的各类操做所有都不须要继续了。
开发者自定义的中间件,会被按顺序插入到Scrapy自带的中间件中。爬虫会按照从100~900的顺序依次运行全部的中间件。直到全部中间件所有运行完成,或者遇到某一个中间件而取消了此次请求。
Scrapy其实自带了UA中间件(UserAgentMiddleware)、代理中间件(HttpProxyMiddleware)和重试中间件(RetryMiddleware)。因此,从“原则上”说,要本身开发这3个中间件,须要先禁用Scrapy里面自带的这3个中间件。要禁用Scrapy的中间件,须要在settings.py里面将这个中间件的顺序设为None:
DOWNLOADER_MIDDLEWARES = {
'AdvanceSpider.middlewares.ProxyMiddleware': 543,
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware': None
}
复制代码
为何说“原则上”应该禁用呢?先查看Scrapy自带的代理中间件的源代码,以下图所示:
从上图能够看出,若是Scrapy发现这个请求已经被设置了代理,那么这个中间件就会什么也不作,直接返回。所以虽然Scrapy自带的这个代理中间件顺序为750,比开发者自定义的代理中间件的顺序543大,可是它并不会覆盖开发者本身定义的代理信息,因此即便不由用系统自带的这个代理中间件也没有关系。
完整地激活自定义中间件的settings.py的部份内容以下图所示。
配置好之后运行爬虫,爬虫会在每次请求前都随机设置一个代理。要测试代理中间件的运行效果,可使用下面这个练习页面:
http://exercise.kingname.info/exercise_middleware_ip
复制代码
这个页面会返回爬虫的IP地址,直接在网页上打开,以下图所示。
这个练习页支持翻页功能,在网址后面加上“/页数”便可翻页。例如第100页的网址为:
http://exercise.kingname.info/exercise_middleware_ip/100
复制代码
使用了代理中间件为每次请求更换代理的运行结果,以下图所示。
代理中间件的可用代理列表不必定非要写在settings.py里面,也能够将它们写到数据库或者Redis中。一个可行的自动更换代理的爬虫系统,应该有以下的3个功能。
开发UA中间件和开发代理中间件几乎同样,它也是从settings.py配置好的UA列表中随机选择一项,加入到请求头中。代码以下:
class UAMiddleware(object):
def process_request(self, request, spider):
ua = random.choice(settings['USER_AGENT_LIST'])
request.headers['User-Agent'] = ua
复制代码
比IP更好的是,UA不会存在失效的问题,因此只要收集几十个UA,就能够一直使用。常见的UA以下:
USER_AGENT_LIST = [
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
"Dalvik/1.6.0 (Linux; U; Android 4.2.1; 2013022 MIUI/JHACNBL30.0)",
"Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; HUAWEI MT7-TL00 Build/HuaweiMT7-TL00) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
"AndroidDownloadManager",
"Apache-HttpClient/UNAVAILABLE (java 1.4)",
"Dalvik/1.6.0 (Linux; U; Android 4.3; SM-N7508V Build/JLS36C)",
"Android50-AndroidPhone-8000-76-0-Statistics-wifi",
"Dalvik/1.6.0 (Linux; U; Android 4.4.4; MI 3 MIUI/V7.2.1.0.KXCCNDA)",
"Dalvik/1.6.0 (Linux; U; Android 4.4.2; Lenovo A3800-d Build/LenovoA3800-d)",
"Lite 1.0 ( http://litesuits.com )",
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0",
"Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; HTC T528t Build/JRO03H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30; 360browser(securitypay,securityinstalled); 360(android,uppayplugin); 360 Aphone Browser (2.0.4)",
]
复制代码
配置好UA之后,在settings.py下载器中间件里面激活它,并使用UA练习页来验证UA是否每一次都不同。练习页的地址为:
http://exercise.kingname.info/exercise_middleware_ua。
复制代码
UA练习页和代理练习页同样,也是能够无限制翻页的。
运行结果以下图所示。
对于须要登陆的网站,可使用Cookies来保持登陆状态。那么若是单独写一个小程序,用Selenium持续不断地用不一样的帐号登陆网站,就能够获得不少不一样的Cookies。因为Cookies本质上就是一段文本,因此能够把这段文本放在Redis里面。这样一来,当Scrapy爬虫请求网页时,能够从Redis中读取Cookies并给爬虫换上。这样爬虫就能够一直保持登陆状态。
如下面这个练习页面为例:
http://exercise.kingname.info/exercise_login_success
复制代码
若是直接用Scrapy访问,获得的是登陆界面的源代码,以下图所示。
如今,使用中间件,能够实现彻底不改动这个loginSpider.py里面的代码,就打印出登陆之后才显示的内容。
首先开发一个小程序,经过Selenium登陆这个页面,并将网站返回的Headers保存到Redis中。这个小程序的代码以下图所示。
这段代码的做用是使用Selenium和ChromeDriver填写用户名和密码,实现登陆练习页面,而后将登陆之后的Cookies转换为JSON格式的字符串并保存到Redis中。
接下来,再写一个中间件,用来从Redis中读取Cookies,并把这个Cookies给Scrapy使用:
class LoginMiddleware(object):
def __init__(self):
self.client = redis.StrictRedis()
def process_request(self, request, spider):
if spider.name == 'loginSpider':
cookies = json.loads(self.client.lpop('cookies').decode())
request.cookies = cookies
复制代码
设置了这个中间件之后,爬虫里面的代码不须要作任何修改就能够成功获得登陆之后才能看到的HTML,如图12-12所示。
若是有某网站的100个帐号,那么单独写一个程序,持续不断地用Selenium和ChromeDriver或者Selenium 和PhantomJS登陆,获取Cookies,并将Cookies存放到Redis中。爬虫每次访问都从Redis中读取一个新的Cookies来进行爬取,就大大下降了被网站发现或者封锁的可能性。
这种方式不只适用于登陆,也适用于验证码的处理。
这一篇就讲到这里,在下一篇,咱们将会介绍如何在下载器中间件中集成Selenium,进行请求重试和处理异常。
本文节选自个人新书《Python爬虫开发 从入门到实战》完整目录能够在京东查询到 item.jd.com/12436581.ht…
买不买书不重要,重要的是请关注个人公众号:未闻 Code
公众号已经连续日更三个多月了。在接下来的很长时间里也会连续日更。