Scrapy 中的内存泄漏

Memory Leak in Scrapy

我编写了以下代码来抓取电子邮件地址(用于测试目的):

import scrapy
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.selector import Selector
from crawler.items import EmailItem

class LinkExtractorSpider(CrawlSpider):
    name = 'emailextractor'
    start_urls = ['http://news.google.com']

    rules = ( Rule (LinkExtractor(), callback='process_item', follow=True),)

    def process_item(self, response):
        refer = response.url
        items = list()
        for email in Selector(response).re("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}"):

            emailitem = EmailItem()
            emailitem['email'] = email
            emailitem['refer'] = refer
            items.append(emailitem)
        return items

不幸的是,似乎没有正确关闭对Requests的引用,与scrapy telnet控制台一样,Requests的数量增加了5k/s。在大约 3 分钟和 10k 抓取页面后,我的系统开始交换(8GB RAM)。 任何人都知道出了什么问题? 我已经尝试使用

删除引用和 "copied" 字符串
emailitem['email'] = ''.join(email)

没有成功。 抓取后,项目被保存到 BerkeleyDB 中,计算它们的出现次数(使用管道),因此引用应该在那之后消失。

退回一组物品和分别产生每件物品有什么区别?

编辑:

经过一段时间的调试后,我发现请求没有被释放,因此我得到了:

$> nc localhost 6023
>>> prefs()
Live References
Request 10344   oldest: 536s ago
>>> from scrapy.utils.trackref import get_oldest
>>> r = get_oldest('Request')
>>> r.url
<GET http://news.google.com>

这实际上是开始 url。 有人知道问题出在哪里吗?缺少对 Request 对象的引用在哪里?

编辑2:

在服务器(具有 64GB RAM)上 运行 约 12 小时后,使用的 RAM 约为 16GB(使用 ps,即使 ps 不是正确的工具为了它)。问题是,被抓取的页面数量正在显着下降,并且抓取的项目数量自几个小时以来一直保持为 0:

INFO: Crawled 122902 pages (at 82 pages/min), scraped 3354 items (at 0 items/min)

编辑3: 我做了 objgraph 分析,结果如下图(感谢@Artur Gaspar):

我好像影响不了?

如果您 yield 每个项目单独,Python 解释器执行的代码不同:它不再是函数,而是 generator.

这样,就不会创建完整的列表,并且当使用生成器的代码请求下一项时,每个项目都会一次分配一个内存。

所以,可能是您没有内存泄漏,您只是分配了很多内存,大约 10k 页是列表一页所用内存的时间。

当然你仍然可能有真正的内存泄漏,有tips for debugging leaks in Scrapy here

我的最终答案是将基于磁盘的队列与工作目录结合使用作为运行时参数。

这是将以下代码添加到 settings.py:

DEPTH_PRIORITY = 1 
SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'

之后,使用以下命令行启动搜寻器会使更改在给定目录中持久化:

scrapy crawl {spidername} -s JOBDIR=crawls/{spidername} see scrapy docs for details

这种方法的额外好处是,可以随时暂停和恢复抓取。 我的爬虫现在运行超过 11 天,阻塞了 ~15GB 内存(磁盘 FIFO 队列的文件缓存内存)

我想指出 Robins 回答的更新(无法在他的 post 中回复,低代表)。

确保您使用队列s 的新语法,因为他们的提议现在已被弃用。那个“s”花了我几天的时间才弄清楚出了什么问题。新语法是这样的:

    DEPTH_PRIORITY = 1 
    SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
    SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'