【python】【Memory leak】urllib2, request内存泄露问题解决方案

本文探讨了使用Python 2.7的urllib2和requests库进行大量网络请求时遇到的内存泄漏问题,并提供了一个使用urllib结合contextlib.closing的解决方案。

提到python,很多人的第一直觉大概就是爬虫和网络相关。然而最近使用python2.7 urllib2和request的时候却无意中发现可能存在严重的内存泄漏问题,或者说垃圾回收有问题。stackoverflow了一下,确实有很多人反应了相关的问题,至今还没解决。综合了各种解决方案,最终确定了一种临时的替代方案,在此记录和分享。

问题介绍 & 重现

用过python进行大量网络请求的童鞋,可能都会发现,当请求达到一定量之后会出现内存问题,无法再进行请求。下面,我们用利用python内置的gc库看一下urllib2和request请求过后的内存回收情况。

import gc, urllib, urllib2, requests
def get_unreachable_memory_len():
    # check memory on memory leaks
    gc.set_debug(gc.DEBUG_SAVEALL)
    gc.collect()
    unreachableL = []
    for it in gc.garbage:
        unreachableL.append(it)
    print str(unreachableL)
    return len(str(unreachableL))

def request_with_urllib2(url):
    resp = urllib2.urlopen(url)
    return resp.read()

def request_with_requests(url):
    resp = requests.get(url)
    return resp.content

启动python,先调用一下get_unreachable_memory_len()看一下当前有没有内存泄露:
这里写图片描述
返回是为空,证明没有memory leak.

运行request_with_urllib2('http://www.ah.xinhuanet.com/titlepic/1117418255_1449717108394_title1n.jpg')之后我们拿到了长度为41536的胡歌歌的照片:
这里写图片描述 但不幸的是,我们看内存:
这里写图片描述
由于网络请求没有被正确回收,导致内存出现了一定的泄露,这部分泄露是response中没有被处理的部分。

同理我们再试一下request_with_requests('http://www.ah.xinhuanet.com/titlepic/1117418255_1449717108394_title1n.jpg')
这里写图片描述
这里写图片描述
同样,在进行requests.get后,内存回收也出现了问题。

解决方案

在若干次实验后,终于找到了较为理想的解决方案,也就是暂时用urllib+contextlib.closing的组合进行临时替代。这样可以在不换掉python2.7的条件下,实现不伤害内存的网络请求。

from contextlib import closing
def url_request(url): # request without memory leak
    res = None
    with closing(urllib.urlopen(url)) as resp:
        res = resp.read()
    return res

同样的方法,我们来试一下这种方案。(注意此处要重启一个新的python shell测试哦~)

get_unreachable_memory_len()
url_request('http://www.ah.xinhuanet.com/titlepic/1117418255_1449717108394_title1n.jpg')
get_unreachable_memory_len()

这里写图片描述
从图片中我们可以清楚的看到,通过url_request我们不仅获取了胡歌歌的大图,还完成了完整的response回收,通过gc检查内存也没有任何问题。妥妥的解决~

KeyboardInterrupt Traceback (most recent call last) <ipython-input-2-a49b7ed76b34> in <module>() 9 for start in range(0, 250, 25): 10 url = f'https://movie.douban.com/top250?start={start}' ---> 11 response = requests.get(url, headers=headers) 12 html = etree.HTML(response.text) 13 items = html.xpath('//div[@class="item"]') ~\Anaconda3\lib\site-packages\requests\api.py in get(url, params, **kwargs) 70 71 kwargs.setdefault('allow_redirects', True) ---> 72 return request('get', url, params=params, **kwargs) 73 74 ~\Anaconda3\lib\site-packages\requests\api.py in request(method, url, **kwargs) 56 # cases, and look like a memory leak in others. 57 with sessions.Session() as session: ---> 58 return session.request(method=method, url=url, **kwargs) 59 60 ~\Anaconda3\lib\site-packages\requests\sessions.py in request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json) 506 } 507 send_kwargs.update(settings) --> 508 resp = self.send(prep, **send_kwargs) 509 510 return resp ~\Anaconda3\lib\site-packages\requests\sessions.py in send(self, request, **kwargs) 616 617 # Send the request --> 618 r = adapter.send(request, **kwargs) 619 620 # Total elapsed time of the request (approximately) ~\Anaconda3\lib\site-packages\requests\adapters.py in send(self, request, stream, timeout, verify, cert, proxies) 438 decode_content=False, 439 retries=self.max_retries, --> 440 timeout=timeout 441 ) 442 ~\Anaconda3\lib\site-packages\urllib3\connectionpool.py in urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw) 599 timeout=timeout_obj, 600 body=body, headers=headers, --> 601 chunked=chunked) 602 603 # If we're going to release the connection in ``finally:``, then ~\Anaconda3\lib\site-packages\urllib3\connectionpool.py in _make_request(self, conn, method, url, timeout, chunked, **httplib_request_kw) 344 # Trigger any extra validation we need to do. 345 try: --> 346 self._validate_conn(conn) 347 except (SocketTimeout, BaseSSLError) as e: 348 # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. ~\Anaconda3\lib\site-packages\urllib3\connectionpool.py in _validate_conn(self, conn) 848 # Force connect early to allow us to validate the connection. 849 if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` --> 850 conn.connect() 851 852 if not conn.is_verified: ~\Anaconda3\lib\site-packages\urllib3\connection.py in connect(self) 282 def connect(self): 283 # Add certificate verification --> 284 conn = self._new_conn() 285 286 hostname = self.host ~\Anaconda3\lib\site-packages\urllib3\connection.py in _new_conn(self) 139 try: 140 conn = connection.create_connection( --> 141 (self.host, self.port), self.timeout, **extra_kw) 142 143 except SocketTimeout as e: ~\Anaconda3\lib\site-packages\urllib3\util\connection.py in create_connection(address, timeout, source_address, socket_options) 71 if source_address: 72 sock.bind(source_address) ---> 73 sock.connect(sa) 74 return sock 75 KeyboardInterrupt: ​
最新发布
11-02
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值