热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Scrapy–调试内存泄漏

在Scrapy中,请求、响应和项等对象的生命周期是有限的:它们被创建、使用一段时间,最后被销毁。从所有这些对象中,请求可能是生命周期最长的请求,因为它一直在调度程序队列中等待,直到

在Scrapy中,请求、响应和项等对象的生命周期是有限的:它们被创建、使用一段时间,最后被销毁。

从所有这些对象中,请求可能是生命周期最长的请求,因为它一直在调度程序队列中等待,直到需要处理它为止。有关详细信息,请参阅 体系结构概述 .

由于这些零碎的物体有(相当长的)寿命,总有在没有正确释放它们的情况下将它们累积到内存中的风险,从而导致所谓的“内存泄漏”。

为了帮助调试内存泄漏,scrapy提供了一种内置机制,用于跟踪调用的对象引用 trackref ,您还可以使用第三方库 muppy 有关更高级的内存调试(请参阅下面的详细信息)。两种机制都必须从 Telnet Console .


内存泄漏的常见原因¶

Scrapy开发人员传递请求中引用的对象(例如,使用 cb_kwargsmeta 属性或请求回调函数),它有效地将这些引用对象的生存期限制为请求的生存期。到目前为止,这是导致零碎项目内存泄漏的最常见原因,对于新手来说,这是一个很难调试的原因。

在大型项目中,蜘蛛通常是由不同的人编写的,其中一些蜘蛛可能会“泄漏”,从而在其他(写得好的)蜘蛛同时运行时影响其他蜘蛛,而这反过来又会影响整个爬行过程。

如果您没有正确地释放(以前分配的)资源,那么泄漏也可能来自您编写的定制中间件、管道或扩展。例如,在上分配资源 spider_opened 但不释放它们 spider_closed 如果你跑步,可能会引起问题 multiple spiders per process .


请求太多?¶

默认情况下,scrapy将请求队列保存在内存中;它包括 Request 对象和请求属性中引用的所有对象(例如 cb_kwargsmeta )虽然不一定是泄漏,但这可能会占用大量内存。有可能 persistent job queue 有助于控制内存使用。




使用调试内存泄漏 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() 首先获取每个类名的所有跟踪活动对象的列表。






用muppy调试内存泄漏¶

trackref 提供了一种非常方便的机制来跟踪内存泄漏,但它只跟踪更可能导致内存泄漏的对象。然而,在其他情况下,内存泄漏可能来自其他(或多或少模糊)对象。如果这是你的案子,而且你用 trackref ,你还有另一个资源:muppy类库。

你可以从 Pympler .

如果你使用 pip ,可以使用以下命令安装muppy::


pip install Pympler


下面是一个使用muppy查看堆中可用的所有Python对象的示例:


>>> from pympler import muppy
>>> all_objects = muppy.get_objects()
>>> len(all_objects)
28667
>>> from pympler import summary
>>> suml = summary.summarize(all_objects)
>>> summary.print_(suml)
types | # objects | total size
==================================== | =========== | ============

















有关Muppy的更多信息,请参阅 muppy documentation .



无泄漏泄漏¶

有时,您可能会注意到您的废进程的内存使用只会增加,但不会减少。不幸的是,即使Scrapy和您的项目都没有泄漏内存,也可能发生这种情况。这是由于Python的一个(不太常见)已知问题造成的,在某些情况下,该问题可能不会将释放的内存返回到操作系统。有关此问题的详细信息,请参阅:


  • Python Memory Management


  • Python Memory Management Part 2


  • Python Memory Management Part 3


Evan Jones提出的改进建议,详情见 this paper 在python 2.5中进行了合并,但这只会减少问题,并不能完全解决问题。引用论文:


不幸的是,这个补丁只能在竞技场中不再分配对象的情况下释放竞技场。这意味着 Scrapy 化是一个大问题。一个应用程序可以有许多兆字节的空闲内存,分散在所有的区域中,但是它将无法释放其中的任何一个。这是所有内存分配器都遇到的问题。解决这个问题的唯一方法是移动到一个压缩垃圾收集器,它能够移动内存中的对象。这将需要对python解释器进行重大更改。


为了保持内存消耗合理,可以将作业拆分为几个较小的作业或启用 persistent job queue 不时停止/启动Spider。


内存泄漏的常见原因¶

Scrapy开发人员传递请求中引用的对象(例如,使用 cb_kwargsmeta 属性或请求回调函数),它有效地将这些引用对象的生存期限制为请求的生存期。到目前为止,这是导致零碎项目内存泄漏的最常见原因,对于新手来说,这是一个很难调试的原因。

在大型项目中,蜘蛛通常是由不同的人编写的,其中一些蜘蛛可能会“泄漏”,从而在其他(写得好的)蜘蛛同时运行时影响其他蜘蛛,而这反过来又会影响整个爬行过程。

如果您没有正确地释放(以前分配的)资源,那么泄漏也可能来自您编写的定制中间件、管道或扩展。例如,在上分配资源 spider_opened 但不释放它们 spider_closed 如果你跑步,可能会引起问题 multiple spiders per process .


