web-dev-qa-db-ja.com

リクエストでpythonで大きなファイルをダウンロードする

リクエスト は本当にいいライブラリです。大きなファイル(> 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 

どういうわけかそれはこのようには動作しません。それはまだそれをファイルに保存する前にメモリに応答をロードします。

_ update _

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

299
Roman Podlinov

次のストリーミングコードでは、ダウンロードしたファイルのサイズに関係なく、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 chunk: # filter out keep-alive new chunks
                    f.write(chunk)
                    # f.flush()
    return local_filename

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

詳しくは http://docs.python-requests.org/en/latest/user/advanced/#body-content-workflow を参照してください。

530
Roman Podlinov

Response.raw および shutil.copyfileobj() を使用すれば、はるかに簡単になります。

import requests
import shutil

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

    return local_filename

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

163
John Zwinck

あなたのチャンクサイズは大きすぎるかもしれません、あなたはそれを落としてみました - 多分一度に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 

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

他の SO questions からpythonがデータをファイルにフラッシュしていないかのように聞こえますが、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())
40
danodonovan

厳密には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のままでした。私は何かが足りないのですか?

37
x-yuri