【问题标题】:Requests with multiple connections具有多个连接的请求
【发布时间】:2012-12-08 00:47:47
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: python networking download python-requests


    【解决方案1】:

    您可以使用 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 = ''.join(parts[i] for i in sorted(parts.keys()))
    

    另请注意,并非每个服务器都支持 Range 标头(尤其是带有 php scripts responsible for data fetching 的服务器通常不实现对它的处理)。

    【讨论】:

      【解决方案2】:

      这是一个 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)
      

      【讨论】:

        【解决方案3】:

        此解决方案需要名为“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 个文件,那么只需转到 linux 上的终端(如果你使用的是 linux)并运行aria2c -x 16 -s 20 &lt;insert-URL-here&gt;。我喜欢我的解决方案,因为每当我需要下载多个大文件(甚至只有 1 个大文件)时,我只需将它们放入 MY_HTTP_LOC 目录,然后运行我的脚本。
        猜你喜欢
        • 2015-12-18
        • 1970-01-01
        • 2021-11-23
        • 1970-01-01
        • 2021-10-15
        • 2019-06-04
        • 1970-01-01
        • 2015-01-20
        • 1970-01-01
        相关资源
        最近更新 更多