リクエストを使用してPythonで大きなファイルをダウンロードする


401

リクエストは本当に素晴らしいライブラリです。大きなファイル(> 1GB)のダウンロードに使用したい。問題は、ファイル全体をメモリに保持できないため、チャンクで読み取る必要があることです。そして、これは次のコードの問題です

import requests

def DownloadFile(url)
    local_filename = url.split('/')[-1]
    r = requests.get(url)
    f = open(local_filename, 'wb')
    for chunk in r.iter_content(chunk_size=512 * 1024): 
        if chunk: # filter out keep-alive new chunks
            f.write(chunk)
    f.close()
    return 

何らかの理由で、この方法では機能しません。それでもファイルに保存する前に応答をメモリにロードします。

更新

FTPから大きなファイルをダウンロードできる小さなクライアント(Python 2.x /3.x)が必要な場合は、こちらで見つけることができます。マルチスレッドと再接続をサポートし(接続を監視します)、ダウンロードタスクのソケットパラメータも調整します。

回答:


653

次のストリーミングコードでは、ダウンロードされたファイルのサイズに関係なく、Pythonのメモリ使用量が制限されています。

def download_file(url):
    local_filename = url.split('/')[-1]
    # NOTE the stream=True parameter below
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(local_filename, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192): 
                # If you have chunk encoded response uncomment if
                # and set chunk_size parameter to None.
                #if chunk: 
                f.write(chunk)
    return local_filename

を使用して返されるバイト数はiter_content正確にはchunk_size;でないことに注意してください。乱数であることが多く、はるかに大きく、反復ごとに異なることが予想されます。

詳細については、https://requests.readthedocs.io/en/latest/user/advanced/#body-content-workflowおよびhttps://requests.readthedocs.io/en/latest/api/#requests.Response.iter_contentを参照してください参照。


9
@Shumanご覧のとおり、http://からhttps://に切り替えたときに問題が解決しました(github.com/kennethreitz/requests/issues/2043)。1024Mbを超えるファイルのコードに問題があると人々が思う可能性があるため、コメントを更新または削除していただけますか
Roman Podlinov

8
これchunk_sizeは重要です。デフォルトでは1(1バイト)です。つまり、1MBの場合、100万回の繰り返しになります。docs.python-requests.org/en/latest/api/...
エドゥアルドGamonal

4
f.flush()不要のようです。それを使用して何を達成しようとしていますか?(あなたがそれを落とした場合、あなたのメモリ使用量は1.5GBではありません)。f.write(b'')iter_content()空の文字列を返す可能性がある場合)は無害である必要if chunkがあるため、削除することもできます。
jfs 2015

11
@RomanPodlinov:f.flush()データを物理ディスクにフラッシュしません。OSにデータを転送します。通常、停電がなければ十分です。f.flush()ここでは理由もなくコードが遅くなります。フラッシュは、対応するファイルバッファー(アプリ内)がいっぱいになると発生します。より頻繁な書き込みが必要な場合。buf.sizeパラメータをに渡しますopen()
jfs 2015

9
r.close()
0xcaff

273

とを使用するResponse.rawと、はるかに簡単になりますshutil.copyfileobj()

import requests
import shutil

def download_file(url):
    local_filename = url.split('/')[-1]
    with requests.get(url, stream=True) as r:
        with open(local_filename, 'wb') as f:
            shutil.copyfileobj(r.raw, f)

    return local_filename

これにより、過度のメモリを使用せずにファイルがディスクにストリーミングされ、コードは簡単です。


10
あなたは時に調整する必要があるかもしれないことに注意gzip圧縮された応答のストリーミング問題2155あたりを
ChrisP

32
これが正解です!受け入れられた答えは2〜3メガバイト/秒にあなたを取得します。copyfileobjを使用すると、最大40MB / sになります。〜50-55 MB /秒のCurlダウンロード(同じマシン、同じURLなど)。
visoft 2017

24
確認要求の接続がリリースされるようにするには、第二(ネストされた)を使用することができますwith:要求を行うためにブロックwith requests.get(url, stream=True) as r:
クリスチャンロング

7
@ChristianLong:それは本当ですが、ごく最近です。サポートする機能がwith requests.get()2017-06-07にマージされただけです!あなたの提案は、リクエスト2.18.0以降を持っている人にとっては合理的です。参照:github.com/requests/requests/issues/4136
John Zwinck 2017年

4
あなたはこの動作をアップパッチを適用することができます@EricCousineau 交換するread方法を:response.raw.read = functools.partial(response.raw.read, decode_content=True)
ヌーノ・アンドレ

54

OPが求めていたものとは正確には異なりますが、urllib次のようにしてそれを実行するのは途方もなく簡単です。

from urllib.request import urlretrieve
url = 'http://mirror.pnl.gov/releases/16.04.2/ubuntu-16.04.2-desktop-amd64.iso'
dst = 'ubuntu-16.04.2-desktop-amd64.iso'
urlretrieve(url, dst)

または、この方法で一時ファイルに保存したい場合:

from urllib.request import urlopen
from shutil import copyfileobj
from tempfile import NamedTemporaryFile
url = 'http://mirror.pnl.gov/releases/16.04.2/ubuntu-16.04.2-desktop-amd64.iso'
with urlopen(url) as fsrc, NamedTemporaryFile(delete=False) as fdst:
    copyfileobj(fsrc, fdst)

私はプロセスを見ました:

watch 'ps -p 18647 -o pid,ppid,pmem,rsz,vsz,comm,args; ls -al *.iso'

ファイルが大きくなるのを見ましたが、メモリ使用量は17 MBのままでした。何か不足していますか?


2
Python 2.xの場合、次を使用しますfrom urllib import urlretrieve
Vadim Kotov 2018

これにより、ダウンロード速度が遅くなります...
citynorman

@citynorman詳しく説明してもらえますか?どのソリューションと比較して?どうして?
x-yuri

@ x-yuri shutil.copyfileobjと最も投票数の多いソリューションの
比較

42

チャンクサイズが大きすぎる可能性があります。一度に1024バイトを落としてみましたか?(また、with構文を整理するために使用できます)

def DownloadFile(url):
    local_filename = url.split('/')[-1]
    r = requests.get(url)
    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
    return 

ちなみに、レスポンスがメモリにロードされたとどうやって推測しますか?

Pythonは他から、ファイルにデータをフラッシュしていないかのように聞こえるSOの質問あなたは試みることができるf.flush()os.fsync()、ファイルの書き込みや空きメモリを強制的に。

    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024): 
            if chunk: # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
                os.fsync(f.fileno())

1
Kubuntuでシステムモニターを使用しています。Pythonプロセスメモリが増加することを示しています(25kbから最大1.5GB)。
ローマポドリノフ2013年

そのメモリの肥大化はひどい、おそらくf.flush(); os.fsync()書き込みを強制的に解放するかもしれない
ダノドノバン2013年

2
それはos.fsync(f.fileno())
sebdelsol 2014年

29
requests.get()呼び出しでstream = Trueを使用する必要があります。それがメモリの肥大化の原因です。
Hut8

1
マイナーなタイプミス:コロン( ':')を逃すdef DownloadFile(url)
オーブリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.