requests请求库之文件下载(小文件下载&&大文件下载)

前言:

1、普通下载

①对于非文本请求,我们可以通过 Response 对象的 content 属性以字节的方式访问请求响应体。

【注意】这种模式只能下载小文件。因为在这种模式下,从服务器接收到的数据是一直储存在内存中,只有当 write 时才写入硬盘,如果文件很大,那么所占用的内存也是很大的。

②下面将一张网络上的图片下载到本地并保存(文件名不变):

import requests

url = 'http://www.hangge.com/blog/images/logo.png'
response = requests.get(url)  # 此时在内存为response响应对象开辟空间,从服务器返回的数据也是一直存储在内存中中
with open("logo.png", "wb") as code:
    code.write(response.content)  # 调用write方法时将内存中的二进制数据写入硬盘

代码运行后可以看到图片已经成功下载下来。

2、流式下载:详细见下面的方法二

下面代码我们改成流式下载,即边下载边保存。这种方式适合用来下载大文件

import requests

url = 'http://www.hangge.com/blog/images/logo.png'
r = requests.get(url, stream=True)
with open("logo.png", "wb") as f:
    for bl in r.iter_content(chunk_size=1024):
        if bl:
            f.write(bl)

3、带进度的文件下载

①如果文件体积很大,下载时我们最好能实时显示当前的下载进度。

为方便使用应该封装一个下载方法(内部同样使用流式下载的方式)

import requests
from contextlib import closing


# 文件下载器
def down_load(file_url, file_path):
    with closing(requests.get(file_url, stream=True)) as response:
        chunk_size = 1024  # 单次请求最大值
        content_size = int(response.headers['content-length'])  # 内容体总大小
        data_count = 0
        with open(file_path, "wb") as file:
            for data in response.iter_content(chunk_size=chunk_size):
                file.write(data)
                data_count = data_count + len(data)
                now_jd = (data_count / content_size) * 100
                print("\r 文件下载进度:%d%%(%d/%d) - %s"
                      % (now_jd, data_count, content_size, file_path), end=" ")


if __name__ == '__main__':
    fileUrl = 'http://www.hangge.com/hangge.zip'  # 文件链接
    filePath = "logo.zip"  # 文件路径
    down_load(fileUrl, filePath)

运行效果如下,可以看到在文件下载的过程会实时显示当前的进度:

4、带下载速度显示的文件下载

对上面的方法做个改进,增加实时下载速度的计算和显示:

import requests
import time
from contextlib import closing


# 文件下载器
def down_load(file_url, file_path):
    start_time = time.time()  # 文件开始下载时的时间
    with closing(requests.get(file_url, stream=True)) as response:
        chunk_size = 1024  # 单次请求最大值
        content_size = int(response.headers['content-length'])  # 内容体总大小
        data_count = 0
        with open(file_path, "wb") as file:
            for data in response.iter_content(chunk_size=chunk_size):
                file.write(data)
                data_count = data_count + len(data)
                now_jd = (data_count / content_size) * 100
                speed = data_count / 1024 / (time.time() - start_time)
                print("\r 文件下载进度:%d%%(%d/%d) 文件下载速度:%dKB/s - %s"
                      % (now_jd, data_count, content_size, speed, file_path), end=" ")


if __name__ == '__main__':
    fileUrl = 'http://www.hangge.com/hangge.zip'  # 文件链接
    filePath = "hangge.zip"  # 文件路径
    down_load(fileUrl, filePath)

方法一:

使用python的内置 urllib 模块提供的 urlretrieve() 函数。

urlretrieve() 方法直接将远程数据下载到本地

urlretrieve(url, [filename=None, [reporthook=None, [data=None]]])
  • 请求url不可为空: urlretrieve(url) 
  • 下载后的文件保存路径可为空: urlretrieve(url, [filename=None]) 
  • 传输到服务器的数据data可为空: urlretrieve(url, [filename=None, [reporthook=None, [data=None]]]) 

