前言
    使用scrapy进行大型爬取任务的时候(爬取耗时以天为单位),无论主机网速多好,爬完之后总会发现scrapy日志中“item_scraped_count”不等于预先的种子数量,总有一部分种子爬取失败,失败的类型可能有如下图两种(下图为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 responseEXCEPTIONS_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 NotConfiguredself.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')@classmethoddef from_crawler(cls, crawler):return cls(crawler.settings)def process_response(self, request, response, spider):if request.meta.get('dont_retry', False):return responseif response.status in self.retry_http_codes:reason = response_status_message(response.status)return self._retry(request, reason, spider) or responsereturn responsedef 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) + 1retry_times = self.max_retry_timesif 'max_retry_times' in request.meta:retry_times = request.meta['max_retry_times']stats = spider.crawler.statsif 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'] = retriesretryreq.dont_filter = Trueretryreq.priority = request.priority + self.priority_adjustif isinstance(reason, Exception):reason = global_object_name(reason.__class__)stats.inc_value('retry/count')stats.inc_value('retry/reason_count/%s' % reason)return retryreqelse: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 TunnelErrorclass 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的responseif str(response.status).startswith('4') or str(response.status).startswith('5'):#随意封装,直接返回response,spider代码中根据url==''来处理responseresponse = HtmlResponse(url='')return response#其他状态码不处理return responsedef process_exception(self,request,exception,spider):#捕获几乎所有的异常if isinstance(exception, self.ALL_EXCEPTIONS):#在日志中打印异常类型print('Got exception: %s' % (exception))#随意封装一个response,返回给spiderresponse = 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),下图为未启用中间件的情况:

再启用中间件查看效果:

如何在scrapy中捕获并处理各种异常相关推荐

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

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

  2. [Scrapy使用技巧] 如何在scrapy中捕获并处理各种异常

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

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

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

  4. 如何在Python中捕获SIGINT?

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

  5. java如何捕获多个异常_是否可以在单个catch块中捕获多个Java异常?

    例外是程序执行期间发生的问题(运行时错误).发生异常时,程序会突然终止,并且生成异常的行之后的代码将永远不会执行. 代码中有多个异常 在Java 7之前,只要我们有一个可能会生成多个异常的代码,并且如 ...

  6. 在Scrapy中使用爬虫动态代理IP

    本文介绍如何在Scrapy中使用无忧代理(www.data5u.com)的爬虫动态代理IP,以及如何设置User-Agent. 动态转发参考https://blog.csdn.net/u0109787 ...

  7. asp.net捕获全局未处理异常的几种方法

    通过HttpModule来捕获未处理的异常[推荐] 首先需要定义一个HttpModule,并监听未处理异常,代码如下: public void Init(HttpApplication context ...

  8. scarpy框架如何在crawl中正确传递自定义参数,scrapy.cmdline的execute为什么不能在while True中无限循环,execute换成crawl 方法

    问题:scrapy.cmdline的execute函数执行完毕为什么一定会停,不能执行后面函数,也不在while True中无限循环(解决问题的结果 在最后) 我想让scrapy程序全年无休止运行,2 ...

  9. sigterm信号_详解如何在 docker 容器中捕获信号

    概述 玩过docker的朋友可能都使用过 docker stop 命令来停止正在运行的容器,有些会使用 docker kill 命令强行关闭容器或者把某个信号传递给容器中的进程.这些操作的本质都是通过 ...

最新文章

  1. 输入今天是星期几的序号,给今天和昨天赋予枚举值,并输出昨天是星期几的枚举值
  2. notepad++取消语法检测
  3. python朋友圈为什么这么火-看我如何用Python发一个高逼格的朋友圈
  4. android 添加子view,Android基于Window.ID_ANDROID_CONTENT给定id添加子View
  5. 2013\Province_Java_B\1.世纪末的星期
  6. Format Currency Sample
  7. django框架 day05
  8. 数据库两个表有一个字段互相关联,根据这个关联字段更新一张表
  9. Linux常用命令及笔记
  10. ECshop生态全面开放,城市合伙人招募火热启动
  11. powershell自动化操作AD域、Exchange邮箱系列(10)—获取Exchange邮箱用户配额并导出excel
  12. thinkpad分区win10_预装win10系统Thinkpad笔记本只有一个C盘怎么分区
  13. 街头篮球服务器维护中,雷冥竟然有这能力? 《街头篮球》五一稀有角色能力解析...
  14. Linux驱动开发|USB驱动
  15. (个人笔记)EDEM耦合Recurdyn流程
  16. 【操作系统】TCP流式传输、UDP数据报传输
  17. ubuntu: 安装 摄像头驱动
  18. html中bottom的作用,css bottom属性怎么用
  19. Iterator详解
  20. oracle and not 的用法,[ORACLE]详解not in与not exists的区别与用法(not in的性能并不差!)...

热门文章

  1. Pytorch的grad、backward()、zero_grad()
  2. matlab图像网格化像素提取像素扩大图片分块
  3. 5元的小乌龟吃什么_小乌龟吃什么?小乌龟怎么养经验详解
  4. SVN异常处理——禁止访问
  5. 河南高考成绩位次查询2021,2021年河南高考状元多少分是谁,河南高考状元名单资料...
  6. 计算机考研408需要带小刀吗,考研要带小刀和胶水?这些有什么用?
  7. 语音模块LD3320模块的二次开发,并与树莓派进行串口通信
  8. [ROC-RK3568-PC] [Firefly-Android] 10min带你了解ADC的使用
  9. Android 人脸解锁源码剖析
  10. 活体检测Face Anti-spoofing前世今生:作者(Fisher Yu )