hello,大家好,我是wangzirui32,今天我们来学习如何使用Python实现多线程下载器,开始学习吧!
1. 流程&原理- 将
HEAD
请求发送到目标URL,获取文件的大小 - 根据文件大小对下载任务进行分配
- 每个线程发送含有
Range
参数的请求,获取文件的一部分 - 把每一个部分进行合成,下载完成
HEAD
请求是用来获取对方文件的基本信息,而不会返回具体内容,响应头中的Content-Length
便是文件的大小(单位:字节)。
发送请求头含有Range
参数的GET
请求时,不会返回文件的全部内容,只会返回Range
指定的部分内容,如Range='0-3000'
就只获取文件字节0-3000
的部分。
请在工作目录下创建app.py
和文件夹files
,执行命令下载所需包:
pip install requests
3. 代码
3.1 导入所需包
from queue import Queue # 队列
import requests # 网络请求库
import threading # 多线程
import os # *** 作文件
3.2 下载设置
我们要对下载的文件URL,文件名等进行设置:
# download settings
url = "http://img1.baidu.com/it/u=2476325767,3197989021&fm=26&fmt=auto" # 目标文件URL
filename = "img.jpg" # 下载后保存的文件名
thread_count = 5 # 启用线程数
copies_count = 20 # 将文件分为多少个部分作为单个下载任务
3.3 获取文件大小
def get_file_size(url) -> int:
response = requests.head(url) # HEAD请求
file_length = int(response.headers['Content-Length']) # 获取大小
return file_length # 返回大小
3.4 计算单个部分下载大小
def get_thread_download(file_length) -> list:
bytes = Queue(copies_count) # 创建字节队列
start_bytes = -1 # 开始字节为-1
for i in range(copies_count):
bytes_size = int(file_length/copies_count)*i # 计算目前字节
# 最后一个时 末尾字节为文件大小 避免落下一些字节未下载
if i == copies_count-1: bytes_size = file_length
# 字节范围
# start_bytes用来保存上一次的字节末尾
bytes_length = "{}-{}".format(start_bytes+1, bytes_size)
bytes.put([i, bytes_length]) # 加入队列 并赋予编号(i)
start_bytes = bytes_size # 将开始字节重新赋值
return bytes
3.5 线程类
class DownloadThread(threading.Thread):
def __init__(self, bytes_queue: Queue, url):
super().__init__(daemon=True)
self.bytes_queue = bytes_queue
self.url = url
def run(self):
while not self.bytes_queue.empty(): # 如果字节队列不为空
bytes_range = self.bytes_queue.get() # 读取范围信息
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84",
"Range": "bytes={}".format(bytes_range[1]) # 'Range'设置
}
response = requests.get(self.url, headers=headers) # 请求发送
with open("files/{}.tmp".format(bytes_range[0]), "wb") as f:
f.write(response.content) # 根据id生成临时文件
3.6 创建线程 开启下载
def create_threading(bytes_queue):
thread_list = []
for i in range(thread_count):
thread = DownloadThread(bytes_queue, url)
thread.start()
thread_list.append(thread)
for thread in thread_list:
thread.join()
3.7 合成文件
def composite_file():
# 如果文件存在 先移除
if os.path.isfile(filename): os.remove(filename)
with open(filename, "ab") as f: # 以追加模式打开文件
for i in range(copies_count): # 根据id查找文件
with open("files/{}.tmp".format(i), "rb") as bytes_f:
f.write(bytes_f.read())
for i in os.listdir("files"): # 清理临时文件
os.remove("files/{}".format(i))
3.8 入口函数
def main():
file_length = get_file_size(url)
copies_queue = get_thread_download(file_length)
create_threading(copies_queue)
composite_file()
if __name__ == '__main__':
main()
3.9 完整代码
from queue import Queue
import requests
import threading
import os
# download settings
url = "http://img1.baidu.com/it/u=2476325767,3197989021&fm=26&fmt=auto"
filename = "img.jpg"
thread_count = 5
copies_count = 20
class DownloadThread(threading.Thread):
def __init__(self, bytes_queue: Queue, url):
super().__init__(daemon=True)
self.bytes_queue = bytes_queue
self.url = url
def run(self):
while not self.bytes_queue.empty():
bytes_range = self.bytes_queue.get()
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84",
"Range": "bytes={}".format(bytes_range[1])
}
response = requests.get(self.url, headers=headers)
with open("files/{}.tmp".format(bytes_range[0]), "wb") as f:
f.write(response.content)
def get_file_size(url) -> int:
response = requests.head(url)
file_length = int(response.headers['Content-Length'])
return file_length
def get_thread_download(file_length) -> list:
bytes = Queue(copies_count)
start_bytes = -1
for i in range(copies_count):
bytes_size = int(file_length/copies_count)*i
if i == copies_count-1: bytes_size = file_length
bytes_length = "{}-{}".format(start_bytes+1, bytes_size)
bytes.put([i, bytes_length])
start_bytes = bytes_size
return bytes
def create_threading(bytes_queue):
thread_list = []
for i in range(thread_count):
thread = DownloadThread(bytes_queue, url)
thread.start()
thread_list.append(thread)
for thread in thread_list:
thread.join()
def composite_file():
if os.path.isfile(filename): os.remove(filename)
with open(filename, "ab") as f:
for i in range(copies_count):
with open("files/{}.tmp".format(i), "rb") as bytes_f:
f.write(bytes_f.read())
for i in os.listdir("files"):
os.remove("files/{}".format(i))
def main():
file_length = get_file_size(url)
copies_queue = get_thread_download(file_length)
create_threading(copies_queue)
composite_file()
if __name__ == '__main__':
main()
设置好url
和filename
,运行代码,便可以开启愉快的下载之路了!
注意:大部分URL(指向文件的URL)都支持HEAD
请求,如果HEAD
请求失效,程序将无法运行。
好了,今天的课程就到这里,我是wangzirui32,喜欢的可以点个收藏和关注,我们下次再见!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)