阅读(276) (0)

scrapy 2.3 使用调试内存泄漏 trackref

2021-06-16 16:43:46 更新

trackref​ 是Scrapy提供的一个模块,用于调试最常见的内存泄漏情况。它基本上跟踪对所有实时请求、响应、项、蜘蛛和选择器对象的引用。

您可以进入telnet控制台并使用 ​prefs()​ 函数的别名 ​print_live_refs()​ 功能:

telnet localhost 6023

>>> prefs()
Live References

ExampleSpider                       1   oldest: 15s ago
HtmlResponse                       10   oldest: 1s ago
Selector                            2   oldest: 0s ago
FormRequest                       878   oldest: 7s ago

如您所见,该报告还显示了每个类中最旧对象的“年龄”。如果每个进程运行多个spider,那么通过查看最早的请求或响应,您很可能会发现哪个spider正在泄漏。您可以使用 ​get_oldest()​ 功能(从telnet控制台)。

跟踪哪些对象?

被跟踪的对象 ​trackrefs​ 都来自这些类(及其所有子类):

  • scrapy.http.Request
  • scrapy.http.Response
  • scrapy.item.Item
  • scrapy.selector.Selector
  • scrapy.spiders.Spider

一个真实的例子

让我们来看一个假设的内存泄漏案例的具体示例。假设我们有一只蜘蛛,上面有一条和这条类似的线:

return Request(f"http://www.somenastyspider.com/product.php?pid={product_id}",
               callback=self.parse, cb_kwargs={'referer': response})

该行正在请求中传递一个响应引用,它有效地将响应生命周期与请求的生命周期联系起来,这肯定会导致内存泄漏。

让我们看看如何通过使用 ​trackref​ 工具。

当爬虫运行几分钟后,我们注意到它的内存使用量增长了很多,我们可以进入它的telnet控制台并检查实时引用:

>>> prefs()
Live References

SomenastySpider                     1   oldest: 15s ago
HtmlResponse                     3890   oldest: 265s ago
Selector                            2   oldest: 0s ago
Request                          3878   oldest: 250s ago

事实上,存在如此多的实时响应(而且它们太老了),这是绝对可疑的,因为与请求相比,响应的生存期应该相对较短。响应的数量与请求的数量相似,因此看起来它们是以某种方式捆绑在一起的。我们现在可以检查蜘蛛的代码,以发现产生泄漏的讨厌的行(在请求中传递响应引用)。

有时,有关活动对象的额外信息可能会有所帮助。让我们检查最早的回答:

>>> from scrapy.utils.trackref import get_oldest
>>> r = get_oldest('HtmlResponse')
>>> r.url
'http://www.somenastyspider.com/product.php?pid=123'

如果您希望遍历所有对象,而不是获取最旧的对象,则可以使用 ​scrapy.utils.trackref.iter_all()​ 功能:

>>> from scrapy.utils.trackref import iter_all
>>> [r.url for r in iter_all('HtmlResponse')]
['http://www.somenastyspider.com/product.php?pid=123',
 'http://www.somenastyspider.com/product.php?pid=584',
...]

蜘蛛太多了?

如果项目并行执行的spider太多,则 ​prefs()​ 很难阅读。因此,该函数具有 ​ignore​ 参数,该参数可用于忽略特定类(及其所有子类)。例如,这不会显示对spider的任何实时引用:

>>> from scrapy.spiders import Spider
>>> prefs(ignore=Spider)

scrapy.utils.trackRef模块

以下是 ​trackref​ 模块。

classscrapy.utils.trackref.object_ref

如果要使用跟踪活动实例,则从该类继承 trackref 模块。

scrapy.utils.trackref.print_live_refs(class_nameignore=NoneType)

打印实时引用的报告,按类名分组。

参数

ignore (type or tuple) -- 如果给定,则将忽略指定类(或类的元组)中的所有对象。

scrapy.utils.trackref.get_oldest(class_name)

返回具有给定类名的最旧活动对象,或者 None 如果没有找到。使用 ​print_live_refs()​ 首先获取每个类名的所有跟踪活动对象的列表。

scrapy.utils.trackref.iter_all(class_name)

返回具有给定类名的所有活动对象的迭代器,或者 None 如果没有找到。使用 ​print_live_refs()​ 首先获取每个类名的所有跟踪活动对象的列表。