14

Python Requests ライブラリを使用して大きなファイルをダウンロードします。

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

大きなファイルは 1 秒あたり +-30 Kb でダウンロードされますが、これは少し遅いです。bigfile サーバーへのすべての接続が調整されているため、複数の接続を作成したいと考えています。

1 つのファイルをダウンロードするために同時に複数の接続を確立する方法はありますか?

4

3 に答える 3

24

HTTPRangeヘッダーを使用して、ファイルの一部だけを取得できます ( 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 スクリプトを使用するサーバーは、多くの場合、ヘッダーの処理を実装していません)。

于 2012-12-20T13:44:54.690 に答える
7

指定された URL をファイルに保存し、複数のスレッドを使用してダウンロードする 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)
于 2012-12-21T12:14:12.403 に答える
1

このソリューションには、「aria2c」という名前の Linux ユーティリティが必要ですが、ダウンロードを簡単に再開できるという利点があります。

また、ダウンロードするすべてのファイルが http ディレクトリ リストの location にリストされていることも前提としていますMY_HTTP_LOC。このスクリプトを 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()
于 2013-04-28T05:33:30.890 に答える