场景

  1. 使用Python做自动化测试时,有时候需要从网络下载软件安装包并安装。但是使用urllib库时,默认都是单线程下载文件,如果文件比较小还好说,如果文件有20M时,普通的网速就要等待很长的时间。有没有模块类似下载工具那样能多线程下载同一个文件?

  2. 如果没有多线程下载单个文件的模块,那我们应该如何编码实现功能?

说明

  1. Python作为日常的部署语言,编写自动化脚本目前看来还是比较方便的,因为它的库很多,动态语言特性灵活,自动内存管理,编码到执行都无需编译等待等。

  2. 说到这个多线程下载单个文件,在Python的使用手册里,真没发现有相关的模块做这个功能。搜索了下也没简单能用的模块。

  3. 实现多线程下载同一个静态文件(注意是静态文件,而流式文件是获取不到大小的),原理就是每个线程下载文件的不同部分(一个文件可以看成不同大小的块组成),这样每个线程执行完之后,文件就全部下载完了。多线程下载速度也不一定是快的,要看下载网站的带宽出口,如果它的带宽出口比较小,那么多线程都会比单线程快。如果像腾讯那种大厂,它的网站带宽很大,还有DDOS检测防护,比你自己的网络带宽还大,所以基本上只用一个线程就很快了。

  4. 多线程下载文件的不同部分,首先需要发送HEAD请求获取http头里的Content-Length的文件大小,之后才能根据文件大小和线程个数分成多个块,每个块有起始位置和结束位置,而每个线程只下载自己的文件块就行了。

  5. 这里说明下,访问https和支持TLS协议,需要安装额外的模块,请查看关于如何使用urllib3库和访问https的问题,总的说需要先通过pip安装pyOpenSSL, cryptography, idna, certifi模块。

pip install pyOpenSSL
pip install cryptography
pip install idna
pip install certifi
  1. 之后如果想支持命令行参数管理,可以安装click模块。
pip install click
  1. 最后就是需要设定一个下载缓存大小,我这里设置为100k。可以根据自己的网速设定,太大的话,http请求可能就会超过远程网站的返回大小导致速度很慢。之后还需要通过发送GET请求,附带请求头内容属性Range来获取文件指定范围的数据。当然如果某个线程负责下载的文件块过大,我们还需要分割为100k的子块,循环请求多次直到完成下载负责的文件块。
headers = {"Range":"bytes=%d-%d"%(start,end)}
res = self.http.request('GET',self.url,headers=headers,preload_content=False)
  1. 我这里的测试Python版本是3.7.

代码

MultiThreadDownloadFile.py


