从0到1手把手教你实现一个 Python 多线程下载器(四)

简介: 从0到1手把手教你实现一个 Python 多线程下载器(四)

示例操作


回顾之前写的单线程版本下载器,我们知道怎么获取待下载的文件大小以及如何分块下载。注意上面的分块下载是仅有一个线程在操作的,譬如文件大小为:1000 B,每次下载 100 B,那么单线程会连续地每次读取 100 B 的内容,直到没有内容可读取。


为了能让多线程下载同一个文件,我们需要为每一个线程分配属于它自己的任务,比如说要下载大小为 100 B的文件,那么线程一可以负责下载 0-50 B,线程二负责下载 50-100 B,这样子分两个线程来下载。


要实现任务以上的任务分配。我们自然得学会怎么将一个数字分为多个区间,下面以将 100 分为每一块 20 为例,展示怎么实现这样子的功能

# 总大小
total = 100
# 每一块的大小
step = 20
# 分多块
parts = [(start, min(start+step,total)) for start in range(0, total, step)]
print(parts)


运行上面的代码,会达到以下输出

[(0, 20), (20, 40), (40, 60), (60, 80), (80, 100)]


可以看到,我们成功地完成了较为完美的切割操作! 同样地,为了能够更好地维护代码,我们可以尝试把它抽取为函数,示例如下

from __future__ import annotations
def split(start: int, end: int, step: int) -> list[tuple[int, int]]:
    '''
    将指定区间的数切割为多个区间
    Parameters
    ----------
    start :起始位置
    end : 终止位置
    step : 区间长度
    Return
    ------
    区间元组构成的列表
    '''
    # 分多块
    parts = [(start, min(start+step, end))
             for start in range(0, end, step)]
    return parts
if "__main__" == __name__:
    # 起始位置
    start = 1
    # 终止位置
    total = 102
    # 区间长度
    step = 20
    parts = split(start, total, step)
    print(parts)


以上操作是为了后续分段下载文件做准备

下载部分文件

下面我将以下面这个链接为例,演示如何下载某一部分的文件

https://issuecdn.baidupcs.com/issue/netdisk/yunguanjia/BaiduNetdisk_7.2.8.9.exe
import requests
# 待下载部分的文件起始位置
start = 0
# 待下载部分的文件终止位置
end = 1000
# 每次读取的大小
chunk_size = 128
# 记录下载的位置
seek = start
# 请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE'
}
# 这是核心!设定下载的范围
headers['Range'] = f'bytes={start}-{end}'
# 下载链接
url = 'https://issuecdn.baidupcs.com/issue/netdisk/yunguanjia/BaiduNetdisk_7.2.8.9.exe'
response = requests.get(url, headers=headers, stream=True)
for chunk in response.iter_content(chunk_size=chunk_size):
    _seek = min(seek+chunk_size, end)
    print(f'下载: {seek}-{_seek}')
    seek = _seek
代码运行输出
下载: 0-128
下载: 128-256
下载: 256-384
下载: 384-512
下载: 512-640
下载: 640-768
下载: 768-896
下载: 896-1000


有了以上基础,我们来尝试把之前的单线程下载器修改为多线程的(我还没想好怎么一步步教,先贴有详细注释的代码吧,后续更新)


最终代码(带进度条的多线程下载器)

from __future__ import annotations
# 用于显示进度条
from tqdm import tqdm
# 用于发起网络请求
import requests
# 用于多线程操作
import multitasking
import signal
# 导入 retry 库以方便进行下载出错重试
from retry import retry
signal.signal(signal.SIGINT, multitasking.killall)
# 请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE'
}
# 定义 1 MB 多少为 B
MB = 1024**2
def split(start: int, end: int, step: int) -> list[tuple[int, int]]:
    # 分多块
    parts = [(start, min(start+step, end))
             for start in range(0, end, step)]
    return parts
def get_file_size(url: str, raise_error: bool = False) -> int:
    '''
    获取文件大小
    Parameters
    ----------
    url : 文件直链
    raise_error : 如果无法获取文件大小,是否引发错误
    Return
    ------
    文件大小(B为单位)
    如果不支持则会报错
    '''
    response = requests.head(url)
    file_size = response.headers.get('Content-Length')
    if file_size is None:
        if raise_error is True:
            raise ValueError('该文件不支持多线程分段下载!')
        return file_size
    return int(file_size)
