*2022年1月的最新进展
requests核心开发者nateprewitt在这个2021年8月的Issue中提到:由于添加enforce_content_length=True
是个Breaking Change,因此只能提交此PR到requests的3.0版本中;但同时,他们已经给urllib3提交了一个PR,建议让enforce_content_length
默认开启
问题
在使用python时,我们通常用requests库来实现文件下载,如下:
import requests
r = requests.get(url)
with open("demo3.zip", "wb") as code:
code.write(r.content)
偶尔我们会发现下载的文件不完整,影响后面的操作。
原因
简而言之,由于目前python3使用的仍然是requests 2.xx版本,里面包含的urllib3也是旧版本,因此未做校验。
在未来的requests 3.x中,它会提供enforce_content_length
参数,默认值为True。也就是说,如果requests库收到一个不完整的内容,会引发一个异常:
urllib3.exceptions.IncompleteRead: IncompleteRead(6 bytes read, 4 more expected)
解决办法
在我们仍然使用requests 2.xx版本的情况下,可以使用如下代码校验下载的文件的完整性:
response = requests.get(...)
expected_length = response.headers.get('Content-Length')
if expected_length is not None:
actual_length = response.raw.tell()
expected_length = int(expected_length)
if actual_length < expected_length:
raise IOError(
'incomplete read ({} bytes read, {} more expected)'.format(
actual_length,
expected_length - actual_length
)
)
此时,你可能想问,response.raw.tell()
的功能是什么呢?
因为服务器可能返回的是gzip压缩过的响应,此时Header中的Content-Length
对应的是压缩后的字节数。而response.raw.tell()
恰好返回的是在解压缩之前,读取到的字节数;相比之下,len(response.content)
却返回的是解压缩后的字节数,因此需要使用前者进行校验。
节选自:https://blog.petrzemek.net/2018/04/22/on-incomplete-http-reads-and-the-requests-library-in-python,对应的中文翻译版:https://python.freelycode.com/contribution/detail/1213