Pobierz duży plik w Pythonie z żądaniami
Requests to naprawdę fajna biblioteka. Chciałbym go użyć do pobierania dużych plików (>1GB). Problem w tym, że nie jest możliwe utrzymanie 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 nie działa to w ten sposób: nadal ładuje odpowiedź do pamięci, zanim zostanie zapisana do pliku.
UPDATE
Jeśli potrzebujesz małego klienta (Python 2.x / 3.x) które można pobrać Duże pliki z FTP, znajdziesz je tutaj . Obsługuje wielowątkowość i ponowne połączenia (monitoruje połączenia), a także dostraja paramy gniazd do zadania pobierania.
6 answers
W poniższym kodzie strumieniowym użycie pamięci Pythona jest ograniczone niezależnie od rozmiaru pobranego pliku:
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
Należy zauważyć, że liczba bajtów zwróconych za pomocą iter_content
nie jest dokładnie chunk_size
; oczekuje się, że będzie to liczba losowa, która jest często znacznie większa i oczekuje się, że będzie inna w każdej iteracji.
Zobacz body-content-workflow i odpowiedź.iter_content dla dalszych informacji.
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
2021-01-14 19:02:52
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]
with requests.get(url, stream=True) as r:
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.
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
2020-03-24 12:31:43
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?
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
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())
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
W oparciu o najbardziej upvoted komentarz Romana powyżej, oto moja realizacja, W tym mechanizm" download as "I" retries":
def download(url: str, file_path='', attempts=2):
"""Downloads a URL content into a file (with large file support by streaming)
:param url: URL to download
:param file_path: Local file name to contain the data downloaded
:param attempts: Number of attempts
:return: New file path. Empty string if the download failed
"""
if not file_path:
file_path = os.path.realpath(os.path.basename(url))
logger.info(f'Downloading {url} content to {file_path}')
url_sections = urlparse(url)
if not url_sections.scheme:
logger.debug('The given url is missing a scheme. Adding http scheme')
url = f'http://{url}'
logger.debug(f'New url: {url}')
for attempt in range(1, attempts+1):
try:
if attempt > 1:
time.sleep(10) # 10 seconds wait time between downloads
with requests.get(url, stream=True) as response:
response.raise_for_status()
with open(file_path, 'wb') as out_file:
for chunk in response.iter_content(chunk_size=1024*1024): # 1MB chunks
out_file.write(chunk)
logger.info('Download finished successfully')
return file_path
except Exception as ex:
logger.error(f'Attempt #{attempt} failed with error: {ex}')
return ''
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
2020-07-05 17:15:42
Zamiast tego użyj wget
modułu Pythona. Oto fragment
import wget
wget.download(url)