如何在scrapy中捕获并处理各种异常


Posted in Python onSeptember 28, 2020

前言

    使用scrapy进行大型爬取任务的时候(爬取耗时以天为单位),无论主机网速多好,爬完之后总会发现scrapy日志中“item_scraped_count”不等于预先的种子数量,总有一部分种子爬取失败,失败的类型可能有如下图两种(下图为scrapy爬取结束完成时的日志):

如何在scrapy中捕获并处理各种异常

scrapy中常见的异常包括但不限于:download error(蓝色区域), http code 403/500(橙色区域)。

不管是哪种异常,我们都可以参考scrapy自带的retry中间件写法来编写自己的中间件。

正文

     使用IDE,现在scrapy项目中任意一个文件敲上以下代码:

from scrapy.downloadermiddlewares.retry import RetryMiddleware

按住ctrl键,鼠标左键点击RetryMiddleware进入该中间件所在的项目文件的位置,也可以通过查看文件的形式找到该中间件的位置,路径是:site-packages/scrapy/downloadermiddlewares/retry.RetryMiddleware

该中间件的源代码如下:

class RetryMiddleware(object):

  # IOError is raised by the HttpCompression middleware when trying to
  # decompress an empty response
  EXCEPTIONS_TO_RETRY = (defer.TimeoutError, TimeoutError, DNSLookupError,
              ConnectionRefusedError, ConnectionDone, ConnectError,
              ConnectionLost, TCPTimedOutError, ResponseFailed,
              IOError, TunnelError)

  def __init__(self, settings):
    if not settings.getbool('RETRY_ENABLED'):
      raise NotConfigured
    self.max_retry_times = settings.getint('RETRY_TIMES')
    self.retry_http_codes = set(int(x) for x in settings.getlist('RETRY_HTTP_CODES'))
    self.priority_adjust = settings.getint('RETRY_PRIORITY_ADJUST')

  @classmethod
  def from_crawler(cls, crawler):
    return cls(crawler.settings)

  def process_response(self, request, response, spider):
    if request.meta.get('dont_retry', False):
      return response
    if response.status in self.retry_http_codes:
      reason = response_status_message(response.status)
      return self._retry(request, reason, spider) or response
    return response

  def process_exception(self, request, exception, spider):
    if isinstance(exception, self.EXCEPTIONS_TO_RETRY) \
        and not request.meta.get('dont_retry', False):
      return self._retry(request, exception, spider)

  def _retry(self, request, reason, spider):
    retries = request.meta.get('retry_times', 0) + 1

    retry_times = self.max_retry_times

    if 'max_retry_times' in request.meta:
      retry_times = request.meta['max_retry_times']

    stats = spider.crawler.stats
    if retries <= retry_times:
      logger.debug("Retrying %(request)s (failed %(retries)d times): %(reason)s",
             {'request': request, 'retries': retries, 'reason': reason},
             extra={'spider': spider})
      retryreq = request.copy()
      retryreq.meta['retry_times'] = retries
      retryreq.dont_filter = True
      retryreq.priority = request.priority + self.priority_adjust

      if isinstance(reason, Exception):
        reason = global_object_name(reason.__class__)

      stats.inc_value('retry/count')
      stats.inc_value('retry/reason_count/%s' % reason)
      return retryreq
    else:
      stats.inc_value('retry/max_reached')
      logger.debug("Gave up retrying %(request)s (failed %(retries)d times): %(reason)s",
             {'request': request, 'retries': retries, 'reason': reason},
             extra={'spider': spider})

查看源码我们可以发现,对于返回http code的response,该中间件会通过process_response方法来处理,处理办法比较简单,大概是判断response.status是否在定义好的self.retry_http_codes集合中,通过向前查找,这个集合是一个列表,定义在default_settings.py文件中,定义如下:

RETRY_HTTP_CODES = [500, 502, 503, 504, 522, 524, 408]

也就是先判断http code是否在这个集合中,如果在,就进入retry的逻辑,不在集合中就直接return response。这样就已经实现对返回http code但异常的response的处理了。

但是对另一种异常的处理方式就不一样了,刚才的异常准确的说是属于HTTP请求error(超时),而另一种异常发生的时候则是如下图这种实实在在的代码异常(不处理的话):