def download(url: str, file_name: str, retry_times: int = 3, each_size=16*MB) -> None:
    '''
    根据文件直链和文件名下载文件
    Parameters
    ----------
    url : 文件直链
    file_name : 文件名
    retry_times: 可选的,每次连接失败重试次数
    Return
    ------
    None
    '''
    f = open(file_name, 'wb')
    file_size = get_file_size(url)
    @retry(tries=retry_times)
    @multitasking.task
    def start_download(start: int, end: int) -> None:
        '''
        根据文件起止位置下载文件
        Parameters
        ----------
        start : 开始位置
        end : 结束位置
        '''
        _headers = headers.copy()
        # 分段下载的核心
        _headers['Range'] = f'bytes={start}-{end}'
        # 发起请求并获取响应(流式)
        response = session.get(url, headers=_headers, stream=True)
        # 每次读取的流式响应大小
        chunk_size = 128
        # 暂存已获取的响应,后续循环写入
        chunks = []
        for chunk in response.iter_content(chunk_size=chunk_size):
            # 暂存获取的响应
            chunks.append(chunk)
            # 更新进度条
            bar.update(chunk_size)
        f.seek(start)
        for chunk in chunks:
            f.write(chunk)
        # 释放已写入的资源
        del chunks
    session = requests.Session()
    # 分块文件如果比文件大,就取文件大小为分块大小
    each_size = min(each_size, file_size)
    # 分块
    parts = split(0, file_size, each_size)
    print(f'分块数:{len(parts)}')
    # 创建进度条
    bar = tqdm(total=file_size, desc=f'下载文件:{file_name}')
    for part in parts:
        start, end = part
        start_download(start, end)
    # 等待全部线程结束
    multitasking.wait_for_tasks()
    f.close()
    bar.close()
if "__main__" == __name__:
    # url = 'https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0d/ea/f936c14b6e886221e53354e1992d0c4e0eb9566fcc70201047bb664ce777/tensorflow-2.3.1-cp37-cp37m-macosx_10_9_x86_64.whl#sha256=1f72edee9d2e8861edbb9e082608fd21de7113580b3fdaa4e194b472c2e196d0'
    url = 'https://issuecdn.baidupcs.com/issue/netdisk/yunguanjia/BaiduNetdisk_7.2.8.9.exe'
    file_name = 'BaiduNetdisk_7.2.8.9.exe'
    # 开始下载文件
    download(url, file_name)


代码运行过程image.png

写在最后


本文内容丰富,涉及到的主要知识有:文件读写、发起网络请求、多线程操作、代码出错重试。由浅入深,层层递进,最终实现了一个简易的带进度条的多线程下载器。

快来试试吧!

相关文章
|
2月前
|
安全 数据处理 开发者
Python中的多线程编程:从入门到精通
本文将深入探讨Python中的多线程编程,包括其基本原理、应用场景、实现方法以及常见问题和解决方案。通过本文的学习,读者将对Python多线程编程有一个全面的认识,能够在实际项目中灵活运用。
|
25天前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
1月前
|
Java Unix 调度
python多线程!
本文介绍了线程的基本概念、多线程技术、线程的创建与管理、线程间的通信与同步机制,以及线程池和队列模块的使用。文章详细讲解了如何使用 `_thread` 和 `threading` 模块创建和管理线程,介绍了线程锁 `Lock` 的作用和使用方法,解决了多线程环境下的数据共享问题。此外,还介绍了 `Timer` 定时器和 `ThreadPoolExecutor` 线程池的使用,最后通过一个具体的案例展示了如何使用多线程爬取电影票房数据。文章还对比了进程和线程的优缺点,并讨论了计算密集型和IO密集型任务的适用场景。
59 4
|
20天前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
54 0
|
2月前
|
Python
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。
|
2月前
|
Java Python
python知识点100篇系列(16)-python中如何获取线程的返回值
【10月更文挑战第3天】本文介绍了两种在Python中实现多线程并获取返回值的方法。第一种是通过自定义线程类继承`Thread`类,重写`run`和`join`方法来实现;第二种则是利用`concurrent.futures`库,通过`ThreadPoolExecutor`管理线程池,简化了线程管理和结果获取的过程,推荐使用。示例代码展示了这两种方法的具体实现方式。
python知识点100篇系列(16)-python中如何获取线程的返回值
|
2月前
|
数据挖掘 程序员 调度
探索Python的并发编程:线程与进程的实战应用
【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
34 3
|
2月前
|
并行计算 安全 Java
Python 多线程并行执行详解
Python 多线程并行执行详解
74 3
|
2月前
|
网络协议 安全 Java
难懂,误点!将多线程技术应用于Python的异步事件循环
难懂,误点!将多线程技术应用于Python的异步事件循环
70 0
|
2月前
|
安全 Java 数据库连接
Python多线程编程:竞争问题的解析与应对策略
Python多线程编程:竞争问题的解析与应对策略
28 0