49

我是scrapy的新手,我知道这是一个很棒的爬虫框架!

在我的项目中,我发送了超过 90,000 个请求,但其中一些请求失败了。我将日志级别设置为 INFO,我只能看到一些统计信息,但没有详细信息。

2012-12-05 21:03:04+0800 [pd_spider] INFO: Dumping spider stats:
{'downloader/exception_count': 1,
 'downloader/exception_type_count/twisted.internet.error.ConnectionDone': 1,
 'downloader/request_bytes': 46282582,
 'downloader/request_count': 92383,
 'downloader/request_method_count/GET': 92383,
 'downloader/response_bytes': 123766459,
 'downloader/response_count': 92382,
 'downloader/response_status_count/200': 92382,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2012, 12, 5, 13, 3, 4, 836000),
 'item_scraped_count': 46191,
 'request_depth_max': 1,
 'scheduler/memory_enqueued': 92383,
 'start_time': datetime.datetime(2012, 12, 5, 12, 23, 25, 427000)}

有什么方法可以获得更详细的报告吗?例如,显示那些失败的 URL。谢谢!

4

9 回答 9

55

是的,这是可能的。

  • 如果 url 的响应状态是 404,下面的代码将一个failed_urls列表添加到一个基本的蜘蛛类并附加 urls (这需要扩展以涵盖其他需要的错误状态)。
  • 接下来,我添加了一个句柄,它将列表连接成一个字符串,并在蜘蛛关闭时将其添加到蜘蛛的统计信息中。
  • 根据您的评论,可以跟踪 Twisted 错误,下面的一些答案提供了有关如何处理该特定用例的示例
  • 代码已更新为可与 Scrapy 1.8 一起使用。这一切都应该归功于Juliano Mendieta,因为我所做的只是添加他建议的编辑并确认蜘蛛按预期工作。

from scrapy import Spider, signals

class MySpider(Spider):
    handle_httpstatus_list = [404] 
    name = "myspider"
    allowed_domains = ["example.com"]
    start_urls = [
        'http://www.example.com/thisurlexists.html',
        'http://www.example.com/thisurldoesnotexist.html',
        'http://www.example.com/neitherdoesthisone.html'
    ]

    def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.failed_urls = []

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = super(MySpider, cls).from_crawler(crawler, *args, **kwargs)
        crawler.signals.connect(spider.handle_spider_closed, signals.spider_closed)
        return spider

    def parse(self, response):
        if response.status == 404:
            self.crawler.stats.inc_value('failed_url_count')
            self.failed_urls.append(response.url)

    def handle_spider_closed(self, reason):
        self.crawler.stats.set_value('failed_urls', ', '.join(self.failed_urls))

    def process_exception(self, response, exception, spider):
        ex_class = "%s.%s" % (exception.__class__.__module__, exception.__class__.__name__)
        self.crawler.stats.inc_value('downloader/exception_count', spider=spider)
        self.crawler.stats.inc_value('downloader/exception_type_count/%s' % ex_class, spider=spider)

示例输出(请注意,只有在实际抛出异常时才会出现下载器/exception_count* 统计信息 - 我通过在关闭无线适配器后尝试运行蜘蛛来模拟它们):

2012-12-10 11:15:26+0000 [myspider] INFO: Dumping Scrapy stats:
    {'downloader/exception_count': 15,
     'downloader/exception_type_count/twisted.internet.error.DNSLookupError': 15,
     'downloader/request_bytes': 717,
     'downloader/request_count': 3,
     'downloader/request_method_count/GET': 3,
     'downloader/response_bytes': 15209,
     'downloader/response_count': 3,
     'downloader/response_status_count/200': 1,
     'downloader/response_status_count/404': 2,
     'failed_url_count': 2,
     'failed_urls': 'http://www.example.com/thisurldoesnotexist.html, http://www.example.com/neitherdoesthisone.html'
     'finish_reason': 'finished',
     'finish_time': datetime.datetime(2012, 12, 10, 11, 15, 26, 874000),
     'log_count/DEBUG': 9,
     'log_count/ERROR': 2,
     'log_count/INFO': 4,
     'response_received_count': 3,
     'scheduler/dequeued': 3,
     'scheduler/dequeued/memory': 3,
     'scheduler/enqueued': 3,
     'scheduler/enqueued/memory': 3,
     'spider_exceptions/NameError': 2,
     'start_time': datetime.datetime(2012, 12, 10, 11, 15, 26, 560000)}
