(原创)scrapy的MemoryError
背景知识
MemoryError
MemoryError是python的常见异常之一,可以通过查看python的在线文档来进行了解:
exception MemoryError
Raised when an operation runs out of memory but the situation may still be rescued (by deleting some objects). The associated value is a string indicating what kind of (internal) operation ran out of memory. Note that because of the underlying memory management architecture (C’s malloc() function), the interpreter may not always be able to completely recover from this situation; it nevertheless raises an exception so that a stack traceback can be printed, in case a run-away program was the cause.
从字义上看,这个异常是因为堆上的可用内存空间不足导致的。
scrapy爬虫的内存检查
scrapy爬虫具有其独立的内存检查工具,可以利用其自带的telnet控制台来进行内存检查。
在Scrapy中,类似Requests, Response及Items的对象具有有限的生命周期: 他们被创建,使用,最后被销毁。
这些对象中,Request的生命周期应该是最长的,其会在调度队列(Scheduler queue)中一直等待,直到被处理。 更多内容请参考 架构概览 。
由于这些Scrapy对象拥有很长的生命,因此将这些对象存储在内存而没有正确释放的危险总是存在。 而这导致了所谓的”内存泄露”。
为了帮助调试内存泄露,Scrapy提供了跟踪对象引用的机制,叫做 trackref , 或者您也可以使用第三方提供的更先进内存调试库 Guppy (更多内容请查看下面)。而这都必须在 Telnet终端 中使用。
问题代码:
2017-07-12 12:26:06 [scrapy.extensions.logstats] INFO: Crawled 1412 pages (at 0 pages/min), scraped 651 items (at 0 items/min)
Unhandled Error
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 440, in _finished
self.finisher(rest)
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 954, in dispatcher
return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 1537, in _finishResponse_WAITING
self._disconnectParser(reason)
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 1563, in _disconnectParser
parser.connectionLost(reason)
--- <exception caught here> ---
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 550, in connectionLost
self.response._bodyDataFinished()
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 954, in dispatcher
return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/twisted/web/_newclient.py", line 1210, in _bodyDataFinished_CONNECTED
self._bodyProtocol.connectionLost(reason)
File "/usr/local/lib/python2.7/dist-packages/scrapy/core/downloader/handlers/http11.py", line 406, in connectionLost
body = self._bodybuf.getvalue()
exceptions.MemoryError:
爬虫程序运行一段时间之后,几乎所有的request在处理过程中都出现了这样的问题。
使用telnet进行检查:
虽然scrapy的爬取过程暂时停止了,但是其程序本身仍然在运行,这是可以使用telnet终端进行检查
>telnet localhost 6023 #也可能是6023~6027中的某个值,根据设置而定。
>prefs()
Live References
CommitItem 162 oldest: 6183s ago
GitlabSpider 1 oldest: 44287s ago
HtmlResponse 1923 oldest: 39205s ago
PjItem 177 oldest: 44255s ago
Request 2023 oldest: 44260s ago
Selector 228 oldest: 39210s ago
这里可以看到当前运行过程中堆上内存的所有对象。
代码修改
确定是内存中的并发请求过多导致的问题,这里我首先想到了这个办法:
(1)对生成request的方法进行改造,让其每次生成一部分request之后暂停生成,但是无法确定每次生成的request何时可以运行完毕。
(2)目前的爬虫带宽实际上也无法支持多达32个爬取线程,现在对爬取线程进行数目限制,让并发的请求最多只做8个,这样速度也没有明显的下降,而内存中的待处理HTTP请求对象数目也可以维持在300到500个左右。不会导致内存泄露。
Live References
CommitItem 123 oldest: 1073s ago
GitlabSpider 1 oldest: 4883s ago
HtmlResponse 52 oldest: 847s ago
PjItem 177 oldest: 4881s ago
Request 360 oldest: 4881s ago
Selector 51 oldest: 847s ago