如何解决使用urllib2时的Python内存泄漏?

9 投票
7 回答
4418 浏览
提问于 2025-04-16 07:16

我正在尝试为我的手机写一个简单的Python脚本,目的是定期加载一个网页,使用的是urllib2。其实我并不太关心服务器的回应,我只想把一些值通过URL传给PHP。不过问题是,Python for S60使用的是比较旧的2.5.4版本的Python核心,这个版本的urllib2模块似乎有内存泄漏的问题。我看到在各种网络通信中似乎都有类似的问题。这个bug几年前就有人在这里报告过,同时也有人提供了一些解决方法。我尝试了页面上找到的所有方法,还有借助Google的帮助,但我的手机在大约加载70个页面后还是会内存不足。奇怪的是,垃圾回收器似乎也没有什么效果,只是让我的脚本变得更慢。听说更新的3.1版本解决了这个问题,但不幸的是,我等不了一年(或更久)才能等到S60的版本。

这是我在添加了所有找到的小技巧后,脚本的样子:


import urrlib2, httplib, gc
while(true):
 url = "http://something.com/foo.php?parameter=" + value 
 f = urllib2.urlopen(url)
 f.read(1)
 f.fp._sock.recv=None # hacky avoidance
 f.close()
 del f
 gc.collect()
有没有什么建议,让它能一直运行而不出现“无法分配内存”的错误呢?提前谢谢你们,祝好,b_m

更新: 我在内存耗尽之前成功连接了92次,但这仍然不够好。

更新2: 我尝试了之前建议的socket方法,这个是目前为止第二好的(错误的)解决方案:


class UpdateSocketThread(threading.Thread):
  def run(self):
  global data
  while 1:
  url = "/foo.php?parameter=%d"%data
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect(('something.com', 80))
  s.send('GET '+url+' HTTP/1.0\r\n\r\n')
  s.close()
  sleep(1)
我也尝试了上面提到的小技巧。线程在大约50次上传后就关闭了(手机还有50MB的内存,但显然Python的环境没有)。

更新: 我觉得我离解决方案更近了!我尝试在不关闭和重新打开socket的情况下发送多个数据。这可能是关键,因为这种方法只会留下一个打开的文件描述符。问题是:


import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.connect(("something.com", 80))
socket.send("test") #returns 4 (sent bytes, which is cool)
socket.send("test") #4
socket.send("test") #4
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns the number of sent bytes, ok
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns 0 on the phone, error on Windows7*
socket.send("GET /foo.php?parameter=bar HTTP/1.0\r\n\r\n") #returns 0 on the phone, error on Windows7*
socket.send("test") #returns 0, strange...
*: 错误信息:10053,软件导致连接中断

为什么我不能发送多个消息呢??

7 个回答

1

在urllib2这个库里,有一个叫做引用循环的问题,这个问题出现在urllib2.py的第1216行。这个问题从2009年就开始存在,至今仍然没有解决。你可以在这里查看详细信息:https://bugs.python.org/issue1208304

1

根据你提供的链接中的测试代码,我测试了我的Python安装,确认它确实存在内存泄漏的问题。不过,如果按照@Russell的建议,把每个urlopen放在自己的进程里,操作系统应该能自动清理这些内存泄漏。在我的测试中,内存、无法访问的对象和打开的文件数量基本保持不变。我把代码分成了两个文件:

connection.py

import cPickle, urllib2

def connectFunction(queryString):
    conn = urllib2.urlopen('http://something.com/foo.php?parameter='+str(queryString))
    data = conn.read()
    outfile = ('sometempfile'. 'wb')
    cPickle.dump(data, outfile)
    outfile.close()

if __name__ == '__main__':
    connectFunction(sys.argv[1])

###launcher.py
import subprocess, cPickle

#code from your link to check the number of unreachable objects

def print_unreachable_len():
    # check memory on memory leaks
    import gc
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []

    for it in gc.garbage:
        unreachableL.append(it)
    return len(str(unreachableL))

    #my code
    if __name__ == '__main__':        
        print 'Before running a single process:', print_unreachable_len()
        return_value_list = []
        for i, value in enumerate(values): #where values is a list or a generator containing (or yielding) the parameters to pass to the URL
             subprocess.call(['python', 'connection.py', str(value)])
             print 'after running', i, 'processes:', print_unreachable_len()
             infile = open('sometempfile', 'rb')
             return_value_list.append(cPickle.load(infile))
             infile.close()

显然,这段代码是顺序执行的,所以你一次只能建立一个连接,这对你来说可能是个问题,也可能不是。如果是问题的话,你需要找到一种非阻塞的方式来和你启动的进程进行通信,不过这部分我就留给你自己去探索了。

编辑:在重新阅读你的问题后,似乎你并不关心服务器的响应。在这种情况下,你可以去掉所有与序列化相关的代码。而且,显然你最终的代码中也不会有print_unreachable_len()相关的部分。

0

我觉得这个链接可能就是你遇到的问题。简单来说,那个讨论里提到,Pys60在查找DNS的时候有个内存泄漏的问题。你可以通过把DNS查找放到内层循环之外来解决这个问题。

撰写回答