import threading
import time
import urllib3
import urllib3.contrib.pyopenssl
import certifi
import click
import randomlock = threading.Lock()
count = 0def requestFileSize(http,url):r = http.request('HEAD',url)return r.headers["Content-Length"]def test_urllib3(http,url):header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36"}response = http.request('GET', url, None, header)data = response.data.decode('utf-8') # 注意, 返回的是字节数据.需要转码.print (data) # 打印网页的内容class MulThreadDownload(threading.Thread):def __init__(self,http,url,startpos,endpos,fo):super(MulThreadDownload,self).__init__()self.url = urlself.startpos = startposself.endpos = endposself.fo = foself.http = httpdef downloadBlock(self,start,end):headers = {"Range":"bytes=%d-%d"%(start,end)}res = self.http.request('GET',self.url,headers=headers,preload_content=False)lock.acquire()self.fo.seek(start)global countcount = (count+ len(res.data))print("download total %d" % count)self.fo.write(res.data)self.fo.flush()lock.release()def download(self):print("start thread:%s at %s" % (self.getName(), time.process_time()))bufSize = 102400pos = self.startpos+bufSizewhile pos < self.endpos:time.sleep(random.random()) # 延迟 0-1s,避免被服务器识别恶意访问self.downloadBlock(self.startpos,pos)self.startpos = pos+1pos = self.startpos + bufSizeself.downloadBlock(self.startpos,self.endpos)print("stop thread:%s at %s" % (self.getName(), time.process_time()))def run(self):self.download()def createFile(filename,size):with open(filename,'wb') as f:f.seek(size-1)f.write(b'\x00')@click.command(help="""多线程下载单个静态文件,注意,目前不支持数据流文件.如果下载不了,请减少线程个数. \nMultiThreadDownloadFile.py pathUrl pathOutput""")
@click.option('--threads_num',default=2, help="线程个数")
@click.option('--url_proxy',default="", help="HTTP代理")
@click.argument('path_url',type=click.Path())
@click.argument('path_output',type=click.Path())
@click.pass_context
def runDownload(ctx,threads_num,url_proxy,path_url,path_output):print(" threadNum: %d\n urlProxy: %s\n pathUrl: %s\n PathOutput %s\n" % (threads_num,url_proxy,path_url,path_output))http = Noneif len(url_proxy) == 0:http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())else:http = urllib3.ProxyManager(url_proxy,cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())print(path_url)print(http)fileSize = int(requestFileSize(http,path_url))print(fileSize)step = fileSize // threads_nummtd_list = []createFile(path_output,fileSize)startTime = time.time()# rb+ ,二进制打开,可任意位置读写with open(path_output,'rb+') as  f:loopCount = 1start = 0while loopCount < threads_num:end = loopCount*step -1t = MulThreadDownload(http,path_url,start,end,f)t.start()mtd_list.append(t)start = end+1loopCount = loopCount+1t = MulThreadDownload(http,path_url,start,fileSize-1,f)t.start()mtd_list.append(t)for i in  mtd_list:i.join()endTime = time.time()print("Download Time: %fs" % (endTime - startTime))if __name__ == "__main__":urllib3.contrib.pyopenssl.inject_into_urllib3()random.seed()runDownload(obj = {})

下载

如何执行

python MultiThreadDownloadFile.py --help
python MultiThreadDownloadFile.py http://dldir1.qq.com/invc/tt/QTB/Wechat_QQBrowser_Setup.exe setup.exeMultiThreadDownloadFile.exe --help
MultiThreadDownloadFile.exe http://dldir1.qq.com/invc/tt/QTB/Wechat_QQBrowser_Setup.exe setup.exe

下载EXE独立文件

  1. 我使用pyinstaller打包了为单个独立的EXE文件, 如果没有Python环境的,可以从
    https://gitee.com/tobey-robot/AutomaticPython/releases下载MultiThreadDownloadFile.zip

参考

关于如何使用urllib3库和访问https的问题

urllib3库的官方说明

Python开发环境配置

Simple Multithreaded Download Manager in Python

python多线程下载文件

[Python]_[初级]_[多线程下载单个文件]相关推荐

  1. github 下载单个文件夹_从Github上下载单个文件夹的快速方式

    最近使用python需要从Github上下载文件,但官网上只能下载一个大目录下的所有文件,无法下载单个自己需要的文件夹. 网上主要的解决方式是SVN,有点麻烦没试. 尝试使用了Chrome的GitZi ...

  2. Python实现多进程/多线程同时下载单个文件

    功能描述: 使用多进程/多线程同时下载单个文件,可以自定义文件地址.进程/线程数量. 主要思路: 获取文件大小,使用多个进程/线程分别下载一部分,最后再把这些文件拼接起来. 参考代码: 运行结果: - ...

  3. 转:使用Python写一个m3u8多线程下载器

    转载:使用Python写一个m3u8多线程下载器 可去看原文:https://blog.csdn.net/muslim377287976/article/details/104340242 文章目录 ...

  4. 从GitHub存储库下载单个文件夹或目录

    如何从GitHub上托管的远程Git存储库中仅下载特定文件夹或目录? 假设示例GitHub存储库位于此处: git@github.com:foobar/Test.git 其目录结构: Test/ ├─ ...

  5. libcurl使用多线程下载大文件源码示例!

    使用libcurl多线程下载大文件的基本思想: 首选打开文件,将文件等分为指定的片段,使用http range下载,一个线程下载一个片段,当线程下载片段时,它们将数据写到打开文件的指定位置,类似BT文 ...

  6. JAVA代码实现下载单个文件,和下载打包文件

    //下载单个文件调用方法 /**     * response     * imgPath 下载图片地址     * fileName 保存下载文件名称     * @date 2015年4月14日 ...

  7. GitHub如何下载单个文件夹

    更新: 如果用 Chrome 的话,我一般用 GitZip for github 这个扩展 Github中并不提供单个文件夹下载, 每当下载仓库中某个文件夹时,只能克隆整个仓库, 浪费硬盘空间不说,浪 ...

  8. Android 开发工具类 27_多线程下载大文件

    多线程下载大文件时序图 FileDownloader.java 1 package com.wangjialin.internet.service.downloader; 2 3 import jav ...

  9. [工具库]JFileDownloader工具类——多线程下载网络文件,并保存在本地

    本人大四即将毕业的准程序员(JavaSE.JavaEE.android等)一枚,小项目也做过一点,于是乎一时兴起就写了一些工具. 我会在本博客中陆续发布一些平时可能会用到的工具. 代码质量可能不是很好 ...

最新文章

  1. 贵阳市全国首部大数据立法《条例》5月起正式实施
  2. lua table.sort的bug
  3. python培训中心-吴中区Python培训中心
  4. [转]文件浏览直接显示[兼容IE,FireFox]
  5. python手把手入门_新手必看:手把手教你入门 Python
  6. 一个json格式转xml格式的java实现
  7. python矩阵输入_Python基础之矩阵输入
  8. princomp 与pca的区别与联系
  9. [转]Ubuntu terminator 无法打开解决方案
  10. Android Bitmap Drawable 常用摘要
  11. http协议与php关系,HTTP协议的由来
  12. mysql分页查询sql语句_mysql 分页查询的sql语句
  13. extjs中的flex_Extjs 教程
  14. HTML5 CSS3 专题 :诱人的实例 3D旋转木马效果相冊
  15. 基于BINN算法的CCPP全路径覆盖算法
  16. pta一元多项式求导
  17. node : 无法将“node”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
  18. Android 第五章 TextView
  19. unity与php的交互-图片上传下载
  20. 外卖订单详情界面android,小程序外卖订单界面

热门文章

  1. win系统录音设备无法正常录音解决方案之一
  2. MATLAB | 三个趣的圆相关的数理性质可视化
  3. 找不到ld-linux.so.3,usr/bin/ld: cannot find 错误解决方法和 /etc/ld.so.conf
  4. JavaScript经典进阶:javascript – 9宫格 – 拼图
  5. lammps案例:聚乙烯/石墨烯侧面pull out模拟案例
  6. linux手写数字识别,mnist手写数字识别与图片预处理
  7. Outlook Express邮件丢失问题解决方法之一
  8. 如何解决MathType公式显示方框
  9. 如何正确的从零开始学英语
  10. chrome拓展以及油猴脚本推荐