scrapy 2.3 使用调试内存泄漏 trackref
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
模块。
- class
scrapy.utils.trackref.
object_ref
-
如果要使用跟踪活动实例,则从该类继承
trackref
模块。
scrapy.utils.trackref.
print_live_refs
(class_name, ignore=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()
首先获取每个类名的所有跟踪活动对象的列表。