如何获取scrapy失败的URL?
Posted
技术标签:
【中文标题】如何获取scrapy失败的URL?【英文标题】:How to get the scrapy failure URLs? 【发布时间】:2012-11-23 08:48:51 【问题描述】:我是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。谢谢!
【问题讨论】:
【参考方案1】:是的,这是可能的。
下面的代码将failed_urls
列表添加到基本蜘蛛类,如果url 的响应状态为404,则将url 附加到它(这需要根据需要扩展以涵盖其他错误状态)。
接下来我添加了一个句柄,它将列表连接成一个字符串,并在蜘蛛关闭时将其添加到蜘蛛的统计信息中。
根据您的 cmets,可以跟踪 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)
【讨论】:
这不再有效。exceptions.NameError: global name 'self' is not defined
发生错误。 BaseSpider
现在只是 Spider
doc.scrapy.org/en/0.24/news.html?highlight=basespider#id2 github.com/scrapy/dirbot/blob/master/dirbot/spiders/dmoz.py 但我找不到让您的代码工作的修复程序 @Talvalin。
如上所述,代码已于 2020 年 1 月 1 日更新,以确认它适用于 Scrapy 1.8【参考方案2】:
Scrapy 默认忽略 404 并且不解析它。如果您收到错误代码 404 作为响应,您可以通过一种非常简单的方法来处理。
在 settings.py 中,写:
HTTPERROR_ALLOWED_CODES = [404,403]
然后在你的解析函数中处理响应状态码:
def parse(self,response):
if response.status == 404:
#your action on error
【讨论】:
简单易行。最佳解决方案【参考方案3】:Scrapy 默认忽略 404 错误,它是在 httperror 中间件中定义的。
所以,将 HTTPERROR_ALLOW_ALL = True 添加到您的设置文件中。
之后,您可以通过解析函数访问response.status。
你可以这样处理。
def parse(self,response):
if response.status==404:
print(response.status)
else:
do something
【讨论】:
这是为我做的【参考方案4】:@Talvalin 和 @alecxe 的回答对我帮助很大,但他们似乎没有捕获不生成响应对象的下载器事件(例如,twisted.internet.error.TimeoutError
和 twisted.web.http.PotentialDataLoss
)。这些错误显示在运行结束时的统计转储中,但没有任何元信息。
我发现here,错误由stats.py 中间件跟踪,在DownloaderStats
类的process_exception
方法中捕获,特别是在ex_class
变量中,它将每个错误类型递增为必要的,然后在运行结束时转储计数。
要将此类错误与来自相应请求对象的信息相匹配,您可以为每个请求添加一个唯一的 id(通过request.meta
),然后将其拉入stats.py
的process_exception
方法中:
self.stats.set_value('downloader/my_errs/0'.format(request.meta), ex_class)
这将为每个没有响应的基于下载器的错误生成一个唯一的字符串。然后,您可以将更改后的 stats.py
保存为其他内容(例如 my_stats.py
),将其添加到下载器中间件(具有正确的优先级),然后禁用库存 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'....
This answer 处理非基于下载器的错误。
【讨论】:
正是我想要的。我认为 Scrapy 应该添加此功能以提供对 URL 等故障信息的便捷访问。 使用scrapy.downloadermiddlewares.stats
,而不是在最新(1.0.5)版本scrapy.contrib.downloadermiddleware.stats
弃用
多么好的答案,但我想问一下,如果还有其他类型的错误,不仅仅是http状态码错误或下载器错误,这些都是我们应该处理的关于网络的所有错误吗?错误?【参考方案5】:
您可以通过两种方式捕获失败的 url。
使用 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
使用信号.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。
【讨论】:
在这种情况下你会如何优雅地关闭蜘蛛? @not2qubit 什么情况? Twisted 似乎发生了一些有趣的事情,所以即使我已经命令蜘蛛关闭,我也会不断收到this error。所以也许有更好的方法来关闭蜘蛛,在它重试之前,甚至在此之前。 @not2qubit 检查 errback 和 request_dropped 中的self.crawler.crawling
。如果你关闭蜘蛛,self.crawler.crawling
将是False
。【参考方案6】:
除了这些答案之外,如果您想跟踪 Twisted 错误,我会看看使用 Request 对象的 errback
参数,您可以在该参数上设置要使用 Twisted Failure 调用的回调函数在请求失败时。
除了url,这个方法还可以让你跟踪失败的类型。
然后您可以使用以下命令记录 url:failure.request.url
(其中 failure
是传递给 errback
的 Twisted Failure
对象)。
# 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 记录器的调用现在是 depreciated,所以我已经调整了我的示例以使用 Python 内置的 logging):
https://doc.scrapy.org/en/latest/topics/request-response.html#topics-request-response-ref-errbacks
【讨论】:
【参考方案7】:这是另一个如何处理和收集 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
文件中的项目列表。
【讨论】:
【参考方案8】:这是关于这个问题的更新。我遇到了类似的问题,需要使用 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)
我希望这对以后遇到同样问题的人有所帮助。
【讨论】:
【参考方案9】:从 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
【讨论】:
以上是关于如何获取scrapy失败的URL?的主要内容,如果未能解决你的问题,请参考以下文章