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库,同一会话内的持久连接是完全自动处理的!同一会话内你发出的任何请求都会自动复用恰当的连接。
只有所有的响应体数据被读取完毕连接才会被释放为连接池;以下是释放连接池的两种情况:
- 所以当requests请求时设置的参数 stream 为 False 时【即使不设置,requests请求时, stream 参数默认为 False 】,所有的响应体数据被读取完之后立即释放连接池。
- 或者当requests请求时设置的参数 stream 为 True 时,当访问 response.content 属性且读取下载响应体数据之后也会立即释放连接池。
【注意】如果在 stream 参数为 True 的网络请求中,不访问响应对象的任何方法属性,那么requests 无法将连接释放回连接池;除非消耗了所有的响应数据,或者调用了 Response.close 来关闭客户端与服务端的连接,否则这样会带来连接效率低下的问题。
关于response.iter_content的详解
response.iter_content 是可迭代对象。
一块一块的遍历需要下载的内容【可以理解为一块一块的下载服务器返回的数据量过大的响应体数据】,然后通过对文件流的操作按块逐一写入指定的路径文件中。块大小是它应该循环每次读入内存的字节数【即 chunk_size=1024 ]。