요청으로 파이썬에서 큰 파일 다운로드


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 

어떤 이유로 든이 방법으로 작동하지 않습니다. 파일에 저장하기 전에 여전히 응답을 메모리에로드합니다.

최신 정보

FTP에서 큰 파일을 다운로드 할 수있는 작은 클라이언트 (Python 2.x /3.x)가 필요한 경우 여기에서 찾을 수 있습니다 . 멀티 스레딩 및 재 연결 (연결 모니터링)도 지원하며 다운로드 작업에 대한 소켓 매개 변수를 조정합니다.

답변:


650

다음 스트리밍 코드를 사용하면 다운로드 한 파일의 크기에 관계없이 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; 종종 훨씬 더 큰 난수 일 것으로 예상되며 매 반복마다 다를 것으로 예상됩니다.

자세한 내용은 https://requests.readthedocs.io/en/latest/user/advanced/#body-content-workflowhttps://requests.readthedocs.io/en/latest/api/#requests.Response.iter_content 를 참조 하십시오. 참고.


9
@Shuman http : //에서 https : // ( github.com/kennethreitz/requests/issues/2043 ) 로 전환하면 문제가 해결되었습니다 . 사람들이 더 큰 1024Mb 파일의 코드에 문제가 있다고 생각할 수도 있기 때문에 의견을 업데이트하거나 삭제할 수 있습니까?
Roman Podlinov

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떨어 뜨릴 수도 있습니다.
jfs

11
@RomanPodlinov : f.flush()데이터를 물리 디스크로 플러시하지 않습니다. 데이터를 OS로 전송합니다. 일반적으로 정전이 없으면 충분합니다. f.flush()아무 이유없이 코드를 느리게 만듭니다. 플러시는 해당 파일 버퍼 (앱 내부)가 가득 찼을 때 발생합니다. 더 자주 쓰는 것이 필요한 경우; buf.size 매개 변수를에 전달하십시오 open().
jfs

9
와의 연결 종료하는 것을 잊지 마세요r.close()
0xcaff

271

당신이 사용하는 경우 그것은 훨씬 쉽게 Response.rawshutil.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

32
이것이 정답이어야합니다! 허용 대답은 2-3메가바이트 / s로 당신을 가져옵니다. copyfileobj를 사용하면 ~ 40MB / s가됩니다. ~ 50-55MB / s의 컬 다운로드 (동일한 머신, 동일한 URL 등)
visoft

24
요청 연결이 해제되도록하기 위해 두 번째 (중첩) with블록을 사용하여 요청을 할 수 있습니다 .with requests.get(url, stream=True) as r:
Christian Long

7
@ChristianLong : 사실입니다.하지만 최근에는 지원 기능이 with requests.get()2017-06-07에만 병합되었으므로 매우 최근입니다 ! 귀하의 제안은 요청 2.18.0 이상을 가진 사람들에게 합리적입니다. 참조 : 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'

파일이 커지는 것을 보았지만 메모리 사용량은 17MB로 유지되었습니다. 뭔가 빠졌습니까?


2
Python 2.x의 경우from urllib import urlretrieve
Vadim Kotov

다운로드 속도가 느려집니다.
citynorman

@citynorman 정교하게 할 수 있습니까? 어떤 솔루션에 비해? 왜?
x-yuri

@ x-yuri 대 shutil.copyfileobj가장 많은 표를 얻은 솔루션 대, 저와 다른 사람들의 의견보기
citynorman

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 

또한 응답이 메모리에로드되었다고 어떻게 추론하고 있습니까?

파이썬은 다른에서 파일의 데이터를 플러시되지 않은 것처럼 소리 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())

1
쿠분투에서 시스템 모니터를 사용합니다. 파이썬 프로세스 메모리가 증가한다는 것을 보여줍니다 (25kb에서 최대 1.5gb).
로마 Podlinov

메모리가 부풀어 오르면 f.flush(); os.fsync()메모리에 여유 공간이 필요할 수 있습니다.
danodonovan

2
그것은os.fsync(f.fileno())
sebdelsol

29
requests.get () 호출에서 stream = True를 사용해야합니다. 그것이 메모리 팽창을 일으키는 원인입니다.
Hut8

1
사소한 오타 : 후에 콜론 ( ':')을 놓치다def DownloadFile(url)
Aubrey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.