Jak pobrać duży plik w Pythonie z requests.py?

Requests to naprawdę fajna biblioteka. Chciałbym go użyć do pobierania dużych plików (>1GB). Problem w tym, że nie da się utrzymać całego pliku w pamięci, muszę go odczytać w kawałkach. I jest to problem z następującym kodem

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 

Z jakiegoś powodu to nie działa w ten sposób. Nadal ładuje odpowiedź do pamięci przed zapisaniem jej do pliku.

UPDATE

Jeśli potrzebujesz małego klienta (Python 2.x / 3.x) które mogą pobierać duże pliki z FTP, można znaleźć to tutaj . Obsługuje wielowątkowość i ponowne połączenia (monitoruje połączenia), a także dostraja paramy gniazd do zadania pobierania.

Author: Roman Podlinov, 2013-05-22

4 answers

Wymyśliłem, co należy zmienić. Sztuczka polegała na ustawieniu stream = True w metodzie get().

Po tym, jak proces Pythona przestał ssać pamięć (pozostaje około 30kb niezależnie od rozmiaru pobranego pliku).

Dziękuję @danodonovan za składnię używam go tutaj:

def download_file(url):
    local_filename = url.split('/')[-1]
    # NOTE the stream=True parameter
    r = requests.get(url, stream=True)
    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() commented by recommendation from J.F.Sebastian
    return local_filename

Zobacz http://docs.python-requests.org/en/latest/user/advanced/#body-content-workflow w celach informacyjnych.

 469
Author: Roman Podlinov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-30 13:32:43

Jest znacznie łatwiejsze, jeśli używasz Response.raw oraz 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

Spowoduje to Przesyłanie pliku na dysk bez użycia nadmiernej pamięci, a kod jest prosty.

 117
Author: John Zwinck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-30 02:13:18

Twój rozmiar kawałka może być zbyt duży, próbowałeś to upuścić-może 1024 bajty na raz? (możesz też użyć with do uporządkowania składni)

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 

Nawiasem mówiąc, jak wnioskujesz, że odpowiedź została załadowana do pamięci?

Brzmi to tak, jakby python nie spłukiwał danych do pliku, z innych więc pytania można spróbować f.flush() i os.fsync() wymusić zapis pliku i wolną pamięć;

    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())
 36
Author: danodonovan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:30

Nie do końca o co prosiła OP, ale... śmiesznie łatwo to zrobić z 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)

Lub w ten sposób, jeśli chcesz zapisać go do pliku tymczasowego:

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)

Obejrzałem proces:

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

I widziałem, że plik rośnie, ale zużycie pamięci pozostało na 17 MB. Coś przeoczyłem?

 26
Author: x-yuri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-15 09:14:18