Büyük dosyayı isteklerle python olarak indir


399

İstekler gerçekten güzel bir kütüphane. Büyük dosyaları indirmek için kullanmak istiyorum (> 1GB). Sorun şu ki, tüm dosyayı bellekte saklamak mümkün değil. Ve bu aşağıdaki kodla ilgili bir sorundur

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 

Bazı nedenlerden dolayı bu şekilde çalışmaz. Hala bir dosyaya kaydetmeden önce belleğe yanıt yükler.

GÜNCELLEME

FTP'den büyük dosyaları indirebilen küçük bir istemciye (Python 2.x /3.x) ihtiyacınız varsa, onu burada bulabilirsiniz . Çoklu iş parçacığı ve yeniden bağlamaları destekler (bağlantıları izler) ayrıca indirme görevi için soket parametrelerini ayarlar.

Yanıtlar:


651

Aşağıdaki akış koduyla, indirilen dosyanın boyutuna bakılmaksızın Python bellek kullanımı kısıtlanır:

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

Kullanarak döndürülen bayt sayısının iter_contenttam olarak chunk_size; genellikle çok daha büyük olan ve her yinelemede farklı olması beklenen rastgele bir sayı olması beklenir.

Https://requests.readthedocs.io/en/latest/user/advanced/#body-content-workflow ve daha fazla bilgi için https://requests.readthedocs.io/en/latest/api/#requests.Response.iter_content adresine bakın. referans.


9
@ İnsan Gördüğüm gibi http: // 'den https: //' ye ( github.com/kennethreitz/requests/issues/2043 ) geçtiğinizde sorunu çözdünüz . Lütfen yorumlarınızı güncelleyebilir veya silebilir misiniz, çünkü insanlar daha büyük 1024Mb dosya koduyla ilgili sorunlar olduğunu düşünebilirler
Roman Podlinov

8
bu chunk_sizeçok önemlidir. varsayılan olarak 1'dir (1 bayt). yani 1MB için 1 milyon yineleme yapacak. docs.python-requests.org/tr/latest/api/…
Eduard

4
f.flush()gereksiz görünüyor. Bunu kullanarak neyi başarmaya çalışıyorsunuz? (düşürürseniz bellek kullanımınız 1,5 gb olmayacaktır). f.write(b'')( iter_content()boş bir dize döndürürse) zararsız olmalı ve bu nedenle if chunkde bırakılabilir.
jfs

11
@RomanPodlinov: f.flush()verileri fiziksel diske temizlemez. Verileri işletim sistemine aktarır. Genellikle, bir elektrik kesintisi olmadığı sürece yeterlidir. f.flush()kodu burada sebepsiz yere yavaşlatır. Temizleme, karşılık gelen dosya arabelleği (uygulamanın içinde) dolduğunda gerçekleşir. Daha sık yazılmaya ihtiyacınız varsa; buf.size parametresini open().
jfs

9
r.close()
İle

272

Kullanırsanız çok daha kolay Response.rawve 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

Bu, dosyayı aşırı bellek kullanmadan diske aktarır ve kod basittir.


10
Her sorun 2155 için gzip edilmiş yanıtlar yayınlarken ayarlamanız gerekebileceğini unutmayın .
ChrisP

32
BU doğru cevap olmalı! Kabul edilen cevap sizi 2-3MB / s'ye kadar çıkarır. Copyfileobj kullanmak ~ 40MB / s'ye ulaşmanızı sağlar. İndirmeleri ~ 50-55 MB / sn ile kıvırın (aynı makineler, aynı url, vb.).
visoft

24
İstekler bağlantısının serbest bırakıldığından emin olmak withiçin, istekte bulunmak üzere ikinci (iç içe) bir blok kullanabilirsiniz:with requests.get(url, stream=True) as r:
Christian Long

7
@ChristianLong: Bu doğru, ancak çok yakın zamanda, destek özelliği with requests.get()sadece 2017-06-07'de birleştirildiğinden! Öneriniz, 2.18.0 veya daha sonraki İstekleri olan kişiler için makul. Referans: github.com/requests/requests/issues/4136
John Zwinck


54

OP'nin tam olarak sorduğu şey değil, ama ... bunu yapmak gülünç derecede kolay 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)

Veya bu şekilde, dosyayı geçici bir dosyaya kaydetmek istiyorsanız:

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)

Süreci izledim:

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

Dosyanın büyüdüğünü gördüm, ancak bellek kullanımı 17 MB'da kaldı. Bir şey mi kaçırıyorum?


2
Python 2.x için, kullanınfrom urllib import urlretrieve
Vadim Kotov

Bu yavaş bir indirme hızı ile sonuçlanır ...
citynorman

@citynorman Ayrıntılı olabilir misiniz? Hangi çözümle karşılaştırıldığında? Neden?
x-yuri

@ x-yuri vs en shutil.copyfileobjçok oy ile çözüm , benim ve diğerleri orada yorum
citynorman

42

Yığın boyutunuz çok büyük olabilir, bunu düşürmeyi denediniz mi - belki bir seferde 1024 bayt? (ayrıca withsözdizimini düzenlemek için de kullanabilirsiniz )

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 

Bu arada, yanıtın belleğe yüklendiğinden nasıl çıkarıyorsunuz?

Python, verileri dosyaya akıtmıyormuş gibi geliyor, diğer SO sorularından deneyebilir f.flush()ve os.fsync()dosyayı yazmaya ve serbest belleği zorlamaya çalışabilirsiniz ;

    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())

1
Kubuntu'da Sistem Monitörü kullanıyorum. Bana python işlem belleğinin arttığını gösteriyor (25kb'den 1.5gb'ye kadar).
Roman Podlinov

Bu bellek şişmesi berbat, belki f.flush(); os.fsync()de bir hafızayı serbest bırakmaya zorlayabilir.
danodonovan

2
buos.fsync(f.fileno())
sebdelsol

29
Requests.get () çağrısında stream = True kullanmanız gerekir. Hafıza şişmesine neden olan şey budur.
Hut8

1
minör yazım hatası: Sonra iki nokta üst üste (':') özledimdef DownloadFile(url)
Aubrey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.