请求太多?¶

默认情况下,scrapy将请求队列保存在内存中;它包括 Request 对象和请求属性中引用的所有对象(例如 cb_kwargsmeta )虽然不一定是泄漏,但这可能会占用大量内存。有可能 persistent job queue 有助于控制内存使用。


请求太多?¶

默认情况下,scrapy将请求队列保存在内存中;它包括 Request 对象和请求属性中引用的所有对象(例如 cb_kwargsmeta )虽然不一定是泄漏,但这可能会占用大量内存。有可能 persistent job queue 有助于控制内存使用。

使用调试内存泄漏 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() 首先获取每个类名的所有跟踪活动对象的列表。




跟踪哪些对象?¶

被跟踪的对象 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() 首先获取每个类名的所有跟踪活动对象的列表。



用muppy调试内存泄漏¶

trackref 提供了一种非常方便的机制来跟踪内存泄漏,但它只跟踪更可能导致内存泄漏的对象。然而,在其他情况下,内存泄漏可能来自其他(或多或少模糊)对象。如果这是你的案子,而且你用 trackref ,你还有另一个资源:muppy类库。

你可以从 Pympler .

如果你使用 pip ,可以使用以下命令安装muppy::


pip install Pympler


下面是一个使用muppy查看堆中可用的所有Python对象的示例:


>>> from pympler import muppy
>>> all_objects = muppy.get_objects()
>>> len(all_objects)
28667
>>> from pympler import summary
>>> suml = summary.summarize(all_objects)
>>> summary.print_(suml)
types | # objects | total size
==================================== | =========== | ============

















有关Muppy的更多信息,请参阅 muppy documentation .

无泄漏泄漏¶

有时,您可能会注意到您的废进程的内存使用只会增加,但不会减少。不幸的是,即使Scrapy和您的项目都没有泄漏内存,也可能发生这种情况。这是由于Python的一个(不太常见)已知问题造成的,在某些情况下,该问题可能不会将释放的内存返回到操作系统。有关此问题的详细信息,请参阅:


  • Python Memory Management


  • Python Memory Management Part 2


  • Python Memory Management Part 3


Evan Jones提出的改进建议,详情见 this paper 在python 2.5中进行了合并,但这只会减少问题,并不能完全解决问题。引用论文:


不幸的是,这个补丁只能在竞技场中不再分配对象的情况下释放竞技场。这意味着 Scrapy 化是一个大问题。一个应用程序可以有许多兆字节的空闲内存,分散在所有的区域中,但是它将无法释放其中的任何一个。这是所有内存分配器都遇到的问题。解决这个问题的唯一方法是移动到一个压缩垃圾收集器,它能够移动内存中的对象。这将需要对python解释器进行重大更改。


为了保持内存消耗合理,可以将作业拆分为几个较小的作业或启用 persistent job queue 不时停止/启动Spider。


推荐阅读
  • 深入探讨CPU虚拟化与KVM内存管理
    本文详细介绍了现代服务器架构中的CPU虚拟化技术,包括SMP、NUMA和MPP三种多处理器结构,并深入探讨了KVM的内存虚拟化机制。通过对比不同架构的特点和应用场景,帮助读者理解如何选择最适合的架构以优化性能。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • andr ... [详细]
  • 本文提供了使用Java实现Bellman-Ford算法解决POJ 3259问题的代码示例,详细解释了如何通过该算法检测负权环来判断时间旅行的可能性。 ... [详细]
  • 作者:守望者1028链接:https:www.nowcoder.comdiscuss55353来源:牛客网面试高频题:校招过程中参考过牛客诸位大佬的面经,但是具体哪一块是参考谁的我 ... [详细]
  • 本文详细探讨了HTML表单中GET和POST请求的区别,包括它们的工作原理、数据传输方式、安全性及适用场景。同时,通过实例展示了如何在Servlet中处理这两种请求。 ... [详细]
  • 利用决策树预测NBA比赛胜负的Python数据挖掘实践
    本文通过使用2013-14赛季NBA赛程与结果数据集以及2013年NBA排名数据,结合《Python数据挖掘入门与实践》一书中的方法,展示如何应用决策树算法进行比赛胜负预测。我们将详细讲解数据预处理、特征工程及模型评估等关键步骤。 ... [详细]
  • 本文探讨了如何通过预处理器开关选择不同的类实现,并解决在特定情况下遇到的链接器错误。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 本文介绍了如何在 C# 和 XNA 框架中实现一个自定义的 3x3 矩阵类(MMatrix33),旨在深入理解矩阵运算及其应用场景。该类参考了 AS3 Starling 和其他相关资源,以确保算法的准确性和高效性。 ... [详细]
  • yikesnews第11期:微软Office两个0day和一个提权0day
    点击阅读原文可点击链接根据法国大选被黑客干扰,发送了带漏洞的文档Trumps_Attack_on_Syria_English.docx而此漏洞与ESET&FireEy ... [详细]
  • 由二叉树到贪心算法
    二叉树很重要树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。单就面试而言,在 ... [详细]
author-avatar
去弥O补从前所有不完整
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有