解释说明:

  • 参数  finename 指定了保存本地路径(如果参数未指定,urllib库 将会生成一个临时文件用以保存数据。)

  • 参数  reporthook 是一个回调函数,当连接上服务器、以及相应的数据块传输完毕时会触发该回调,我们可以利用这个回调函数来显示当前的下载进度。

  • 参数  data 是请求数据,该方法返回一个包含两个元素的 (filename, headers) 元组, filename  表示保存到本地的路径, header 表示服务器的响应头。 

实例:

from urllib import request

load_url = 'http://docs.python-requests.org/zh_CN/latest/_static/requests-sidebar.png'


def Schedule(a,b,c):
    '''
    a:已经下载的数据块
    b:数据块的大小
    c:远程文件的大小
   '''
    per = 100.0 * a * b / c
    if per > 100 :
        per = 100
    print('%.2f%%' % per)


request.urlretrieve(url=load_url,filename='d:\\python.png',reporthook=Schedule)

方法二:

使用python第三方requests库。

使用该方法下载大文件可以防止占用过多的内存,因为每次只下载小部分数据。

import requests

load_url = 'http://docs.python-requests.org/zh_CN/latest/_static/requests-sidebar.png'

r = requests.get(url=load_url, stream=True)
with open("D:\\python.png","wb") as f:
    for chunk in r.iter_content(chunk_size=1024):
        f.write(chunk)
  •  stream 参数默认为False,它会立即开始下载文件并放到内存中,如果文件过大,有可能导致内存不足。

  •  stream 参数设置成True时,它不会立即开始下载,当你使用 iter_content 或 iter_lines 遍历二进制响应内容访问内容属性时才开始下载。

  •  iter_content :一块一块的遍历要下载的内容

  •  iter_lines :一行一行的遍历要下载的内容

关于requests请求时的stream参数详解:

默认情况下,进行网络请求后,接口响应体会立即被下载。(当响应体数据量过大时,立即下载会占用系统大量内存)【当响应体数据量并不是很大时,可以不用考虑内存,即可以直接使用 response.content 下载】

可以通过 stream 参数覆盖该行为,推迟下载响应体直到访问 Response.content 属性:(请求后不立即下载响应体,而是先下载响应头)【当访问 resonse.content 字节码内容的时候才会下载响应体】

import requests

tarball_url = 'https://github.com/kennethreitz/requests/tarball/master'

r = requests.get(tarball_url, stream=True)

注意,此时仅有响应头被下载下来了,连接保持打开状态,因此允许我们可以根据某些限制条件获取接口响应二进制内容:

if int(r.headers['content-length']) < TOO_LONG:
  content = r.content # 此时开始下载响应体
  ...

解释:

归功于 urllib3库,同一会话内的持久连接是完全自动处理的!同一会话内你发出的任何请求都会自动复用恰当的连接。

只有所有的响应体数据被读取完毕连接才会被释放为连接池;以下是释放连接池的两种情况:

  1. 所以当requests请求时设置的参数 stream 为 False 时【即使不设置,requests请求时, stream 参数默认为 False 】,所有的响应体数据被读取完之后立即释放连接池
  2. 或者当requests请求时设置的参数 stream 为 True 时,当访问 response.content 属性且读取下载响应体数据之后也会立即释放连接池

【注意】如果在 stream 参数为 True 的网络请求中,不访问响应对象的任何方法属性,那么requests 无法将连接释放回连接池;除非消耗了所有的响应数据,或者调用了 Response.close 来关闭客户端与服务端的连接,否则这样会带来连接效率低下的问题。

关于response.iter_content的详解

 response.iter_content 是可迭代对象

一块一块的遍历需要下载的内容【可以理解为一块一块的下载服务器返回的数据量过大的响应体数据】,然后通过对文件流的操作按块逐一写入指定的路径文件中。块大小是它应该循环每次读入内存的字节数【即 chunk_size=1024 ]

posted @ 2021-07-14 19:38  习久性成  阅读(4039)  评论(0编辑  收藏  举报