Requests是一个非常好的库。我想用它来下载大文件(>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内存使用都会受到限制:

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供进一步参考。

其他回答

这不是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 MB。我错过了什么吗?

请使用python的wget模块。下面是一个片段

import wget
wget.download(url)

如果使用响应,就简单多了。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

这样可以在不使用过多内存的情况下将文件流到磁盘,而且代码很简单。

注:根据文档,响应。Raw不会解码gzip和压缩传输编码,因此您需要手动执行此操作。

使用以下流代码,无论下载的文件大小如何,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供进一步参考。

下面是异步分块下载用例的另一种方法,无需将所有文件内容读入内存。 这意味着从URL读取和写入文件都是使用asyncio库实现的(aiohttp从URL读取,aiofiles写入文件)。

以下代码应该适用于Python 3.7及更高版本。 复制粘贴前只需编辑SRC_URL和DEST_FILE变量。

import aiofiles
import aiohttp
import asyncio

async def async_http_download(src_url, dest_file, chunk_size=65536):
    async with aiofiles.open(dest_file, 'wb') as fd:
        async with aiohttp.ClientSession() as session:
            async with session.get(src_url) as resp:
                async for chunk in resp.content.iter_chunked(chunk_size):
                    await fd.write(chunk)

SRC_URL = "/path/to/url"
DEST_FILE = "/path/to/file/on/local/machine"

asyncio.run(async_http_download(SRC_URL, DEST_FILE))