अनुरोध के साथ अजगर में बड़ी फ़ाइल डाउनलोड करें


398

अनुरोध एक बहुत अच्छा पुस्तकालय है। मैं इसे बड़ी फ़ाइलों को डाउनलोड करने के लिए उपयोग करना चाहता हूं (> 1GB)। समस्या यह है कि पूरी फाइल को याद में रखना संभव नहीं है, मुझे इसे चंक्स में पढ़ने की आवश्यकता है। और यह निम्नलिखित कोड के साथ एक समस्या है

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 से बड़ी फ़ाइलों को डाउनलोड कर सकता है, तो आप इसे यहाँ पा सकते हैं । यह मल्टीथ्रेडिंग और रीकनेक्ट्स का समर्थन करता है (यह कनेक्शनों की निगरानी करता है) यह भी डाउनलोड कार्य के लिए सॉकेट परमर्स को ट्यून करता है।

जवाबों:


650

निम्नलिखित स्ट्रीमिंग कोड के साथ, पायथन मेमोरी का उपयोग डाउनलोड की गई फ़ाइल के आकार की परवाह किए बिना प्रतिबंधित है:

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; यह एक यादृच्छिक संख्या होने की उम्मीद है जो अक्सर बहुत बड़ी होती है, और हर पुनरावृत्ति में भिन्न होने की उम्मीद की जाती है।

आगे के लिए https://requests.readthedocs.io/en/latest/user/advanced/#body-content-workflow और https://requests.readthedocs.io/en/latest/api/#requests.Response.iter_content देखें संदर्भ।


9
@ शूमन जैसा कि मैंने देखा कि जब आपने http: // से https: // ( github.com/kennethreitz/requests/issues/2043 ) पर स्विच किया तो समस्या का समाधान हो गया । क्या आप कृपया अपनी टिप्पणियों को अपडेट या हटा सकते हैं क्योंकि लोग सोच सकते हैं कि बड़ी फ़ाइलों के लिए कोड हैं
1024Mb

8
chunk_sizeमहत्वपूर्ण है। डिफ़ॉल्ट रूप से यह 1 (1 बाइट) है। इसका मतलब है कि 1MB के लिए यह 1 सैन्य पुनरावृत्तियों को बनाएगा। docs.python-requests.org/en/latest/api/…
एडुआर्ड

4
f.flush()अनावश्यक लगता है। आप इसका उपयोग करके क्या हासिल करने की कोशिश कर रहे हैं? (यदि आप इसे छोड़ते हैं तो आपकी मेमोरी का उपयोग 1.5gb नहीं होगा)। f.write(b'')(यदि iter_content()एक खाली स्ट्रिंग लौट सकता है) हानिरहित होना चाहिए और इसलिए if chunkइसे भी गिराया जा सकता है।
1:28 बजे

11
@ रोमेनप्लोडिनोव: f.flush()डेटा को फिजिकल डिस्क पर नहीं भेजता है। यह डेटा को OS में ट्रांसफर करता है। आमतौर पर, यह पर्याप्त है जब तक कि बिजली की विफलता न हो। f.flush()बिना किसी कारण के यहां कोड धीमा कर देता है। फ्लश तब होता है जब कोरपिंग फ़ाइल बफर (ऐप के अंदर) भरा होता है। यदि आपको अधिक लगातार लिखने की आवश्यकता है; करने के लिए buf.size पैरामीटर पास करें open()
jfs

9
कनेक्शन को बंद करना न भूलेंr.close()
0xcaff

271

यदि आप उपयोग करते हैं तो यह बहुत आसान है 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

यह अत्यधिक मेमोरी का उपयोग किए बिना फ़ाइल को डिस्क पर स्ट्रीम करता है, और कोड सरल है।


10
ध्यान दें कि आपको 2155 प्रति मुद्दे पर gzipped प्रतिक्रियाओं को स्ट्रीमिंग करते समय समायोजित करने की आवश्यकता हो सकती है।
क्रिस सिप

32
यह सही उत्तर होना चाहिए! स्वीकार किए जाते हैं जवाब देने के लिए 2-3MB / s आप ऊपर हो जाता है। Copyfileobj का उपयोग करना आपको ~ 40MB / s तक ले जाता है। कर्ल डाउनलोड (एक ही मशीन, एक ही यूआरएल, आदि) ~ 50-55 एमबी / एस के साथ।
17:17 पर विस्फ़ोट

24
अनुरोध कनेक्शन जारी होने के बारे में सुनिश्चित करने के लिए, आप withअनुरोध करने के लिए दूसरे (नेस्टेड) ब्लॉक का उपयोग कर सकते हैं :with requests.get(url, stream=True) as r:
क्रिश्चियन लॉन्ग

7
@ChristianLong: यह सच है, लेकिन अभी हाल ही में, समर्थन करने की सुविधा with requests.get()केवल 2017-06-07 में विलय कर दी गई थी! आपका सुझाव उन लोगों के लिए उचित है जिनके पास अनुरोध 2.18.0 या बाद का है। Ref: github.com/requests/requests/issues/4136
जॉन Zwinck


54

नहीं बिल्कुल ओपी पूछ रहा था, लेकिन ... यह हास्यास्पद है कि यह करने के लिए आसान है 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 एमबी पर रहा। क्या मैं कुछ भूल रहा हूँ?


2
पायथन 2.x के लिए, उपयोगfrom urllib import urlretrieve
वादिम कोटोव

यह धीमी गति से डाउनलोड होने का परिणाम है ...
citynorman

@citynorman क्या आप विस्तृत कर सकते हैं? किस उपाय की तुलना में? क्यों?
एक्स-यूरी

@ एक्स-यूरी बनाम shutil.copyfileobjसबसे अधिक वोटों के साथ समाधान , मेरी और अन्य टिप्पणियों को देखें
सिटीऑर्मन

41

आपका हिस्सा आकार बहुत बड़ा हो सकता है, क्या आपने उसे छोड़ने की कोशिश की है - शायद एक बार में 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 

संयोग से, आप कैसे समर्पण कर रहे हैं कि प्रतिक्रिया को स्मृति में लोड किया गया है?

ऐसा लगता है जैसे अजगर डेटा को फाइल करने के लिए फ्लश नहीं कर रहा है, अन्य एसओ सवालों से आप कोशिश कर सकते हैं 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())

1
मैं कुबंटू में सिस्टम मॉनिटर का उपयोग करता हूं। यह मुझे दिखाता है कि अजगर प्रक्रिया मेमोरी बढ़ जाती है (25kb से 1.5gb तक)।
रोमन पोडलिनोव

वह स्मृति ब्लोट बेकार है, शायद f.flush(); os.fsync()एक स्मृति मुक्त लिखने के लिए मजबूर कर सकता है।
दानोडोनोवैन

2
यहos.fsync(f.fileno())
sebdelsol

29
आपको request.get () कॉल में स्ट्रीम = ट्रू का उपयोग करने की आवश्यकता है। यही कारण है कि मेमोरी ब्लोट है।
हुत 8

1
नाबालिग टाइपो: आप एक बृहदान्त्र (':') को याद करते हैंdef DownloadFile(url)
और्री
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.