于 2012-12-10T11:22:28.187 回答
19

这是另一个如何处理和收集 404 错误的示例(查看 github 帮助页面):

from scrapy.selector import HtmlXPathSelector
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.item import Item, Field


class GitHubLinkItem(Item):
    url = Field()
    referer = Field()
    status = Field()


class GithubHelpSpider(CrawlSpider):
    name = "github_help"
    allowed_domains = ["help.github.com"]
    start_urls = ["https://help.github.com", ]
    handle_httpstatus_list = [404]
    rules = (Rule(SgmlLinkExtractor(), callback='parse_item', follow=True),)

    def parse_item(self, response):
        if response.status == 404:
            item = GitHubLinkItem()
            item['url'] = response.url
            item['referer'] = response.request.headers.get('Referer')
            item['status'] = response.status

            return item

只需运行scrapy runspider-o output.json查看output.json文件中的项目列表。

于 2013-01-29T22:49:49.240 回答
16

Scrapy 默认忽略 404 并且不解析它。如果您收到错误代码 404 作为响应,您可以通过一种非常简单的方法来处理它。

settings.py 中,写:

HTTPERROR_ALLOWED_CODES = [404,403]

然后在你的解析函数中处理响应状态码:

def parse(self,response):
    if response.status == 404:
        #your action on error
于 2015-11-28T13:45:32.983 回答
14

@Talvalin 和 @alecxe 的答案对我帮助很大,但它们似乎没有捕获不生成响应对象的下载器事件(例如,twisted.internet.error.TimeoutErrortwisted.web.http.PotentialDataLoss)。这些错误显示在运行结束时的统计转储中,但没有任何元信息。

正如我在这里发现的那样,错误由stats.py中间件跟踪,在DownloaderStats类的process_exception方法中捕获,特别是在ex_class变量中,它根据需要递增每个错误类型,然后在运行结束时转储计数。

要将此类错误与来自相应请求对象的信息相匹配,您可以为每个请求添加一个唯一的 id(通过request.meta),然后将其拉入 的process_exception方法stats.py

self.stats.set_value('downloader/my_errs/{0}'.format(request.meta), ex_class)

这将为每个没有响应的基于下载器的错误生成一个唯一的字符串。然后,您可以将更改stats.py的内容另存为其他内容(例如my_stats.py),将其添加到 downloadermiddlewares(具有正确的优先级),然后禁用 stock stats.py

DOWNLOADER_MIDDLEWARES = {
    'myproject.my_stats.MyDownloaderStats': 850,
    'scrapy.downloadermiddleware.stats.DownloaderStats': None,
    }

运行结束时的输出如下所示(此处使用元信息,其中每个请求 url 映射到一个 group_id 和 member_id 由斜杠分隔,如'0/14'):

{'downloader/exception_count': 3,
 'downloader/exception_type_count/twisted.web.http.PotentialDataLoss': 3,
 'downloader/my_errs/0/1': 'twisted.web.http.PotentialDataLoss',
 'downloader/my_errs/0/38': 'twisted.web.http.PotentialDataLoss',
 'downloader/my_errs/0/86': 'twisted.web.http.PotentialDataLoss',
 'downloader/request_bytes': 47583,
 'downloader/request_count': 133,
 'downloader/request_method_count/GET': 133,
 'downloader/response_bytes': 3416996,
 'downloader/response_count': 130,
 'downloader/response_status_count/200': 95,
 'downloader/response_status_count/301': 24,
 'downloader/response_status_count/302': 8,
 'downloader/response_status_count/500': 3,
 'finish_reason': 'finished'....}

这个答案处理非基于下载器的错误。

于 2013-08-26T21:52:47.307 回答
5

从 scrapy 0.24.6 开始,alecxe 建议的方法不会捕获起始 URL 的错误。要使用起始 URL 记录错误,您需要覆盖parse_start_urls. 为此目的调整 alexce 的答案,您将得到:

from scrapy.selector import HtmlXPathSelector
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.item import Item, Field

class GitHubLinkItem(Item):
    url = Field()
    referer = Field()
    status = Field()

