使用请求在python中下载大文件

Download large file in python with requests

请求是一个非常好的库。我想用它下载大文件(>1GB)。问题是不可能将整个文件保存在内存中,我需要将其分块读取。以下代码有问题

1
2
3
4
5
6
7
8
9
10
11
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下载大文件,您可以在这里找到它。它支持多线程和重新连接(它可以监视连接),还可以调整下载任务的套接字参数。


使用以下流代码,无论下载文件的大小,python内存的使用都受到限制:

1
2
3
4
5
6
7
8
9
10
11
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 chunk: # filter out keep-alive new chunks
                    f.write(chunk)
                    # f.flush()
    return local_filename

注意,使用iter_content返回的字节数并不完全是chunk_size;它应该是一个更大的随机数,并且在每次迭代中都会有所不同。

请参阅http://docs.python requests.org/en/latest/user/advanced/body content workflow以获取进一步参考。


如果您使用Response.rawshutil.copyfileobj(),会更容易:

1
2
3
4
5
6
7
8
9
10
import requests
import shutil

def download_file(url):
    local_filename = url.split('/')[-1]
    r = requests.get(url, stream=True)
    with open(local_filename, 'wb') as f:
        shutil.copyfileobj(r.raw, f)

    return local_filename

这将文件流式传输到磁盘,而不使用过多的内存,而且代码很简单。


您的块大小可能太大,您是否尝试删除它?一次可能删除1024个字节?(也可以使用with整理语法)

1
2
3
4
5
6
7
8
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

顺便说一下,您如何推断响应已加载到内存中?

听起来好像python没有将数据刷新到文件中,从其他问题来看,您可以尝试使用f.flush()os.fsync()来强制文件写入并释放内存;

1
2
3
4
5
6
    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())


不完全是OP的要求,但是…用urllib做这件事是非常容易的:

1
2
3
4
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)

或者这样,如果要将其保存到临时文件中:

1
2
3
4
5
6
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)

我看了这个过程:

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

我看到文件在增长,但是内存使用量保持在17MB。我错过什么了吗?