从网站下载单一超大文件有时超时问题

简介: 从网站下载单一超大文件有时超时问题
# -*- codeing=utf-8 -*-
# @Time:2022/5/29 20:33
# @Author:Ye Zhoubing
# @File: download_large_file.py
# @software:PyCharm
"""
python 多线程下载大文件,并实现断点续传
"""
```python
# -*- codeing=utf-8 -*-
# @Time:2022/5/29 20:33
# @Author:Ye Zhoubing
# @File: download_large_file.py
# @software:PyCharm
"""
python 多线程下载大文件,并实现断点续传
"""
import os
import time
import httpx
from tqdm import tqdm
from threading import Thread
import datetime
import sys


class Logger(object):
    def __init__(self, filename='default.log', stream=sys.stdout):
        self.terminal = stream
        self.log = open(filename, 'w' , encoding = 'utf-8')

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)

    def flush(self):
        pass





class DownloadFile(object):
    def __init__(self, download_url, data_folder, thread_num):
        """
        :param download_url: 文件下载连接
        :param data_folder: 文件存储目录
        :param thread_num: 开辟线程数量
        """
        self.download_url = download_url
        self.data_folder = data_folder
        self.thread_num = thread_num
        self.file_size = None
        self.cut_size = None
        self.tqdm_obj = None
        self.thread_list = []
        self.file_path = os.path.join(self.data_folder, download_url.split('/')[-1])

    def downloader(self, etag, thread_index, start_index, stop_index, retry=False):
        sub_path_file = "{}_{}".format(self.file_path, thread_index)
        if os.path.exists(sub_path_file):
            temp_size = os.path.getsize(sub_path_file)  # 本地已经下载的文件大小
            if not retry:
                self.tqdm_obj.update(temp_size)  # 更新下载进度条
        else:
            temp_size = 0
        if stop_index == '-': stop_index = ""
        headers = {
   'Range': 'bytes={}-{}'.format(start_index + temp_size, stop_index),
                   'ETag': etag, 'if-Range': etag,
                   }
        down_file = open(sub_path_file, 'ab')
        try:
            with httpx.stream("GET", self.download_url, headers=headers) as response:
                num_bytes_downloaded = response.num_bytes_downloaded
                for chunk in response.iter_bytes():
                    if chunk:
                        down_file.write(chunk)
                        self.tqdm_obj.update(response.num_bytes_downloaded - num_bytes_downloaded)
                        num_bytes_downloaded = response.num_bytes_downloaded
        except Exception as e:
            print("Thread-{}:请求超时,尝试重连\n报错信息:{}".format(thread_index, e))
            self.downloader(etag, thread_index, start_index, stop_index, retry=True)
        finally:
            down_file.close()
        return

    def get_file_size(self):
        """
        获取预下载文件大小和文件etag
        :return:
        """
        with httpx.stream("GET", self.download_url) as response2:
            etag = ''
            total_size = int(response2.headers["Content-Length"])
            for tltle in response2.headers.raw:
                if tltle[0].decode() == "ETag":
                    etag = tltle[1].decode()
                    break
        return total_size, etag

    def cutting(self):
        """
        切割成若干份
        :param file_size: 下载文件大小
        :param thread_num: 线程数量
        :return:
        """
        cut_info = {
   }
        cut_size = self.file_size // self.thread_num
        for num in range(1, self.thread_num + 1):
            if num != 1:
                cut_info[num] = [cut_size, cut_size * (num - 1) + 1, cut_size * num]
            else:
                cut_info[num] = [cut_size, cut_size * (num - 1), cut_size * num]
            if num == self.thread_num:
                cut_info[num][2] = '-'
        return cut_info, cut_size

    def write_file(self):
        """
        合并分段下载的文件
        :param file_path:
        :return:
        """
        if os.path.exists(self.file_path):
            if len(self.file_path) >= self.file_size:
                return
        with open(self.file_path, 'ab') as f_count:
            for thread_index in range(1, self.thread_num + 1):
                with open("{}_{}".format(self.file_path, thread_index), 'rb') as sub_write:
                    f_count.write(sub_write.read())
                # 合并完成删除子文件
                os.remove("{}_{}".format(self.file_path, thread_index))
        return

    def create_thread(self, etag, cut_info):
        """
        开辟多线程下载
        :param file_path: 文件存储路径
        :param etag: headers校验
        :param cut_info:
        :return:
        """

        for thread_index in range(1, self.thread_num + 1):
            thread = Thread(target=self.downloader,
                            args=(etag, thread_index, cut_info[thread_index][1], cut_info[thread_index][2]))

            thread.setName('Thread-{}'.format(thread_index))
            thread.setDaemon(True)
            thread.start()
            self.thread_list.append(thread)

        for thread in self.thread_list:
            thread.join()
        return

    def check_thread_status(self):
        """
        查询线程状态。
        :return:
        """
        while True:
            for thread in self.thread_list:
                thread_name = thread.getName()
                if not thread.isAlive():
                    print("{}:已停止".format(thread_name))
            time.sleep(1)

    def create_data(self):
        if not os.path.exists(self.data_folder):
            os.mkdir(self.data_folder)
        return

    def main(self):
        # 平分几份
        self.create_data()
        self.file_size, etag = self.get_file_size()
        # 按线程数量均匀切割下载文件
        cut_info, self.cut_size = self.cutting()
        # 下载文件名称
        # 创建下载进度条
        self.tqdm_obj = tqdm(total=self.file_size, unit_scale=True, desc=self.file_path.split('/')[-1],
                             unit_divisor=1024,
                             unit="B")
        # 开始多线程下载
        self.create_thread(etag, cut_info)
        # 合并多线程下载文件
        self.write_file()
        return


if __name__ == '__main__':
    # 将控制台print的报错结果输出到log.txt文件
    sys.stdout = Logger(r'log.txt', sys.stdout) #不希望生成log文件注释掉即可
    # sys.stderr = Logger(r'log_file.txt', sys.stderr)
    start_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    print("开始时间:"+start_time)
    print("==" * 20)
    download_url = "https://heyulei1.github.io/videos/1.mp4"
    data_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'Data')
    thread_num = 20 # 想提高速度可以提高线程数,但不要太高,这与电脑配置有关
    downloader = DownloadFile(download_url, data_folder, thread_num)
    downloader.main()
    print(download_url,'完成')
    end_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    print("==" * 20)
    print("结束时间:"+end_time+"\n")
目录
相关文章
|
3月前
|
机器学习/深度学习 开发工具 计算机视觉
视觉智能平台常见问题之视频文件较大上传时可以分段上传或者切割视频如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
|
11月前
|
存储 前端开发 定位技术
前端加载超大图片实现秒开解决方案
前端加载超大图片实现秒开解决方案
|
C#
如何解决在PotPlayer中看视频音画不同步的问题(C#视频可用)
如何解决在PotPlayer中看视频音画不同步的问题(C#视频可用)
990 0
|
3月前
|
缓存 前端开发 JavaScript
|
2月前
|
存储 缓存 前端开发
全面解析:前端超大文件下载的关键技巧与优化策略
全面解析:前端超大文件下载的关键技巧与优化策略
96 1
全面解析:前端超大文件下载的关键技巧与优化策略
|
3月前
|
Web App开发 移动开发 小程序
mPaaS常见问题之集成的uc浏览器so体积过大如何解决
mPaaS(移动平台即服务,Mobile Platform as a Service)是阿里巴巴集团提供的一套移动开发解决方案,它包含了一系列移动开发、测试、监控和运营的工具和服务。以下是mPaaS常见问题的汇总,旨在帮助开发者和企业用户解决在使用mPaaS产品过程中遇到的各种挑战
|
存储 缓存 Kubernetes
数据缓存系列分享(一):打开大模型应用的另一种方式
容器镜像的加速技术如今已经非常成熟,比如阿里云容器镜像缓存,还有p2p分发技术以及开源的dadi、nydus等按需加载技术,然而这些加速技术对于大模型文件的加载都很难有显著的效果。 MaaS的概念最近开始被提出,模型已经逐渐开始具备相对独立的存储、版本管理能力,也有类OCI的概念被提出,模型与应用的解耦会是必然的一个趋势。 为了解决模型加载与容器镜像加载解耦的问题,我们提供了模型缓存的技术,让模型无需从远端的仓库加载,也不用打包进应用的镜像里,就可以直接像加载本地的文件一样使用模型,而且在模型缓存的制作、使用流程上做了极大的简化。
1625 1
数据缓存系列分享(一):打开大模型应用的另一种方式
|
存储 缓存 Dart
如何处理直播实时在线人数显示并且最小化性能和资源消耗?
直播技术成为一种极为流行的交流方式。而直播平台的核心指标之一就是实时在线人数,准确地显示该指标对于用户和运营商来说都具有重要意义。然而,直播实时在线人数的显示也面临着性能和资源消耗的挑战。本文将介绍如何利用Flutter和Dart开发技术栈来优化直播实时在线人数的显示,以达到最小化性能和资源消耗的目标。 作者:狗头大军之江苏分军 链接:https://juejin.cn/spost/7255473856234913852 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
如何处理直播实时在线人数显示并且最小化性能和资源消耗?
[解决]:【TeamViewer作为个人用途免费,但仅可使用在有限数量的设备上。您已经到达可使用设备的上线】
[解决]:【TeamViewer作为个人用途免费,但仅可使用在有限数量的设备上。您已经到达可使用设备的上线】
207 0
|
测试技术 C# C++
C# 如何部分加载“超大”解决方案中的部分项目
在有的特有的项目环境下,团队会将所有的项目使用同一个解决方案进行管理。这种方式方面了管理,但是却会导致解决方案变得非常庞大,导致加载时间过长。那么,如何部分加载解决方案中的部分项目呢?
185 0
C# 如何部分加载“超大”解决方案中的部分项目