如何使用 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 请求执行限时响应下载?的主要内容,如果未能解决你的问题,请参考以下文章

如何验证来自 json 响应(Python)的数据?

如何使用 python 请求模块创建自定义标头

如何使用请求 Python 从站点获得授权

如何使用 Alamofire 为每个请求添加 auth 标头,如果响应为 401 则执行某些操作?

如何从 Python 请求调用中提取 HTTP 响应正文?

如何在python中为GET请求添加轮询间隔