Скачать большой файл в Python с запросами

Requests - это действительно хорошая библиотека. Я хотел бы использовать его для загрузки больших файлов (> 1 ГБ). Проблема в том, что невозможно сохранить весь файл в памяти, мне нужно прочитать его порциями. И это проблема со следующим кодом

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 

По какой-то причине это не работает таким образом. Он по-прежнему загружает ответ в память, прежде чем сохранить его в файл.

ОБНОВИТЬ

Если вам нужен маленький клиент (Python 2.x/3.x), который может загружать большие файлы с FTP, вы можете найти его здесь. Он поддерживает многопоточность и повторное соединение (он контролирует соединения), а также настраивает параметры сокета для задачи загрузки.

Ответ 1

При использовании следующего потокового кода использование памяти 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 для дальнейшего ознакомления.

Ответ 2

Это намного проще, если вы используете 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

Это потоковое файл на диск без использования чрезмерной памяти, и код прост.

Ответ 3

Размер вашего куска может быть слишком большим, вы пытались удалить его - может быть, 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())

Ответ 4

Не совсем то, о чем спрашивал 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 МБ. Я что-то пропустил?