如何使用 python 请求执行限时响应下载?
Posted
技术标签:
【中文标题】如何使用 python 请求执行限时响应下载?【英文标题】:How to perform time limited response download with python requests? 【发布时间】:2012-11-14 10:04:12 【问题描述】:使用python下载大文件时,我想设置一个时间限制,不仅是连接过程,还有下载。
我正在尝试使用以下 python 代码:
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())
这不起作用(下载没有时间限制),正如文档中正确指出的那样:https://requests.readthedocs.org/en/latest/user/quickstart/#timeouts
如果可能的话,那就太好了:
r.raw.read(timeout = 10)
问题是,如何给下载设置时间限制?
【问题讨论】:
我并不提倡这是最好的解决方案,但这里有一个使用信号对函数调用设置时间限制的通用解决方案:***.com/a/601168/471671。这是一个杂项,除非没有更优雅的解决方案,否则我不建议使用它。 是的,信号不是一个选项,因为***.com/a/1114567/389463 【参考方案1】:答案是:不要使用请求,因为它是阻塞的。使用非阻塞网络 I/O,例如 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
【讨论】:
你见过GRequests: Asynchronous Requests吗? 有了这段代码,当超时触发时会发生什么? :) 你对套接字的状态有什么保证? AFAIK,这里没有线程,仍然并行运行。当超时触发时,正在进行的非阻塞操作被取消。没有杀戮。插座已关闭。我希望 ;)【参考方案2】:使用请求的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():
# Quit after 5 seconds.
data += buffer
break
r.raw.release_conn()
print "Read %s bytes out of %s expected." % (len(data), r.headers['content-length'])
请注意,这有时可能会比分配的 5 秒多一点,因为最终的 r.raw.read(...)
可能会滞后任意时间。但至少它不依赖于多线程或套接字超时。
【讨论】:
不幸的是,这不起作用,因为不仅最后一个,甚至每个 r.raw.read(...) 都可能滞后任意时间。这通常会导致从任意 url 下载时错过超时。 那么听起来套接字超时是唯一的方法。【参考方案3】:在一个线程中运行下载,如果没有按时完成,您可以中止。
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 和任何语言中,突然终止线程通常是一种糟糕的模式。" ***.com/a/325528/389463没有办法告诉线程停止。 使用进程太复杂,需要进程间通信。 线程实际上无法在 Python 中停止。可以使用stop
方法将它们标记为已停止,但它们实际上会继续在后台运行。以上是关于如何使用 python 请求执行限时响应下载?的主要内容,如果未能解决你的问题,请参考以下文章