开始

  • 基于Python的小文件下载是非常简单的:
1
2
3
4
5
6
7
import requests

def dl(url):
r = requests.get(self.url, stream=True)
with open(self.name, 'wb') as f:
for chunk in r.iter_content(8192):
f.write(chunk)
  • 但在实际应用时,这样的简单文件下载往往不满足生产需要。比如,单线程下载速度慢IO频繁玄学的是有时候这个可能比多线程下载要快一些。

普通文件下载策略

如何分配线程数?

  • 在使用qs下载时,首先判断当前系统的CPU状态,保证在调用min(CPU核心数*4, 16) + max(CPU核心数/2, 2)的线程数情况下,平稳下载。其中,max部分是写文件线程池,用于处理频繁的文件IO。

如何针对一个文件并行下载?

  • 利用http通讯协议,在http头部信息添加range: bytes=from-to信息,对一个文件分块下载。
  • 既然可以分块,块与块之间又没有依赖,就可以进行并行优化。并且,得益于文件操作的位置不同,甚至不需要针对下载文件设置读写锁
  • 当然,这样下载有一个前提,就是需要预先知道待下载的文件具体有多大。所以,在无法预知文件大小时,qs仍然会采用最朴素的下载方法(也就是开篇的那个算法)

如何设定块大小?

  • 经过反复测试,我认为相对性能较好的块大小区间:
  • 很多时候,下载的文件其实并没有很大,针对大小不足5M的文件,qs不会启用多线程下载。

下载中途可能的问题

  • 针对块下载时可能遇到的意外错误,qs将无限制的重试下去。如果重试次数达到一定数量,每次重试时qs将小憩一段时间。
  • 在一些并不稳定的下载过程中,遇到网络波动下载进程终止是很麻烦的事情,但是,你不用为qs担心,在启动多线程下载时,qs会将下载过的块进行标记并将信息存储在以.qs_dl结尾的文件里,当你重新进行文件下载时,qs可以实现断点续下载
  • 具备下载限速的资源,多线程下载加速并不十分明显。过快的下载速度+低速的内存可能会造成下载过程中qs占用过多内存的问题。

qs下载的优势

  • 断点续下载
  • 得益于分块策略,可以大幅提速
  • 不会被意外的网络问题中断下载任务
  • qs针对每一块下载都设置了超时重试,这意味着网络状态突然改变时,qs性能不会一直受到影响。

将尝试的优化

  1. 动态调整块大小。
  2. 增强程序健壮性

部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
...


class Downloader:
...

def run(self):
if self.size > 0:
if not self.ctn:
with open(self.name, "wb") as fp:
fp.truncate(self.size)
for i in range(0, self.size, self.fileBlock):
if self.ctn and i in self.ctn:
self.cur_sz += min(i + self.fileBlock, self.size) - i
else:
self.job_queue.put(i)
retry_cnt = 0
while not self.job_queue.empty():
self.futures.clear()
while not self.job_queue.empty():
cur = self.job_queue.get()
if cur not in self.ctn:
self.futures.append(self.pool.submit(self._dl, cur))
wait(self.futures)
if not self.job_queue.empty() and retry_cnt > 2:
print('\r[INFO] Exists File Block Lost, Retrying after 0.5 sec')
time.sleep(0.5)
retry_cnt += 1
self.writers.wait()
self.ctn_file.close()
os.remove(self.name + '.qs_dl')
else:
self._single_dl()
print('[INFO] %s download done!' % self.name)


def normal_dl(url):
Downloader(url, min(16, core_num * 4)).run()

流媒体下载策略

  • 经常开车的小伙们总会需要下载一些流媒体,其视频信息往往存储在以.m3u8结尾的索引文件中;
  • qs可以识别出这类文件,并启用线程池并行下载索引文件中的所有.ts文件,并帮助你把.ts文件们合并(但是并不保证这个过程一定没有错误,可能会造成你转换视频格式时出现问题)