如何在scrapy中捕获并处理各种异常

你可以创建一个scrapy项目,start_url中填入一个无效的url即可模拟出此类异常。比较方便的是,在RetryMiddleware中同样提供了对这类异常的处理办法:process_exception

通过查看源码,可以分析出大概的处理逻辑:同样先定义一个集合存放所有的异常类型,然后判断传入的异常是否存在于该集合中,如果在(不分析dont try)就进入retry逻辑,不在就忽略。

OK,现在已经了解了scrapy是如何捕捉异常了,大概的思路也应该有了,下面贴出一个实用的异常处理的中间件模板:

from twisted.internet import defer
from twisted.internet.error import TimeoutError, DNSLookupError, \
  ConnectionRefusedError, ConnectionDone, ConnectError, \
  ConnectionLost, TCPTimedOutError
from scrapy.http import HtmlResponse
from twisted.web.client import ResponseFailed
from scrapy.core.downloader.handlers.http11 import TunnelError

class ProcessAllExceptionMiddleware(object):
  ALL_EXCEPTIONS = (defer.TimeoutError, TimeoutError, DNSLookupError,
           ConnectionRefusedError, ConnectionDone, ConnectError,
           ConnectionLost, TCPTimedOutError, ResponseFailed,
           IOError, TunnelError)
  def process_response(self,request,response,spider):
    #捕获状态码为40x/50x的response
    if str(response.status).startswith('4') or str(response.status).startswith('5'):
      #随意封装,直接返回response,spider代码中根据url==''来处理response
      response = HtmlResponse(url='')
      return response
    #其他状态码不处理
    return response
  def process_exception(self,request,exception,spider):
    #捕获几乎所有的异常
    if isinstance(exception, self.ALL_EXCEPTIONS):
      #在日志中打印异常类型
      print('Got exception: %s' % (exception))
      #随意封装一个response,返回给spider
      response = HtmlResponse(url='exception')
      return response
    #打印出未捕获到的异常
    print('not contained exception: %s'%exception)

spider解析代码示例:

class TESTSpider(scrapy.Spider):
  name = 'TEST'
  allowed_domains = ['TTTTT.com']
  start_urls = ['http://www.TTTTT.com/hypernym/?q=']
  custom_settings = {
    'DOWNLOADER_MIDDLEWARES': {
      'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
      'TESTSpider.middlewares.ProcessAllExceptionMiddleware': 120,
    },
    'DOWNLOAD_DELAY': 1, # 延时最低为2s
    'AUTOTHROTTLE_ENABLED': True, # 启动[自动限速]
    'AUTOTHROTTLE_DEBUG': True, # 开启[自动限速]的debug
    'AUTOTHROTTLE_MAX_DELAY': 10, # 设置最大下载延时
    'DOWNLOAD_TIMEOUT': 15,
    'CONCURRENT_REQUESTS_PER_DOMAIN': 4 # 限制对该网站的并发请求数
  }
  def parse(self, response):
    if not response.url: #接收到url==''时
      print('500')
      yield TESTItem(key=response.meta['key'], _str=500, alias='')
    elif 'exception' in response.url:
      print('exception')
      yield TESTItem(key=response.meta['key'], _str='EXCEPTION', alias='')

Note:该中间件的Order_code不能过大,如果过大就会越接近下载器,就会优先于RetryMiddleware处理response,但这个中间件是用来兜底的,即当一个response 500进入中间件链时,需要先经过retry中间件处理,不能先由我们写的中间件来处理,它不具有retry的功能,接收到500的response就直接放弃掉该request直接return了,这是不合理的。只有经过retry后仍然有异常的request才应当由我们写的中间件来处理,这时候你想怎么处理都可以,比如再次retry、return一个重新构造的response。

下面来验证一下效果如何(测试一个无效的URL),下图为未启用中间件的情况:

如何在scrapy中捕获并处理各种异常

再启用中间件查看效果:

如何在scrapy中捕获并处理各种异常

ok,达到预期效果:即使程序运行时抛出异常也能被捕获并处理。

