Вопрос-Ответ

Download large file in python with requests

Загрузить большой файл на python с запросами

Запросы - действительно хорошая библиотека. Я бы хотел использовать ее для загрузки больших файлов (> 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

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

Переведено автоматически
Ответ 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 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; ожидается, что это будет случайное число, которое часто намного больше и, как ожидается, будет отличаться на каждой итерации.

Смотрите body-content-workflow и Response.iter_content для дополнительной справки.

Ответ 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

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

Примечание: Согласно документации, Response.raw будет не декодироватьgzip, а deflate передавать-encodings, поэтому вам нужно будет сделать это вручную.

Ответ 3

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

Ответ 4

Размер вашего блока может быть слишком большим, вы пробовали удалять его - возможно, 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())
2023-08-18 07:33 python python-requests