正在 python 中分块下载文件?
Downloading files in chunks in python?
我正在编写一个简单的同步下载管理器,它可以分 10 个部分下载视频文件。我正在使用 requests
从 headers 获取 content-length。使用这个我在 10 中破坏和下载文件;字节块,然后将它们合并以形成完整的视频。下面的代码假设以这种方式工作,但最终合并的文件只能工作几秒钟,然后它就会被破坏。我的代码有什么问题?
import requests
import os
def intervals(parts, duration):
part_duration = duration // parts
return [(i * part_duration, (i + 1) * part_duration) for i in range(parts)]
home = os.path.expanduser("~")
if not os.path.exists(home+'/Desktop/temp'):
os.makedirs(home+'/Desktop/temp')
PATH = home+"/Desktop/temp/tmp.mp4"
example_file_url = "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4"
req = requests.head(example_file_url)
size = int(req.headers['Content-Length'])
content_section = 10
section_intervals = intervals(content_section,size)
with open(PATH, "wb") as file:
for i,(start,end) in enumerate(section_intervals):
headers = {"Range": "bytes="+str(start)+"-"+str(end)}
print(headers)
r = requests.get(example_file_url, headers=headers)
file.write(r.content)
问题
您的范围是错误的,因为 Range
header 指定的间隔给出了第一个和最后一个偏移量,例如bytes=0-10
表示从 0 到 10 的 11 个字节(与切片在 python 中的工作方式不同),因此 bytes=0-10
和 bytes=10-20
是重叠范围。例如,您需要 bytes=0-9
后跟 bytes=10-19
。
参见此 documentation 中的示例:
header requesting the first 1024 bytes ... Range: bytes=0-1023
(而 python 切片中的 [0:1023]
长度为 1023)。
你说它“工作几秒钟然后被损坏”,我假设你的意思是它在解码 MP4 输出的前几秒有效。它中断的点将是第一个下载部分的结尾,第一部分的最后一个字节在第二部分的开头重复。
另一个问题是你的总长度是错误的,因为你用整数除以 parts
然后当你再次乘以它时,你已经丢失了最后的小数部分。
修复
将你的 intervals
函数改成这个,它起作用了:
import math
def intervals(parts, duration):
part_duration = math.ceil(duration / parts)
return [(start, min(start + part_duration - 1, duration - 1))
for start in range(0, duration, part_duration)]
检查范围
正在插入打印语句:
print("Size = ", size)
print(section_intervals)
现在给出:
Size = 9840497
[(0, 984049), (984050, 1968099), (1968100, 2952149), (2952150, 3936199), (3936200, 4920249), (4920250, 5904299), (5904300, 6888349), (6888350, 7872399), (7872400, 8856449), (8856450, 9840496)]
而使用您原来的 intervals
函数,它给出:
Size = 9840497
[(0, 984049), (984049, 1968098), (1968098, 2952147), (2952147, 3936196), (3936196, 4920245), (4920245, 5904294), (5904294, 6888343), (6888343, 7872392), (7872392, 8856441), (8856441, 9840490)]
注意重叠范围和末尾缺少的字节。
使用 md5sum 验证输出
我们可以在最后通过计算校验和来验证下载。在此示例中,我从 Linux 命令行使用 md5sum
(尽管 cksum
也可以工作,因为为此目的不需要加密校验和)。
我调用了输出myoutput
。
$ md5sum myoutput
10c918b1d01aea85864ee65d9e0c2305 myoutput
现在我也直接用wget <url>
下载了一份,看到校验和一样了
$ wget https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4
--2020-07-21 08:26:52-- https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4
$ md5sum file_example_MP4_1280_10MG.mp4
10c918b1d01aea85864ee65d9e0c2305 file_example_MP4_1280_10MG.mp4
我正在编写一个简单的同步下载管理器,它可以分 10 个部分下载视频文件。我正在使用 requests
从 headers 获取 content-length。使用这个我在 10 中破坏和下载文件;字节块,然后将它们合并以形成完整的视频。下面的代码假设以这种方式工作,但最终合并的文件只能工作几秒钟,然后它就会被破坏。我的代码有什么问题?
import requests
import os
def intervals(parts, duration):
part_duration = duration // parts
return [(i * part_duration, (i + 1) * part_duration) for i in range(parts)]
home = os.path.expanduser("~")
if not os.path.exists(home+'/Desktop/temp'):
os.makedirs(home+'/Desktop/temp')
PATH = home+"/Desktop/temp/tmp.mp4"
example_file_url = "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4"
req = requests.head(example_file_url)
size = int(req.headers['Content-Length'])
content_section = 10
section_intervals = intervals(content_section,size)
with open(PATH, "wb") as file:
for i,(start,end) in enumerate(section_intervals):
headers = {"Range": "bytes="+str(start)+"-"+str(end)}
print(headers)
r = requests.get(example_file_url, headers=headers)
file.write(r.content)
问题
您的范围是错误的,因为 Range
header 指定的间隔给出了第一个和最后一个偏移量,例如bytes=0-10
表示从 0 到 10 的 11 个字节(与切片在 python 中的工作方式不同),因此 bytes=0-10
和 bytes=10-20
是重叠范围。例如,您需要 bytes=0-9
后跟 bytes=10-19
。
参见此 documentation 中的示例:
header requesting the first 1024 bytes ...
Range: bytes=0-1023
(而 python 切片中的 [0:1023]
长度为 1023)。
你说它“工作几秒钟然后被损坏”,我假设你的意思是它在解码 MP4 输出的前几秒有效。它中断的点将是第一个下载部分的结尾,第一部分的最后一个字节在第二部分的开头重复。
另一个问题是你的总长度是错误的,因为你用整数除以 parts
然后当你再次乘以它时,你已经丢失了最后的小数部分。
修复
将你的 intervals
函数改成这个,它起作用了:
import math
def intervals(parts, duration):
part_duration = math.ceil(duration / parts)
return [(start, min(start + part_duration - 1, duration - 1))
for start in range(0, duration, part_duration)]
检查范围
正在插入打印语句:
print("Size = ", size)
print(section_intervals)
现在给出:
Size = 9840497
[(0, 984049), (984050, 1968099), (1968100, 2952149), (2952150, 3936199), (3936200, 4920249), (4920250, 5904299), (5904300, 6888349), (6888350, 7872399), (7872400, 8856449), (8856450, 9840496)]
而使用您原来的 intervals
函数,它给出:
Size = 9840497
[(0, 984049), (984049, 1968098), (1968098, 2952147), (2952147, 3936196), (3936196, 4920245), (4920245, 5904294), (5904294, 6888343), (6888343, 7872392), (7872392, 8856441), (8856441, 9840490)]
注意重叠范围和末尾缺少的字节。
使用 md5sum 验证输出
我们可以在最后通过计算校验和来验证下载。在此示例中,我从 Linux 命令行使用 md5sum
(尽管 cksum
也可以工作,因为为此目的不需要加密校验和)。
我调用了输出myoutput
。
$ md5sum myoutput
10c918b1d01aea85864ee65d9e0c2305 myoutput
现在我也直接用wget <url>
下载了一份,看到校验和一样了
$ wget https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4
--2020-07-21 08:26:52-- https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1280_10MG.mp4
$ md5sum file_example_MP4_1280_10MG.mp4
10c918b1d01aea85864ee65d9e0c2305 file_example_MP4_1280_10MG.mp4