到此这篇关于如何在scrapy中捕获并处理各种异常的文章就介绍到这了,更多相关scrapy 捕获处理异常内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python进行数据科学工作的简单入门教程
Apr 01 Python
Python正则简单实例分析
Mar 21 Python
浅谈function(函数)中的动态参数
Apr 30 Python
Python实现按中文排序的方法示例
Apr 25 Python
创建pycharm的自定义python模板方法
May 23 Python
python mysql断开重连的实现方法
Jul 26 Python
python 基于dlib库的人脸检测的实现
Nov 08 Python
django ajax发送post请求的两种方法
Jan 05 Python
python3实现raspberry pi(树莓派)4驱小车控制程序
Feb 12 Python
Django实现从数据库中获取到的数据转换为dict
Mar 27 Python
Python 存取npy格式数据实例
Jul 01 Python
Python GUI之tkinter窗口视窗教程大集合(推荐)
Oct 20 Python
python向企业微信发送文字和图片消息的示例
Sep 28 #Python
python利用tkinter实现图片格式转换的示例
Sep 28 #Python
python在CMD界面读取excel所有数据的示例
Sep 28 #Python
python调用摄像头的示例代码
Sep 28 #Python
python 调用API接口 获取和解析 Json数据
Sep 28 #Python
记录一下scrapy中settings的一些配置小结
Sep 28 #Python
使用scrapy ImagesPipeline爬取图片资源的示例代码
Sep 28 #Python
You might like
删除及到期域名的查看(抢域名必备哦)
2008/05/14 PHP
一个PHP分页类的代码
2011/05/18 PHP
ThinkPHP中url隐藏入口文件后接收alipay传值的方法
2014/12/09 PHP
一次因composer错误使用引发的问题与解决
2019/03/06 PHP
JavaScript使用ActiveXObject访问Access和SQL Server数据库
2015/04/02 Javascript
javascript实现抽奖程序的简单实例
2016/06/07 Javascript
浅谈Nodejs应用主文件index.js
2016/08/28 NodeJs
jQuery中页面返回顶部的方法总结
2016/12/30 Javascript
NodeJS创建最简单的HTTP服务器
2017/05/15 NodeJs
Nodejs 发布自己的npm包并制作成命令行工具的实例讲解
2018/05/15 NodeJs
基于JS开发微信网页录音功能的实例代码
2019/04/30 Javascript
jQuery实现简单日历效果
2020/07/05 jQuery
[59:48]DOTA2-DPC中国联赛 正赛 VG vs Magma BO3 第一场 1月26日
2021/03/11 DOTA
Python自动化测试工具Splinter简介和使用实例
2014/05/13 Python
Python实现爬取逐浪小说的方法
2015/07/07 Python
Python3实现Web网页图片下载
2016/01/28 Python
Python 的内置字符串方法小结
2016/03/15 Python
浅谈Python的垃圾回收机制
2016/12/17 Python
Python设计模式之命令模式简单示例
2018/01/10 Python
python+influxdb+shell编写区域网络状况表
2018/07/27 Python
python实现Flappy Bird源码
2018/12/24 Python
selenium python 实现基本自动化测试的示例代码
2019/02/25 Python
python 在某.py文件中调用其他.py内的函数的方法
2019/06/25 Python
在python中利用numpy求解多项式以及多项式拟合的方法
2019/07/03 Python
Python3分析处理声音数据的例子
2019/08/27 Python
解决Keras自带数据集与预训练model下载太慢问题
2020/06/12 Python
Pure Collection美国官网:来自英国羊绒专家的奢华羊绒
2017/11/19 全球购物
技术总监的工作职责
2013/11/13 职场文书
体育教育个人自荐信范文
2013/12/01 职场文书
采购类个人求职的自我评价
2014/02/18 职场文书
党员学习正风肃纪思想汇报
2014/09/12 职场文书
考试作弊检讨书
2014/10/21 职场文书
加薪通知
2015/04/25 职场文书
学校团代会开幕词
2016/03/04 职场文书
2020年元旦祝福语录,总有适合你的
2019/12/31 职场文书
使用logback实现按自己的需求打印日志到自定义的文件里
2021/08/30 Java/Android