V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
itskingname
V2EX  ›  分享创造

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

  •  1
     
  •   itskingname · 2018-11-21 08:22:33 +08:00 · 1505 次点击
    这是一个创建于 1977 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    爬虫中间件

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

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

    1. 当运行到yield scrapy.Request()或者yield item的时候,爬虫中间件的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/exercise_middleware_ua ,故意在爬虫中制造一个异常,如图 12-26 所示。

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

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

    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,也可以运行yield item语句或者像爬虫的代码一样,使用yield scrapy.Request()发起新的请求。如果运行了yield item或者yield scrapy.Request(),程序就会绕过爬虫里面原有的代码。

    例如,对于有异常的请求,不需要进行重试,但是需要记录是哪一个请求出现了异常,此时就可以在爬虫中间件里面检测异常,然后生成一个只包含标记的 item。还是以抓取http://exercise.kingname.info/exercise_middleware_retry.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)是在爬虫运行yield item或者yield scrapy.Request()的时候调用。在这个方法处理完成以后,数据如果是 item,就会被交给 pipeline ;如果是请求,就会被交给调度器,然后下载器中间件才会开始运行。所以在这个方法里面可以进一步对 item 或者请求做一些修改。这个方法的参数 result 就是爬虫爬出来的 item 或者scrapy.Request()。由于 yield 得到的是一个生成器,生成器是可以迭代的,所以 result 也是可以迭代的,可以使用 for 循环来把它展开。

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

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

    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 爬虫开发 从入门到实战》完整目录可以在京东查询到 https://item.jd.com/12436581.html

    买不买书不重要,重要的是请关注我的公众号:未闻 Code

    公众号已经连续日更三个多月了。在接下来的很长时间里也会连续日更。

    1 条回复    2018-11-21 12:27:26 +08:00
    itskingname
        1
    itskingname  
    OP
       2018-11-21 12:27:26 +08:00
    为什么又是那么多人收藏却不评论?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2722 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 02:04 · PVG 10:04 · LAX 19:04 · JFK 22:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.