如何使用Python的requests库执行有时间限制的响应下载?
如何使用Python的requests库执行有时间限制的响应下载?
在使用Python下载大文件时,我希望不仅对连接过程设置时间限制,还对下载过程设置时间限制。\n我正在尝试以下Python代码:\n
import requests r = requests.get('http://ipv4.download.thinkbroadband.com/1GB.zip', timeout=0.5, prefetch=False) print(r.headers['content-length']) print(len(r.raw.read()))
\n这个代码不起作用(下载没有时间限制),正如文档中正确指出的那样:https://requests.readthedocs.org/en/latest/user/quickstart/#timeouts\n如果可能的话,这将是很好的:\n
r.raw.read(timeout=10)
\n问题是,如何对下载设置时间限制?
如何使用Python的requests库进行限时响应下载?
问题的原因:
- 使用线程运行下载操作,如果在规定时间内未完成,则可以中止。
解决方法:
import requests import threading URL = 'http://ipv4.download.thinkbroadband.com/1GB.zip' TIMEOUT = 0.5 def download(return_value): return_value.append(requests.get(URL)) return_value = [] download_thread = threading.Thread(target=download, args=(return_value,)) download_thread.start() download_thread.join(TIMEOUT) if download_thread.is_alive(): print('The download was not finished on time...') else: print(return_value[0].headers['content-length'])
然而,这种方法不是一个安全的方式。Python的线程存在问题,并且不能在超时时直接终止线程,这不是一个干净的解决方案。
如果愿意,可以将线程替换为进程。为什么不能直接终止线程?
"在Python和任何其他语言中,直接终止线程通常是一种不好的模式。" [stackoverflow.com/a/325528/389463](http://stackoverflow.com/a/325528/389463) 无法告诉线程停止。
使用进程过于复杂,需要进行进程间通信。
使用这段代码时,当超时触发时会发生什么?线程可能永远存在,没有人停止它。如果同时进行多个下载,这将导致线程数爆炸。
是的,但是你可以切换到multiprocessing模块,然后使用Process.terminate()
来终止下载进程。然而,如果有多个下载任务,使用异步方法和gevent级别上的超时可能更好。
实际上,线程无法在Python中被停止。它们可以被标记为停止状态,但实际上它们会在后台继续运行。
在使用Python的Requests库进行网络请求时,有时候需要限制下载响应的时间。下面是一个使用Requests库进行时间限制响应下载的解决方法。
当使用Requests库的prefetch=False参数时,可以一次拉取任意大小的响应数据块,而不是一次性拉取全部数据。为了实现时间限制的响应下载,我们需要告诉Requests库不要预加载整个响应,并且自己维护一个读取数据的时间计数器,同时一次读取小块的数据。可以使用r.raw.read(CHUNK_SIZE)方法来读取数据块。以下是一个示例代码:
import requests import time CHUNK_SIZE = 2**12 # Bytes TIME_EXPIRE = time.time() + 5 # Seconds r = requests.get('http://ipv4.download.thinkbroadband.com/1GB.zip', prefetch=False) data = '' buffer = r.raw.read(CHUNK_SIZE) while buffer: data += buffer buffer = r.raw.read(CHUNK_SIZE) if TIME_EXPIRE < time.time(): # 超过5秒后退出循环 data += buffer break r.raw.release_conn() print "Read %s bytes out of %s expected." % (len(data), r.headers['content-length'])
需要注意的是,由于最后的r.raw.read(...)可能需要消耗任意数量的时间,所以有时候可能会超过给定的5秒时间限制。但至少不依赖于多线程或套接字超时。
然而,这种方法并不总是有效,因为不仅最后一次的r.raw.read(...)可能需要消耗任意数量的时间,即使是每次r.raw.read(...)都可能会消耗不同的时间。这经常导致无法在指定的下载时间内完成从任意URL下载的任务。
这种情况下,套接字超时似乎是唯一可行的方法。
问题的出现原因是使用requests模块进行下载时,会阻塞程序执行,无法设置超时时间。解决方法是使用非阻塞的网络I/O,比如使用eventlet模块。下面的代码演示了如何使用eventlet模块进行时间限制的响应下载。
import eventlet from eventlet.green import urllib2 from eventlet.timeout import Timeout url5 = 'http://ipv4.download.thinkbroadband.com/5MB.zip' url10 = 'http://ipv4.download.thinkbroadband.com/10MB.zip' urls = [url5, url5, url10, url10, url10, url5, url5] def fetch(url): response = bytearray() with Timeout(60, False): response = urllib2.urlopen(url).read() return url, len(response) pool = eventlet.GreenPool() for url, length in pool.imap(fetch, urls): if (not length): print "%s: timeout!" % (url) else: print "%s: %s" % (url, length)
运行以上代码可以得到如下结果:
http://ipv4.download.thinkbroadband.com/5MB.zip: 5242880 http://ipv4.download.thinkbroadband.com/5MB.zip: 5242880 http://ipv4.download.thinkbroadband.com/10MB.zip: timeout! http://ipv4.download.thinkbroadband.com/10MB.zip: timeout! http://ipv4.download.thinkbroadband.com/10MB.zip: timeout! http://ipv4.download.thinkbroadband.com/5MB.zip: 5242880 http://ipv4.download.thinkbroadband.com/5MB.zip: 5242880
使用这段代码时,当超时触发时会发生什么?这里没有使用线程,但操作仍然是并行执行的。当超时触发时,正在进行的非阻塞操作会被取消,但不会强制终止。Socket会被关闭。
还有一个提到的库是GRequests: Asynchronous Requests,可以使用它进行异步请求。