具有多个连接的请求



我使用Python Requests库下载一个大文件,例如:

r = requests.get("http://bigfile.com/bigfile.bin")
content = r.content

大文件的下载速度为每秒+- 30kb,有点慢。每个到大文件服务器的连接都是受限的,所以我想建立多个连接。

是否有办法使多个连接在同一时间下载一个文件?

您可以使用HTTP Range头来获取文件的一部分(这里已经涵盖了python)。

只要启动几个线程,每个线程获取不同的范围,你就完成了;)

def download(url,start):
    req = urllib2.Request('http://www.python.org/')
    req.headers['Range'] = 'bytes=%s-%s' % (start, start+chunk_size)
    f = urllib2.urlopen(req)
    parts[start] = f.read()
threads = []
parts = {}
# Initialize threads
for i in range(0,10):
    t = threading.Thread(target=download, i*chunk_size)
    t.start()
    threads.append(t)
# Join threads back (order doesn't matter, you just want them all)
for i in threads:
    i.join()
# Sort parts and you're done
result = ''.join(parts[i] for i in sorted(parts.keys()))

还需要注意的是,并不是每个服务器都支持Range报头(特别是php脚本负责数据获取的服务器通常不实现对它的处理)。

下面是一个Python脚本,它将给定的url保存到一个文件中,并使用多个线程来下载它:

#!/usr/bin/env python
import sys
from functools import partial
from itertools import count, izip
from multiprocessing.dummy import Pool # use threads
from urllib2 import HTTPError, Request, urlopen
def download_chunk(url, byterange):
    req = Request(url, headers=dict(Range='bytes=%d-%d' % byterange))
    try:
        return urlopen(req).read()
    except HTTPError as e:
        return b''  if e.code == 416 else None  # treat range error as EOF
    except EnvironmentError:
        return None
def main():
    url, filename = sys.argv[1:]
    pool = Pool(4) # define number of concurrent connections
    chunksize = 1 << 16
    ranges = izip(count(0, chunksize), count(chunksize - 1, chunksize))
    with open(filename, 'wb') as file:
        for s in pool.imap(partial(download_part, url), ranges):
            if not s:
                break # error or EOF
            file.write(s)
            if len(s) != chunksize:
                break  # EOF (servers with no Range support end up here)
if __name__ == "__main__":
    main()

如果服务器返回空正文,或416 http代码,或响应大小不是chunksize,则检测文件结束。

它支持不理解Range头的服务器(在这种情况下,所有内容都在单个请求中下载;为了支持大文件,将download_chunk()更改为保存到临时文件,并返回要在主线程中读取的文件名,而不是文件内容本身。

允许在单个http请求中独立更改并发连接数(池大小)和请求的字节数。

要使用多个进程而不是线程,请更改导入:

from multiprocessing.pool import Pool # use processes (other code unchanged)

此解决方案需要名为"aria2c"的linux实用程序,但它具有轻松恢复下载的优点。

它还假设您想要下载的所有文件都列在位置MY_HTTP_LOC的http目录列表中。我在lighttpd/1.4.26 http服务器实例上测试了这个脚本。但是,您可以轻松地修改此脚本,使其适用于其他设置。

#!/usr/bin/python
import os
import urllib
import re
import subprocess
MY_HTTP_LOC = "http://AAA.BBB.CCC.DDD/"
# retrieve webpage source code
f = urllib.urlopen(MY_HTTP_LOC)
page = f.read()
f.close
# extract relevant URL segments from source code
rgxp = '(<td class="n"><a href=")([0-9a-zA-Z()-_.]+)(")'
results =  re.findall(rgxp,str(page))
files = []
for match in results:
    files.append(match[1])
# download (using aria2c) files
for afile in files:
    if os.path.exists(afile) and not os.path.exists(afile+'.aria2'):
        print 'Skipping already-retrieved file: ' + afile
    else:
        print 'Downloading file: ' + afile          
        subprocess.Popen(["aria2c", "-x", "16", "-s", "20", MY_HTTP_LOC+str(afile)]).wait()

您可以使用一个名为pypdl或pySmartDL的模块,它们提供了使用多个线程的选项,并且可以做更多的事情。此外,这些模块默认提供下载条。更多信息请查看

相关内容

  • 没有找到相关文章

最新更新