完全搞懂scrapy的中间件第二章

在上一篇文章中介绍了下载器中间件的一些简单应用,如今再来经过案例说说如何使用下载器中间件集成Selenium、重试和处理请求异常。html

在中间件中集成Selenium

对于一些很麻烦的异步加载页面,手动寻找它的后台API代价可能太大。这种状况下可使用Selenium和ChromeDriver或者Selenium和PhantomJS来实现渲染网页。python

这是前面的章节已经讲到的内容。那么,如何把Scrapy与Selenium结合起来呢?这个时候又要用到中间件了。web

建立一个SeleniumMiddleware,其代码以下:redis

from scrapy.http import HtmlResponse class SeleniumMiddleware(object): def __init__(self): self.driver = webdriver.Chrome('./chromedriver') def process_request(self, request, spider): if spider.name == 'seleniumSpider': self.driver.get(request.url) time.sleep(2) body = self.driver.page_source return HtmlResponse(self.driver.current_url, body=body, encoding='utf-8', request=request)

这个中间件的做用,就是对名为“seleniumSpider”的爬虫请求的网址,使用ChromeDriver先进行渲染,而后用返回的渲染后的HTML代码构造一个Response对象。若是是其余的爬虫,就什么都不作。在上面的代码中,等待页面渲染完成是经过time.sleep(2)来实现的,固然读者也可使用前面章节讲到的等待某个元素出现的方法来实现。chrome

有了这个中间件之后,就能够像访问普通网页那样直接处理须要异步加载的页面,以下图所示。浏览器

在中间件里重试

在爬虫的运行过程当中,可能会由于网络问题或者是网站反爬虫机制生效等缘由,致使一些请求失败。在某些状况下,少许的数据丢失是可有可无的,例如在几亿次请求里面失败了十几回,损失微乎其微,没有必要重试。但还有一些状况,每一条请求都相当重要,容不得有一次失败。此时就须要使用中间件来进行重试。markdown

有的网站的反爬虫机制被触发了,它会自动将请求重定向到一个xxx/404.html页面。那么若是发现了这种自动的重定向,就没有必要让这一次的请求返回的内容进入数据提取的逻辑,而应该直接丢掉或者重试。网络

还有一种状况,某网站的请求参数里面有一项,Key为date,Value为发起请求的这一天的日期或者发起请求的这一天的前一天的日期。例现在天是“2017-08-10”,可是这个参数的值是今天早上10点以前,都必须使用“2017-08-09”,在10点以后才能使用“2017-08-10”,不然,网站就不会返回正确的结果,而是返回“参数错误”这4个字。然而,这个日期切换的时间点受到其余参数的影响,有可能第1个请求使用“2017-08-10”能够成功访问,而第2个请求却只有使用“2017-08-09”才能访问。遇到这种状况,与其花费大量的时间和精力去追踪时间切换点的变化规律,不如简单粗暴,直接先用今天去试,再用昨天的日期去试,反正最多两次,总有一个是正确的。app

以上的两种场景,使用重试中间件都能轻松搞定。less

打开练习页面

http://exercise.kingname.info/exercise_middleware_retry.html。

这个页面实现了翻页逻辑,能够上一页、下一页地翻页,也能够直接跳到任意页数,以下图所示。

如今须要获取1~9页的内容,那么使用前面章节学到的内容,经过Chrome浏览器的开发者工具很容易就能发现翻页其实是一个POST请求,提交的参数为“date”,它的值是日期“2017-08-12”,以下图所示。

使用Scrapy写一个爬虫来获取1~9页的内容,运行结果以下图所示。

从上图能够看到,第5页没有正常获取到,返回的结果是参数错误。因而在网页上看一下,发现第5页的请求中body里面的date对应的日期是“2017-08-11”,以下图所示。

若是测试的次数足够多,时间足够长,就会发现如下内容。

  1. 同一个时间点,不一样页数提交的参数中,date对应的日期多是今天的也多是昨天的。
  2. 同一个页数,不一样时间提交的参数中,date对应的日期多是今天的也多是昨天的。

因为日期不是今天,就是昨天,因此针对这种状况,写一个重试中间件是最简单粗暴且有效的解决办法。中间件的代码以下图所示。

这个中间件只对名为“middlewareSpider”的爬虫有用。因为middlewareSpider爬虫默认使用的是“今天”的日期,因此若是被网站返回了“参数错误”,那么正确的日期就必然是昨天的了。因此在这个中间件里面,第119行,直接把原来请求的body换成了昨天的日期,这个请求的其余参数不变。让这个中间件生效之后,爬虫就能成功爬取第5页了,以下图所示。

爬虫自己的代码,数据提取部分彻底没有作任何修改,若是不看中间件代码,彻底感受不出爬虫在第5页重试过。

除了检查网站返回的内容外,还能够检查返回内容对应的网址。将上面练习页后台网址的第1个参数“para”改成404,暂时禁用重试中间件,再跑一次爬虫。其运行结果以下图所示。

