2012-12-20 129 views
12

我使用Python请求库下载一个大文件,例如:请求与多个连接

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

大文件下载在+ - 每秒30 KB,这是一个有点慢。与bigfile服务器的每个连接都受到限制,因此我想进行多个连接。

有没有办法让多个连接同时下载一个文件?

回答

17

您可以使用HTTP Range标题获取文件的一部分(already covered for python here)。

刚开始几个线程,并获取不同范围与每大功告成;)

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 = '' 
for i in range(0,10): 
    result += parts[i*chunk_size] 

另外请注意,不是每个服务器支持Range头(特别是与php scripts responsible for data fetching服务器通常不执行的处理它)。

6

下面是一个Python脚本,节省了指定网址的文件,并使用多线程下载:

#!/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) 
1

这种解决方案需要一个名为“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() 
+1

如果你真的只需要下载1个文件,那么只要到Linux上的终端(如果你使用的是Linux),并运行'aria2c -x 16 -s 20 <嵌入URL的位置>'。我喜欢我的解决方案,因为无论何时我需要下载多个大文件(甚至只有一个大文件),我都会将它们放入“MY_HTTP_LOC”目录中,然后运行我的脚本。 – synaptik