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

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

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

class ExceptionMiddleware(object):"""异常状态码处理"""def __init__(self):self.retry_http_codes = [500, 502, 503, 504, 522, 524, 408]def process_response(self, request, response, spider):"""当下载器完成http请求,返回响应给引擎的时候调用process_response"""if response.status in self.retry_http_codes:return requestelse:return response

正文
     使用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项目,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不能过大,如果过大就会越接近下载器(默认中间件执行的Order点击这里查看),就会优先于RetryMiddleware处理response,但这个中间件是用来兜底的,即当一个response 500进入中间件链时,需要先经过retry中间件处理,不能先由我们写的中间件来处理,它不具有retry的功能,接收到500的response就直接放弃掉该request直接return了,这是不合理的。只有经过retry后仍然有异常的request才应当由我们写的中间件来处理,这时候你想怎么处理都可以,比如再次retry、return一个重新构造的response。

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

再启用中间件查看效果:

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

最后给自己的开源项目wukongqueue拉一波星星,如果您不嫌麻烦,动动小手就可以了~谢谢!(有兴趣可进一步交流)

如果你有什么意见或建议,请给我留言,生活愉快~

原文链接:https://blog.csdn.net/sc_lilei/article/details/80702449

[Scrapy使用技巧] 如何在scrapy中捕获并处理各种异常相关推荐

  1. python捕获所有异常状态_如何在scrapy中捕获并处理各种异常

    前言 使用scrapy进行大型爬取任务的时候(爬取耗时以天为单位),无论主机网速多好,爬完之后总会发现scrapy日志中"item_scraped_count"不等于预先的种子数量 ...

  2. 我可以在同一个catch子句中捕获多个Java异常吗?

    本文翻译自:Can I catch multiple Java exceptions in the same catch clause? In Java, I want to do something ...

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

    前言     使用scrapy进行大型爬取任务的时候(爬取耗时以天为单位),无论主机网速多好,爬完之后总会发现scrapy日志中"item_scraped_count"不等于预先的 ...

  4. 如何在Python中捕获SIGINT?

    我正在研究启动多个进程和数据库连接的python脚本. 我不时地想用Ctrl + C信号杀死脚本,我想进行一些清理. 在Perl中,我可以这样做: $SIG{'INT'} = 'exit_gracef ...

  5. 表格里面怎么打多个√_Excel技巧—如何在Excel中输入√和×

    点赞再看,养成习惯: 立身以立学为先,立学以读书为本. 今天在这里要和大家分享如何在Excel中输入√和×,不是经常使用的,可能突然这么一问一时半会还真想不起来怎么输入,其中方法有很多种,今天在这里小 ...

  6. excel排名_表格技巧—如何在Excel中快速计算排名

    在利用excel统计成绩的时候,往往会使用它的排名,如果一个一个排会造成工作效率非常低,那么如何快速排名呢?今天我们和大家分享的就是如何在excel中快速计算排名. 首先打开一张我们需要排名的exce ...

  7. 在excel中如何筛选重复数据_Excel表格技巧—如何在 Excel 中查找重复值

    今天和大家一起分享一下excel表格中如何快速查找重复值,在办公中很经常需要它. 先打开一张需要查找重复数据或重复值的EXCLE表格,如图我们以B2做为查找列: 点击工具菜单栏的"开始&qu ...

  8. Excel技巧—如何在Excel中输入√和×

    点赞再看,养成习惯:立身以立学为先,立学以读书为本. 微信搜索[亦心Excel]关注这个不一样的自媒体人. 本文 GitHub https://github.com/hugogoos/Excel 已收 ...

  9. linux如何将文件夹添加到书签,桌面应用|[新手技巧] 如何在Ubuntu中添加和删除书签...

    这是一篇对完全是新手的一篇技巧,我将向你展示如何在Ubuntu文件管理器中添加书签. 现在如果你想知道为什么要这么做,答案很简单.它可以让你可以快速地在左边栏中访问.比如,我在Ubuntu中安装了Co ...

最新文章

  1. codeforces 960A Check the string
  2. Cocos2d-x 中 CCProgressTimer
  3. 已安装的sql怎么添加功能_微信群管理工具有哪些功能?怎么在社群中添加微信小助手?...
  4. iOS探索:Block解析浅谈
  5. 如何以及何时使用例外
  6. python boxplot orient_Python 可视化 | Seaborn5 分钟入门 (三)——boxplot 和 violinplot
  7. mysql数据库安全配置规范_MySQL数据库安全配置
  8. 洛克人红色思考型机器人叫什么_如何让机器人“好好说话”?
  9. tensorflow - model.predict
  10. easyui中datagrid空数据集不刷新的解决方式
  11. ARM 汇编语言教程
  12. Java 二叉树的层序遍历
  13. 小型计算器的实现——Java GUI图形界面设计案例
  14. WanderAndLogData
  15. 不小心执行了rm -f,如何恢复?
  16. 未来十年公务员、事业单位职员以及国企人员的前景?
  17. python-pygame实现飞机大战-5-屏幕渲染绘制分数、生命数、超级炸弹数以及暂停功能
  18. 最优化理论笔记及期末复习(《数值最优化》——高立)
  19. oracle+dbcc+checkdb,dbcc checkdb 修复数据库
  20. 世界各国 省市县 省份 城市 三级数据库表 mysql

热门文章

  1. SSL2668 2017年8月7日提高组T1 根(dfs)
  2. python in arcgis_终于晓得arcgis-python入门教程
  3. 智慧的车联网,是否会让我们失去操控的乐趣?
  4. VMware+ubuntu+win10笔记本实现笔记本连接WIFI且ubuntu既可以上网又能连接开发板
  5. 数据结构实验之查找与排序
  6. Python-裁判文书网
  7. 什么是网站结构,为什么它很重要?
  8. Visual Studio 2019 Community 许可证过期解决方法
  9. matlab公式上为什么会有问号,MathType公式显示问号怎么解决
  10. Python版 孤勇者 | 画图+演奏+音乐可视化