0x01 分析

一个简单的多线程下载资源的Python脚本,主要实现部分包含两个类:

  • Download类:包含download()get_complete_rate()两种方法。

    • download()方法种首先用 urlopen() 方法打开远程资源并通过 Content-Length获取资源的大小,然后计算每个线程应该下载网络资源的大小及对应部分,最后依次创建并启动多个线程来下载网络资源的指定部分。
    • get_complete_rate()则是用来返回已下载的部分占全部资源大小的比例,用来回显进度。
  • ThreadDownload类:该线程类继承了threading.Thread类,包含了一个run()方法。

    • run()方法主要负责每个线程读取网络数据并写入本地。


0x02 代码

ThreadDownload.py:
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import threading
from urllib.request import *


class Download:
def __init__(self, link, file_path, thread_num):
# 下载路径
self.link = link
# 保存位置
self.file_path = file_path
# 使用多少线程
self.thread_num = thread_num
# 初始化threads数组
self.threads = []

def download(self):
req = Request(url=self.link, method='GET')
req.add_header('Accept', '*/*')
req.add_header('Charset', 'UTF-8')
req.add_header('Connection', 'Keep-Alive')
f = urlopen(req)
# 获取要下载的文件的大小
self.file_size = int(dict(f.headers).get('Content-Length', 0))
f.close()
# 计算每个线程要下载的资源的大小
current_part_size = self.file_size // self.thread_num + 1
for i in range(self.thread_num):
# 计算每个线程下载的开始位置
start_pos = i * current_part_size
# 每个线程使用一个wb模式打开的文件进行下载
t = open(self.file_path, 'wb')
t.seek(start_pos, 0)
# 创建下载线程
td = ThreadDownload(self.link, start_pos, current_part_size, t)
self.threads.append(td)
td.start()

# 获下载的完成百分比
def get_complete_rate(self):
sum_size = 0
for i in range(self.thread_num):
sum_size += self.threads[i].length
return sum_size / self.file_size

class ThreadDownload(threading.Thread):
def __init__(self, link, start_pos, current_part_size, current_part):
super().__init__()
# 下载路径
self.link = link
# 当前线程的下载位置
self.start_pos = start_pos
# 定义当前线程负责下载的文件大小
self.current_part_size = current_part_size
# 当前文件需要下载的文件快
self.current_part = current_part
# 定义该线程已经下载的字节数
self.length = 0

def run(self):
req = Request(url = self.link, method='GET')
req.add_header('Accept', '*/*')
req.add_header('Charset', 'UTF-8')
req.add_header('Connection', 'Keep-Alive')

f = urlopen(req)
# 跳过self.start_pos个字节,表明该线程只负责下载自己负责的那部分内容
for i in range(self.start_pos):
f.read(1)
# 读取网络数据,并写入本地
while self.length < self.current_part_size:
data = f.read(1024)
if data is None or len(data) <= 0:
break
self.current_part.write(data)
# 累计该线程下载的总大小
self.length += len(data)
self.current_part.close()
f.close()
thread_download-master.py:
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
#!/usr/bin/env python 
# -*- coding: utf-8 -*-

import sys
import time

from ThreadDownload import *


def show_process(dl):
while dl.get_complete_rate() < 1:
complete_rate = int(dl.get_complete_rate()*100)
print('\r' + '下载中···(已下载' + str(complete_rate) + '%)', end='', flush=True)
time.sleep(0.01)


def main():
try:
Link = input('[+]' + 'Link: ')
file_path = input('[+]' + 'File Path: ')
thread_number = input('[+]' + 'Thread Number: ')
thread_number = int(thread_number)
dl = Download(Link, file_path, thread_number)
dl.download()
print('\n开始下载!')
show_process(dl)
print('\r' + '下载中···(已下载' + '100%)', end='', flush=True)
print('\n下载完成!')
except Exception:
print('Parameter Setting Error')
sys.exit(1)

if __name__=='__main__':
main()


0x03 运行结果

下载歌曲《男孩》为例,下载到./Download/目录下并命名为男孩.mp3,设置5个线程:

在这里插入图片描述

在这里插入图片描述

下载成功:

在这里插入图片描述