中间件是Scrapy里面的一个核心概念。使用中间件可以在爬虫的请求发起之前或者请求返回之后对数据进行定制化修改,从而开发出适应不同情况的爬虫。

“中间件”这个中文名字和前面章节讲到的“中间人”只有一字之差。它们做的事情确实也非常相似。中间件和中间人都能在中途劫持数据,做一些修改再把数据传递出去。不同点在于,中间件是开发者主动加进去的组件,而中间人是被动的,一般是恶意地加进去的环节。中间件主要用来辅助开发,而中间人却多被用来进行数据的窃取、伪造甚至攻击。

在Scrapy中有两种中间件:下载器中间件(Downloader Middleware)和爬虫中间件(Spider Middleware)。

这一篇主要讲解下载器中间件的第一部分。

下载器中间件

Scrapy的官方文档中,对下载器中间件的解释如下。

下载器中间件是介于Scrapy的request/response处理的钩子框架,是用于全局修改Scrapy request和response的一个轻量、底层的系统。

这个介绍看起来非常绕口,但其实用容易理解的话表述就是:更换代理IP,更换Cookies,更换User-Agent,自动重试。

如果完全没有中间件,爬虫的流程如下图所示。

使用了中间件以后,爬虫的流程如下图所示。

开发代理中间件

在爬虫开发中,更换代理IP是非常常见的情况,有时候每一次访问都需要随机选择一个代理IP来进行。

中间件本身是一个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个功能。

  1. 有一个小爬虫ProxySpider去各大代理网站爬取免费代理并验证,将可以使用的代理IP保存到数据库中。
  2. 在ProxyMiddlerware的process_request中,每次从数据库里面随机选择一条代理IP地址使用。
  3. 周期性验证数据库中的无效代理,及时将其删除。 由于免费代理极其容易失效,因此如果有一定开发预算的话,建议购买专业代理机构的代理服务,高速而稳定。

开发UA中间件

开发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中间件

对于需要登录的网站,可以使用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,进行请求重试和处理异常。

作者:青南

链接:https://juejin.im/post/5bf20ac551882578cc546841

来源:掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Scrapy的中间件(一)的更多相关文章

  1. 彻底搞懂Scrapy的中间件(一)

    中间件是Scrapy里面的一个核心概念.使用中间件可以在爬虫的请求发起之前或者请求返回之后对数据进行定制化修改,从而开发出适应不同情况的爬虫. "中间件"这个中文名字和前面章节讲到 ...

  2. scrapy之中间件

    中间件的简介 1.中间件的作用 在scrapy运行的整个过程中,对scrapy框架运行的某些步骤做一些适配自己项目的动作. 例如scrapy内置的HttpErrorMiddleware,可以在http ...

  3. scrapy的中间件Downloader Middleware实现User-Agent随机切换

    scrapy的中间件Download Middleware实现User-Agent随机切换   总架构理解Middleware 通过scrapy官网最新的架构图来理解: 从图中我们可以看出,在spid ...

  4. scrapy框架中间件配置代理

    scrapy框架中间件配置代理import random#代理池PROXY_http = [ '106.240.254.138:80', '211.24.102.168:80',]PROXY_http ...

  5. Scrapy下载中间件的优先级(神踏马值越小优先级越高)

    自从之前看的一篇讲Scrapy下载中间件的文章后,一直认为设置里下载中间件的优先级数值越小,越优先,最近要抓的网站反爬增强了,所以需要使用代理ip,但是由于使用的是免费代理以至于经常失效,需要对失效的 ...

  6. Python爬虫从入门到放弃(二十三)之 Scrapy的中间件Downloader Middleware实现User-Agent随机切换

    总架构理解Middleware 通过scrapy官网最新的架构图来理解: 这个图较之前的图顺序更加清晰,从图中我们可以看出,在spiders和ENGINE提及ENGINE和DOWNLOADER之间都可 ...

  7. Scrapy 框架 中间件,信号,定制命令

    中间件 下载器中间件 写中间件 from scrapy.http import HtmlResponse from scrapy.http import Request class Md1(objec ...

  8. 彻底搞懂Scrapy的中间件(三)

    在前面两篇文章介绍了下载器中间件的使用,这篇文章将会介绍爬虫中间件(Spider Middleware)的使用. 爬虫中间件 爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同.下载器中间 ...

  9. 彻底搞懂Scrapy的中间件(二)

    在上一篇文章中介绍了下载器中间件的一些简单应用,现在再来通过案例说说如何使用下载器中间件集成Selenium.重试和处理请求异常. 在中间件中集成Selenium 对于一些很麻烦的异步加载页面,手动寻 ...

随机推荐

  1. 【MySQL】MariaDB10.2新特性--Flashback

    MariaDB10.2新特性--Flashback Flashback可以回滚到旧的数据,用于解决用户误删除数据的问题. 实战例子 MariaDB [zsd]> select * from te ...

  2. JSON文件加注释的7种方法

    JSON文件加注释的7种方法 缺省不能加注释,现实有需求 根据JSON规范(http://www.json.org, RFC 4627, RFC 7159),不支持注释.JSON规范之所以不允许加注释 ...

  3. MVC+Ninject+三层架构+代码生成 -- 总结(六、邏輯層)

    1.邏輯層的方法應該與數據層的方法一一對應.邏輯層返回的結果都是用接口IResult封裝,用於項目轉換時,能減少變化的代碼量. 2.邏輯層都需要繼承 BaseLogic 類 public class ...

  4. 使用Docker之镜像的拉取、查询、删除

    1:查看镜像列表 2:拉取镜像    通过命令可以从镜像仓库中拉取镜像,默认从Docker Hub 获取. 命令格式: docker image pull <repository>:< ...

  5. win10系统本地iis或nginx服务器部署vue.js项目

    1.前端框架一般依赖node.js,我们首先要安装node.js.请参考: http://www.cnblogs.com/wuac/p/6381819.html to:安装好node.js后npm也安 ...

  6. SVN服务端安装和仓库的创建

    1.安装SVN服务端 双击运行: 点击[next] 勾上复选框,点击[next] 使用默认选项,点击[next] 点击[Standard Edition]建议端口号不用443,因为Vmware占用了, ...

  7. Redis缓存数据库基础

    思维导图xmind文件:https://files-cdn.cnblogs.com/files/benjieming/Redis.zip

  8. FCC-学习笔记 Pig Latin

    FCC-学习笔记  Pig Latin 1>最近在学习和练习FCC的题目.这个真的比较的好,推荐给大家. 2>中文版的地址:https://www.freecodecamp.cn/;英文版 ...

  9. [转]localhost、127.0.0.1和0.0.0.0和本机IP的区别

    一.IP地址是什么 首先,我们要知道网络中的相互访问其实就是在进行两者间的数据传递.就如同送快递一样,快递发出只有知道你的住址信息,才能将快递送到你的手中.而在网络访问时,只有知道你在网络中的地址信息 ...

  10. jdbc预编译实现方式

    jdbc预编译可以有两种方式: 方式一.jdbc自己实现的预编译,就是做一下特殊字符处理来防SQL注入,看PreparedStatement源码就可以了. public static void mai ...