前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底搞懂Scrapy的中间件(三)

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

作者头像
青南
发布2019-01-09 16:30:03
5000
发布2019-01-09 16:30:03
举报
文章被收录于专栏:未闻Code未闻Code

在前面两篇文章介绍了下载器中间件的使用,这篇文章将会介绍爬虫中间件(Spider Middleware)的使用。

爬虫中间件

爬虫中间件的用法与下载器中间件非常相似,只是它们的作用对象不同。下载器中间件的作用对象是请求request和返回response;爬虫中间件的作用对象是爬虫,更具体地来说,就是写在spiders文件夹下面的各个文件。它们的关系,在Scrapy的数据流图上可以很好地区分开来,如下图所示。

其中,4、5表示下载器中间件,6、7表示爬虫中间件。爬虫中间件会在以下几种情况被调用。

  1. 当运行到 yieldscrapy.Request()或者 yielditem的时候,爬虫中间件的 process_spider_output()方法被调用。
  2. 当爬虫本身的代码出现了 Exception的时候,爬虫中间件的 process_spider_exception()方法被调用。
  3. 当爬虫里面的某一个回调函数 parse_xxx()被调用之前,爬虫中间件的 process_spider_input()方法被调用。
  4. 当运行到 start_requests()的时候,爬虫中间件的 process_start_requests()方法被调用。

在中间件处理爬虫本身的异常

在爬虫中间件里面可以处理爬虫本身的异常。例如编写一个爬虫,爬取UA练习页面http://exercise.kingname.info/exercisemiddlewareua ,故意在爬虫中制造一个异常,如图12-26所示。

由于网站返回的只是一段普通的字符串,并不是JSON格式的字符串,因此使用JSON去解析,就一定会导致报错。这种报错和下载器中间件里面遇到的报错不一样。下载器中间件里面的报错一般是由于外部原因引起的,和代码层面无关。而现在的这种报错是由于代码本身的问题导致的,是代码写得不够周全引起的。

为了解决这个问题,除了仔细检查代码、考虑各种情况外,还可以通过开发爬虫中间件来跳过或者处理这种报错。在middlewares.py中编写一个类:

代码语言:javascript
复制
class ExceptionCheckSpider(object):

    def process_spider_exception(self, response, exception, spider):
        print(f'返回的内容是:{response.body.decode()}\n报错原因:{type(exception)}')
        return None

这个类仅仅起到记录Log的作用。在使用JSON解析网站返回内容出错的时候,将网站返回的内容打印出来。

process_spider_exception()这个方法,它可以返回 None,也可以运行 yielditem语句或者像爬虫的代码一样,使用 yieldscrapy.Request()发起新的请求。如果运行了 yielditem或者 yieldscrapy.Request(),程序就会绕过爬虫里面原有的代码。

例如,对于有异常的请求,不需要进行重试,但是需要记录是哪一个请求出现了异常,此时就可以在爬虫中间件里面检测异常,然后生成一个只包含标记的item。还是以抓取http://exercise.kingname.info/exercisemiddlewareretry.html这个练习页的内容为例,但是这一次不进行重试,只记录哪一页出现了问题。先看爬虫的代码,这一次在meta中把页数带上,如下图所示。

爬虫里面如果发现了参数错误,就使用raise这个关键字人工抛出一个自定义的异常。在实际爬虫开发中,读者也可以在某些地方故意不使用try ... except捕获异常,而是让异常直接抛出。例如XPath匹配处理的结果,直接读里面的值,不用先判断列表是否为空。这样如果列表为空,就会被抛出一个IndexError,于是就能让爬虫的流程进入到爬虫中间件的 process_spider_exception()中。

在items.py里面创建了一个ErrorItem来记录哪一页出现了问题,如下图所示。

接下来,在爬虫中间件中将出错的页面和当前时间存放到ErrorItem里面,并提交给pipeline,保存到MongoDB中,如下图所示。

这样就实现了记录错误页数的功能,方便在后面对错误原因进行分析。由于这里会把item提交给pipeline,所以不要忘记在settings.py里面打开pipeline,并配置好MongoDB。储存错误页数到MongoDB的代码如下图所示。

激活爬虫中间件

爬虫中间件的激活方式与下载器中间件非常相似,在settings.py中,在下载器中间件配置项的上面就是爬虫中间件的配置项,它默认也是被注释了的,解除注释,并把自定义的爬虫中间件添加进去即可,如下图所示。

Scrapy也有几个自带的爬虫中间件,它们的名字和顺序如下图所示。

下载器中间件的数字越小越接近Scrapy引擎,数字越大越接近爬虫。如果不能确定自己的自定义中间件应该靠近哪个方向,那么就在500~700之间选择最为妥当。

爬虫中间件输入/输出

在爬虫中间件里面还有两个不太常用的方法,分别为 process_spider_input(response,spider)process_spider_output(response,result,spider)。其中, process_spider_input(response,spider)在下载器中间件处理完成后,马上要进入某个回调函数parse_xxx()前调用。 process_spider_output(response,result,output)是在爬虫运行 yielditem或者 yieldscrapy.Request()的时候调用。在这个方法处理完成以后,数据如果是item,就会被交给pipeline;如果是请求,就会被交给调度器,然后下载器中间件才会开始运行。所以在这个方法里面可以进一步对item或者请求做一些修改。这个方法的参数result就是爬虫爬出来的item或者 scrapy.Request()。由于yield得到的是一个生成器,生成器是可以迭代的,所以result也是可以迭代的,可以使用for循环来把它展开。

代码语言:javascript
复制
def process_spider_output(response, result, spider):
    for item in result:
        if isinstance(item, scrapy.Item):
            # 这里可以对即将被提交给pipeline的item进行各种操作
            print(f'item将会被提交给pipeline')
        yield item

或者对请求进行监控和修改:

代码语言:javascript
复制
def process_spider_output(response, result, spider):
    for request in result:
        if not isinstance(request, scrapy.Item):
            # 这里可以对请求进行各种修改
            print('现在还可以对请求对象进行修改。。。。')
        request.meta['request_start_time'] = time.time()
        yield request

本文节选自我的新书《Python爬虫开发 从入门到实战》完整目录可以在京东查询到

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-11-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 未闻Code 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 爬虫中间件
    • 在中间件处理爬虫本身的异常
      • 激活爬虫中间件
        • 爬虫中间件输入/输出
        相关产品与服务
        消息队列 TDMQ
        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档