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

前言

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

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

再启用中间件查看效果:

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

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

(0)

相关推荐

  • 在Linux系统上安装Python的Scrapy框架的教程

    这是一款提取网站数据的开源工具.Scrapy框架用Python开发而成,它使抓取工作又快又简单,且可扩展.我们已经在virtual box中创建一台虚拟机(VM)并且在上面安装了Ubuntu 14.04 LTS. 安装 Scrapy Scrapy依赖于Python.开发库和pip.Python最新的版本已经在Ubuntu上预装了.因此我们在安装Scrapy之前只需安装pip和python开发库就可以了. pip是作为python包索引器easy_install的替代品,用于安装和管理Python

  • Python3安装Scrapy的方法步骤

    本文介绍了Python3安装Scrapy的方法步骤,分享给大家,具体如下: 运行平台:Windows Python版本:Python3.x IDE:Sublime text3 一.Scrapy简介 Scrapy是一个为了爬取网站数据提取结构性数据而编写的应用框架,可以应用于数据挖掘,信息处理或存储历史数据等一些列的程序中.Scrapy最初就是为了网络爬取而设计的.现在,Scrapy已经推出了曾承诺过的Python3.x版本. 为什么学习Scrapy呢?它能我们更好的完成爬虫任务,自己写Pytho

  • python使用scrapy解析js示例

    复制代码 代码如下: from selenium import selenium class MySpider(CrawlSpider):    name = 'cnbeta'    allowed_domains = ['cnbeta.com']    start_urls = ['http://www.jb51.net'] rules = (        # Extract links matching 'category.php' (but not matching 'subsectio

  • Python实现从脚本里运行scrapy的方法

    本文实例讲述了Python实现从脚本里运行scrapy的方法.分享给大家供大家参考.具体如下: 复制代码 代码如下: #!/usr/bin/python import os os.environ.setdefault('SCRAPY_SETTINGS_MODULE', 'project.settings') #Must be at the top before other imports from scrapy import log, signals, project from scrapy.x

  • Python的Scrapy爬虫框架简单学习笔记

     一.简单配置,获取单个网页上的内容. (1)创建scrapy项目 scrapy startproject getblog (2)编辑 items.py # -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # http://doc.scrapy.org/en/latest/topics/items.html from scrapy.item import

  • python使用scrapy发送post请求的坑

    使用requests发送post请求 先来看看使用requests来发送post请求是多少好用,发送请求 Requests 简便的 API 意味着所有 HTTP 请求类型都是显而易见的.例如,你可以这样发送一个 HTTP POST 请求: >>>r = requests.post('http://httpbin.org/post', data = {'key':'value'}) 使用data可以传递字典作为参数,同时也可以传递元祖 >>>payload = (('ke

  • 零基础写python爬虫之使用Scrapy框架编写爬虫

    网络爬虫,是在网上进行数据抓取的程序,使用它能够抓取特定网页的HTML数据.虽然我们利用一些库开发一个爬虫程序,但是使用框架可以大大提高效率,缩短开发时间.Scrapy是一个使用Python编写的,轻量级的,简单轻巧,并且使用起来非常的方便.使用Scrapy可以很方便的完成网上数据的采集工作,它为我们完成了大量的工作,而不需要自己费大力气去开发. 首先先要回答一个问题. 问:把网站装进爬虫里,总共分几步? 答案很简单,四步: 新建项目 (Project):新建一个新的爬虫项目 明确目标(Item

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

    前言 使用scrapy进行大型爬取任务的时候(爬取耗时以天为单位),无论主机网速多好,爬完之后总会发现scrapy日志中"item_scraped_count"不等于预先的种子数量,总有一部分种子爬取失败,失败的类型可能有如下图两种(下图为scrapy爬取结束完成时的日志): scrapy中常见的异常包括但不限于:download error(蓝色区域), http code 403/500(橙色区域). 不管是哪种异常,我们都可以参考scrapy自带的retry中间件写法来编写自己的

  • 如何在scrapy中集成selenium爬取网页的方法

    1.背景 我们在爬取网页时一般会使用到三个爬虫库:requests,scrapy,selenium.requests一般用于小型爬虫,scrapy用于构建大的爬虫项目,而selenium主要用来应付负责的页面(复杂js渲染的页面,请求非常难构造,或者构造方式经常变化). 在我们面对大型爬虫项目时,肯定会优选scrapy框架来开发,但是在解析复杂JS渲染的页面时,又很麻烦. 尽管使用selenium浏览器渲染来抓取这样的页面很方便,这种方式下,我们不需要关心页面后台发生了怎样的请求,也不需要分析整

  • Perl中捕获警告信息、异常信息并写入日志详解

    虽然建议在每个Perl脚本和模块中开启警告,可是你又不想用户看到Perl发出的警告. 一方面你想在代码前面使用use warnings作为你的安全网,另一方面,通常警告会出现在屏幕上.多数情况下,客户不知道如何处理这些警告.如果幸运的话这些警告仅仅让客户惊讶一下,当然,不幸的是他们尝试着去修复它们... (这里说的不是Perl程序员.) 第三方面,你或许想要保存这些警告供之后分析. 此外,在很多地方还有很多Perl脚本和应用程序没有使用use warnings也没有在#!行中使用-w.加上了us

  • selenium在scrapy中的使用代码

    在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值.但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据.那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值.本文重点给大家介绍selenium在scrapy中的使用,具体内容如下所示: 使用目的 爬

  • .net中捕捉全局未处理异常的三种方式示例

    前言: 我们在实际项目开发中,经常会遇到一些不可预见的异常产生,有的异常在程序运行时就对其进行处理(try) 但是,有的程序不需要每一个地方都用try进行处理,那么针对这种情况,可以参照下面的方式,实现对异常的统一抓取处理,下面话不多说了,来一起看看详细的介绍吧. 方式一.Page_Error处理页面级未处理异常 作用域:当前的.aspx页面 描述:在需要处理的aspx页面的cs文件中,实现Page_Error方法,达到侦听当前页面未处理的异常 protected void Page_Error

  • 详细聊聊如何在C#循环中捕获局部变量

    目录 问题: 解答方案: 总结 问题: 我遇到了一个有趣的问题,它的代码大概是这样的. List<Func<int>> actions = new List<Func<int>>(); int variable = 0; while (variable < 5) { actions.Add(() => variable * 2); ++ variable; } foreach (var act in actions) { Console.Writ

  • 详解如何在 docker 容器中捕获信号

    我们可能都使用过 docker stop 命令来停止正在运行的容器,有时可能会使用 docker kill 命令强行关闭容器或者把某个信号传递给容器中的进程.这些操作的本质都是通过从主机向容器发送信号实现主机与容器中程序的交互.比如我们可以向容器中的应用发送一个重新加载信号,容器中的应用程序在接到信号后执行相应的处理程序完成重新加载配置文件的任务.本文将介绍在 docker 容器中捕获信号的基本知识. 信号(linux) 信号是一种进程间通信的形式.一个信号就是内核发送给进程的一个消息,告诉进程

  • 如何在C#中使用 CancellationToken 处理异步任务

    在 .NET Core 中使用异步编程已经很普遍了, 你在项目中随处可见 async 和 await,它简化了异步操作,允许开发人员,使用同步的方式编写异步代码,你会发现在大部分的异步方法中,都提供了CancellationToken参数,本文主要介绍下 CancellationTokenSource 和 CancellationToken在异步任务中的使用. 手动取消任务 创建一个 CancellationTokenSource,然后调用异步方法时,传入 CancellationToken,它

  • 如何在Python中进行异常处理

    目录 一.抛出异常和自定义异常 1.raise语句 2.自定义异常类型 二.捕捉异常 1.捕捉多个异常 2.获取异常信息 三.finally子句 一.抛出异常和自定义异常 Python中使用用异常对象(exception object)表示异常情况,当程序运行遇到错误后,就会触发发异常.相信大家在编码是都会遇到这种情况,那么如果异常对象并未被处理或捕捉,程序就会用报错然后终止执行. 1.raise语句 Python中的raise 关键字用于触发发一个异常,和我们熟悉的Java中的throw关键字

  • 如何在AngularJs中调用第三方插件库

    在AngularJs中我们会不可避免的使用第三方库,例如jquery插件库.我们不能散乱的在AngularJS中引入这些库,例如在controller中.那么应该怎么在Angular中使用第三方库呢? 如何使用? 很简单,给插件写一个directive. 在这里,我会使用一个简单的jquery插件Toolbar.js 的DEMO. 这是我们如何在jquery中创建一个tooltip的: <!-- Click this to see a toolbar --> <div id="

随机推荐