class GithubHelpSpider(CrawlSpider):
    name = "github_help"
    allowed_domains = ["help.github.com"]
    start_urls = ["https://help.github.com", ]
    handle_httpstatus_list = [404]
    rules = (Rule(SgmlLinkExtractor(), callback='parse_item', follow=True),)

    def parse_start_url(self, response):
        return self.handle_response(response)

    def parse_item(self, response):
        return self.handle_response(response)

    def handle_response(self, response):
        if response.status == 404:
            item = GitHubLinkItem()
            item['url'] = response.url
            item['referer'] = response.request.headers.get('Referer')
            item['status'] = response.status

            return item
于 2015-05-29T11:08:13.543 回答
5

这是关于这个问题的更新。我遇到了类似的问题,需要使用 scrapy 信号来调用管道中的函数。我已经编辑了@Talvalin 的代码,但为了更清楚起见,我想给出一个答案。

基本上,您应该添加 self 作为 handle_spider_closed 的参数。您还应该在 init 中调用调度程序,以便可以将蜘蛛实例 (self) 传递给处理方法。

from scrapy.spider import Spider
from scrapy.xlib.pydispatch import dispatcher
from scrapy import signals

class MySpider(Spider):
    handle_httpstatus_list = [404] 
    name = "myspider"
    allowed_domains = ["example.com"]
    start_urls = [
        'http://www.example.com/thisurlexists.html',
        'http://www.example.com/thisurldoesnotexist.html',
        'http://www.example.com/neitherdoesthisone.html'
    ]

    def __init__(self, category=None):
        self.failed_urls = []
        # the dispatcher is now called in init
        dispatcher.connect(self.handle_spider_closed,signals.spider_closed) 


    def parse(self, response):
        if response.status == 404:
            self.crawler.stats.inc_value('failed_url_count')
            self.failed_urls.append(response.url)

    def handle_spider_closed(self, spider, reason): # added self 
        self.crawler.stats.set_value('failed_urls',','.join(spider.failed_urls))

    def process_exception(self, response, exception, spider):
        ex_class = "%s.%s" % (exception.__class__.__module__,  exception.__class__.__name__)
        self.crawler.stats.inc_value('downloader/exception_count', spider=spider)
        self.crawler.stats.inc_value('downloader/exception_type_count/%s' % ex_class, spider=spider)

我希望这对将来遇到同样问题的人有所帮助。

于 2015-07-06T09:44:48.580 回答
3

除了其中一些答案之外,如果您想跟踪 Twisted 错误,我会看看使用 Request 对象的errback参数,您可以在该参数上设置一个回调函数,以便在请求失败时使用Twisted Failure调用。除了 url,此方法还可以让您跟踪失败的类型。

然后,您可以使用以下方法记录 url:(传入的 Twisted对象failure.request.url在哪里)。failureFailureerrback

# these would be in a Spider
def start_requests(self):
    for url in self.start_urls:
        yield scrapy.Request(url, callback=self.parse,
                                  errback=self.handle_error)

def handle_error(self, failure):
    url = failure.request.url
    logging.error('Failure type: %s, URL: %s', failure.type,
                                               url)

Scrapy 文档给出了一个完整的例子来说明如何做到这一点,除了对 Scrapy 记录器的调用现在已经贬值了,所以我已经调整了我的例子来使用 Python 的内置日志记录):

https://doc.scrapy.org/en/latest/topics/request-response.html#topics-request-response-ref-errbacks

于 2017-08-31T23:37:24.270 回答
2

您可以通过两种方式捕获失败的 url。

  1. 使用 errback 定义 scrapy 请求

    class TestSpider(scrapy.Spider):
        def start_requests(self):
            yield scrapy.Request(url, callback=self.parse, errback=self.errback)
    
        def errback(self, failure):
            '''handle failed url (failure.request.url)'''
            pass
    
  2. 使用信号.item_dropped

    class TestSpider(scrapy.Spider):
        def __init__(self):
            crawler.signals.connect(self.request_dropped, signal=signals.request_dropped)
    
        def request_dropped(self, request, spider):
            '''handle failed url (request.url)'''
            pass
    

[!Notice]带有 errback 的 Scrapy 请求无法捕获一些自动重试失败,例如连接错误,设置中的RETRY_HTTP_CODES

于 2018-05-28T15:52:29.230 回答
2

基本上 Scrapy 默认忽略 404 错误,它是在 httperror 中间件中定义的。

因此,将HTTPERROR_ALLOW_ALL = True添加到您的设置文件中。

在此之后,您可以通过parse 函数访问response.status

你可以这样处理。

def parse(self,response):
    if response.status==404:
        print(response.status)
    else:
        do something
于 2018-12-04T11:41:10.633 回答