此时,对于参数不正确的请求,网站会自动重定向到如下网址对应的页面:

http://exercise.kingname.info/404.html

因为Scrapy自带网址自动去重机制,所以虽然第3页、第6页和第7页都被自动转到了404页面,可是爬虫只会爬一次404页面,剩下两个404页面会被自动过滤。

对于这种状况,在重试中间件里面判断返回的网址便可解决,以下图12-21所示。

在代码的第115行,判断是否被自动跳转到了404页面,或者是否被返回了“参数错误”。若是都不是,说明这一次请求目前看起来正常,直接把response返回,交给后面的中间件来处理。若是被重定向到了404页面,或者被返回“参数错误”,那么进入重试的逻辑。若是返回了“参数错误”,那么进入第126行,直接替换原来请求的body便可从新发起请求。

若是自动跳转到了404页面,那么这里有一点须要特别注意:此时的请求,request这个对象对应的是向404页面发起的GET请求,而不是原来的向练习页后台发起的请求。因此,从新构造新的请求时必须把URL、body、请求方式、Headers所有都换一遍才能够。

因为request对应的是向404页面发起的请求,因此resquest.url对应的网址是404页面的网址。所以,若是想知道调整以前的URL,可使用以下的代码:

request.meta['redirect_urls']

这个值对应的是一个列表。请求自动跳转了几回,这个列表里面就有几个URL。这些URL是按照跳转的前后次序依次append进列表的。因为本例中只跳转了一次,因此直接读取下标为0的元素便可,也就是原始网址。

从新激活这个重试中间件,不改变爬虫数据抓取部分的代码,直接运行之后能够正确获得1~9页的所有内容,以下图所示。

在中间件里处理异常

在默认状况下,一次请求失败了,Scrapy会马上原地重试,再失败再重试,如此3次。若是3次都失败了,就放弃这个请求。这种重试逻辑存在一些缺陷。以代理IP为例,代理存在不稳定性,特别是免费的代理,差很少10个里面只有3个能用。而如今市面上有一些收费代理IP提供商,购买他们的服务之后,会直接提供一个固定的网址。把这个网址设为Scrapy的代理,就能实现每分钟自动以不一样的IP访问网站。若是其中一个IP出现了故障,那么须要等一分钟之后才会更换新的IP。在这种场景下,Scrapy自带的重试逻辑就会致使3次重试都失败。

这种场景下,若是能马上更换代理就马上更换;若是不能马上更换代理,比较好的处理方法是延迟重试。而使用Scrapy_redis就能实现这一点。爬虫的请求来自于Redis,请求失败之后的URL又放回Redis的末尾。一旦一个请求原地重试3次仍是失败,那么就把它放到Redis的末尾,这样Scrapy须要把Redis列表前面的请求都消费之后才会重试以前的失败请求。这就为更换IP带来了足够的时间。

从新打开代理中间件,这一次故意设置一个有问题的代理,因而能够看到Scrapy控制台打印出了报错信息,以下图所示。

从上图能够看到Scrapy自动重试的过程。因为代理有问题,最后会抛出方框框住的异常,表示TCP超时。在中间件里面若是捕获到了这个异常,就能够提早更换代理,或者进行重试。这里以更换代理为例。首先根据上图中方框框住的内容导入TCPTimeOutError这个异常:

from twisted.internet.error import TCPTimedOutError

修改前面开发的重试中间件,添加一个process_exception()方法。这个方法接收3个参数,分别为request、exception和spider,以下图所示。

process_exception()方法只对名为“exceptionSpider”的爬虫生效,若是请求遇到了TCPTimeOutError,那么就首先调用remove_broken_proxy()方法把失效的这个代理IP移除,而后返回这个请求对象request。返回之后,Scrapy会从新调度这个请求,就像它第一次调度同样。因为原来的ProxyMiddleware依然在工做,因而它就会再一次给这个请求更换代理IP。又因为刚才已经移除了失效的代理IP,因此ProxyMiddleware会从剩下的代理IP里面随机找一个来给这个请求换上。

特别提醒:图片中的remove_broken_proxy()函数体里面写的是pass,可是在实际开发过程当中,读者可根据实际状况实现这个方法,写出移除失效代理的具体逻辑。

下载器中间件功能总结

能在中间件中实现的功能,都能经过直接把代码写到爬虫中实现。使用中间件的好处在于,它能够把数据爬取和其余操做分开。在爬虫的代码里面专心写数据爬取的代码;在中间件里面专心写突破反爬虫、登陆、重试和渲染AJAX等操做。

对团队来讲,这种写法能实现多人同时开发,提升开发效率;对我的来讲,写爬虫的时候不用考虑反爬虫、登陆、验证码和异步加载等操做。另外,写中间件的时候不用考虑数据怎样提取。一段时间只作一件事,思路更清晰。

相关文章
相关标签/搜索