从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

写在最后


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

快来试试吧!

相关文章
|
3月前
|
数据采集 存储 JSON
Python爬取知乎评论:多线程与异步爬虫的性能优化
Python爬取知乎评论:多线程与异步爬虫的性能优化
|
3月前
|
人工智能 安全 调度
Python并发编程之线程同步详解
并发编程在Python中至关重要,线程同步确保多线程程序正确运行。本文详解线程同步机制,包括互斥锁、信号量、事件、条件变量和队列,探讨全局解释器锁(GIL)的影响及解决线程同步问题的最佳实践,如避免全局变量、使用线程安全数据结构、精细化锁的使用等。通过示例代码帮助开发者理解并提升多线程程序的性能与可靠性。
113 0
|
3月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
4月前
|
JSON 算法 Java
打造终端里的下载利器:Python实现可恢复式多线程下载器
在数字时代,大文件下载已成为日常需求。本文教你用Python打造专业级下载器,支持断点续传、多线程加速、速度限制等功能,显著提升终端下载体验。内容涵盖智能续传、多线程分块下载、限速控制及Rich库构建现代终端界面,助你从零构建高效下载工具。
245 1
|
3月前
|
数据采集 存储 Java
多线程Python爬虫:加速大规模学术文献采集
多线程Python爬虫:加速大规模学术文献采集
|
4月前
|
数据采集 网络协议 前端开发
Python多线程爬虫模板:从原理到实战的完整指南
多线程爬虫通过并发请求大幅提升数据采集效率,适用于大规模网页抓取。本文详解其原理与实现,涵盖任务队列、线程池、会话保持、异常处理、反爬对抗等核心技术,并提供可扩展的Python模板代码,助力高效稳定的数据采集实践。
190 0
|
9月前
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
565 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
8月前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
250 20
|
7月前
|
数据采集 存储 安全
Python爬虫实战:利用短效代理IP爬取京东母婴纸尿裤数据,多线程池并行处理方案详解
本文分享了一套结合青果网络短效代理IP和多线程池技术的电商数据爬取方案,针对京东母婴纸尿裤类目商品信息进行高效采集。通过动态代理IP规避访问限制,利用多线程提升抓取效率,同时确保数据采集的安全性和合法性。方案详细介绍了爬虫开发步骤、网页结构分析及代码实现,适用于大规模电商数据采集场景。

推荐镜像

更多