今天群里看到有人问关于python多线程写文件的问题,联想到这是reboot的架构师班的入学题,我想了一下,感觉坑和考察的点还挺多,可以当成一个面试题来问,简单说一下我的想法和思路吧,涉及的代码和注释在github 跪求star

本文需要一定的python基础,希望大家对下面几个知识点有所了解

  1. python文件处理,open write
  2. 简单了解http协议头信息
  3. ossys模块
  4. threading模块多进程
  5. requests模块发请求

题目既然是多线程下载,首先要解决的就是下载问题,为了方便测试,我们先不用QQ安装包这么大的,直接用pc大大英明神武又很内涵的头像举例,大概是这个样子(http://51reboot.com/src/blogimg/pc.jpg)

下载

python的requests模块很好的封装了http请求,我们选择用它来发送http的get请求,然后写入本地文件即可(关于requests和http,以及python处理文件的具体介绍,可以百度或者持续关注,后面我会写),思路既然清楚了,代码就呼之欲出了

  1. # 简单粗暴的下载
  2. import requests
  3. res=requests.get('http://51reboot.com/src/blogimg/pc.jpg')
  4. with open('pc.jpg','w') as f:
  5. f.write(res.content)

运行完上面的代码,文件夹下面多了个pc.jpg 就是你想要的图片了

上面代码功能太少了,注意 ,我们的要求是多线程下载,这种简单粗暴的下载完全不符合要求,所谓多线程,你可以理解为仓库里有很多很多袋奥利奥饼干,老板让我去都搬到公司来放好,而且要按照原顺序放好

上面的代码,大概就是我一个人去仓库,把所有奥利奥一次性拿回来,大概流程如下

我们如果要完成题目多线程的要求,首先就要把任务拆解,拆成几个子任务,子任务之间可以并行执行,并且执行结果可以汇总成最终结果

拆解任务

为了完成这个任务,我们首先要知道数据到底有多大,然后把数据分块去取就OK啦,我们要对http协议有一个很好的了解

  • 用head方法请求数据,返回只有http头信息,没有主题部分

    • 我们从头信息Content-length的值,知道资源的大小,比如有50字节
  • 比如我们要分四个线程,每个线程去取大概1/4即可
    • 50/4=12,所以前几个线程每人取12个字节,最后一个现成取剩下的即可
  • 每个线程取到相应的内容,文件中seek到相应的位置再写入即可
    • file.seek
  • 为了方便理解,一开始我们先用单线程的跑通 流程图大概如下

思路清晰了,代码也就呼之欲出了,我们先测试一下range头信息

http头信息中的Range信息,用于请求头中,指定第一个字节的位置和最后一个字节的位置,如1-12,如果省略第二个数,就认为取到最后,比如36-


  1. # range测试代码
  2. import requests
  3. # http头信息,指定获取前15000个字节
  4. headers={'Range':'Bytes=0-15000','Accept-Encoding':'*'}
  5. res=requests.get('http://51reboot.com/src/blogimg/pc.jpg',headers=headers)
  6. with open('pc.jpg','w') as f:
  7. f.write(res.content)

我们得到了头像的前15000个字节,如下图,目测range是对的

继续丰富我们的代码

  • 要先用requests.head方法去获取数据的长度
  • 确认开几个线程后,给每个线程确认要获取的数据区间,即Range字段的值
  • seek写文件
  • 功能比较复杂了,我们需要用面向对象来组织一下代码
  • 先写单线程,逐步优化
  • 代码呼之欲出了
  1. import requests
  2. # 下载器的类
  3. class downloader:
  4. # 构造函数
  5. def __init__(self):
  6. # 要下载的数据连接
  7. self.url='http://51reboot.com/src/blogimg/pc.jpg'
  8. # 要开的线程数
  9. self.num=8
  10. # 存储文件的名字,从url最后面取
  11. self.name=self.url.split('/')[-1]
  12. # head方法去请求url
  13. r = requests.head(self.url)
  14. # headers中取出数据的长度
  15. self.total = int(r.headers['Content-Length'])
  16. print type('total is %s' % (self.total))
  17. def get_range(self):
  18. ranges=[]
  19. # 比如total是50,线程数是4个。offset就是12
  20. offset = int(self.total/self.num)
  21. for i in range(self.num):
  22. if i==self.num-1:
  23. # 最后一个线程,不指定结束位置,取到最后
  24. ranges.append((i*offset,''))
  25. else:
  26. # 没个线程取得区间
  27. ranges.append((i*offset,(i+1)*offset))
  28. # range大概是[(0,12),(12,24),(25,36),(36,'')]
  29. return ranges
  30. def run(self):
  31. f = open(self.name,'w')
  32. for ran in self.get_range():
  33. # 拼出Range参数 获取分片数据
  34. r = requests.get(self.url,headers={'Range':'Bytes=%s-%s' % ran,'Accept-Encoding':'*'})
  35. # seek到相应位置
  36. f.seek(ran[0])
  37. # 写数据
  38. f.write(r.content)
  39. f.close()
  40. if __name__=='__main__':
  41. down = downloader()
  42. down.run()

多线程

多线程和多进程是啥在这就不多说了,要说明白还得专门写个文章,大家知道threading模块是专门解决多线程的问题就OK了,大概的使用方法如下,更详细的请百度或者关注后续文章

  • threading.Thread创建线程,设置处理函数
  • start启动
  • setDaemon 设置守护进程
  • join设置线程等待
  • 代码如下
  1. import requests
  2. import threading
  3. class downloader:
  4. def __init__(self):
  5. self.url='http://51reboot.com/src/blogimg/pc.jpg'
  6. self.num=8
  7. self.name=self.url.split('/')[-1]
  8. r = requests.head(self.url)
  9. self.total = int(r.headers['Content-Length'])
  10. print 'total is %s' % (self.total)
  11. def get_range(self):
  12. ranges=[]
  13. offset = int(self.total/self.num)
  14. for i in range(self.num):
  15. if i==self.num-1:
  16. ranges.append((i*offset,''))
  17. else:
  18. ranges.append((i*offset,(i+1)*offset))
  19. return ranges
  20. def download(self,start,end):
  21. headers={'Range':'Bytes=%s-%s' % (start,end),'Accept-Encoding':'*'}
  22. res = requests.get(self.url,headers=headers)
  23. self.fd.seek(start)
  24. self.fd.write(res.content)
  25. def run(self):
  26. self.fd = open(self.name,'w')
  27. thread_list = []
  28. n = 0
  29. for ran in self.get_range():
  30. start,end = ran
  31. print 'thread %d start:%s,end:%s'%(n,start,end)
  32. n+=1
  33. thread = threading.Thread(target=self.download,args=(start,end))
  34. thread.start()
  35. thread_list.append(thread)
  36. for i in thread_list:
  37. i.join()
  38. print 'download %s load success'%(self.name)
  39. self.fd.close()
  40. if __name__=='__main__':
  41. down = downloader()
  42. down.run()

执行python downloader效果如下


  1. total is 21520
  2. thread 0 start:0,end:2690
  3. thread 1 start:2690,end:5380
  4. thread 2 start:5380,end:8070
  5. thread 3 start:8070,end:10760
  6. thread 4 start:10760,end:13450
  7. thread 5 start:13450,end:16140
  8. thread 6 start:16140,end:18830
  9. thread 7 start:18830,end:
  10. download pc.jpg load success

run函数做了修改,加了多线程的东西,加了一个download函数专门用来下载数据块,这俩函数详细解释如下


  1. def download(self,start,end):
  2. #拼接Range字段,accept字段支持所有编码
  3. headers={'Range':'Bytes=%s-%s' % (start,end),'Accept-Encoding':'*'}
  4. res = requests.get(self.url,headers=headers)
  5. #seek到start位置
  6. self.fd.seek(start)
  7. self.fd.write(res.content)
  8. def run(self):
  9. # 保存文件打开对象
  10. self.fd = open(self.name,'w')
  11. thread_list = []
  12. #一个数字,用来标记打印每个线程
  13. n = 0
  14. for ran in self.get_range():
  15. start,end = ran
  16. #打印信息
  17. print 'thread %d start:%s,end:%s'%(n,start,end)
  18. n+=1
  19. #创建线程 传参,处理函数为download
  20. thread = threading.Thread(target=self.download,args=(start,end))
  21. #启动
  22. thread.start()
  23. thread_list.append(thread)
  24. for i in thread_list:
  25. # 设置等待
  26. i.join()
  27. print 'download %s load success'%(self.name)
  28. #关闭文件
  29. self.fd.close()

持续可以优化的点

  • 一个文件描述符多个进程用会出问题

    • 建议用os.dup复制文件描述符和os.fdopen来打开处理文件
  • 要下载的资源地址和线程数,应该做成命令行传进来的
    • 用sys.argv获取命令行参数
    • 支持python downloader.py url num这种写法
    • 参数数量不对或者格式不对时报错
  • 各种容错处理
  • 正所谓女人的迪奥,男人的奥利奥,这篇文章,你值得拥有

大概就是这样了,我也是正在学习python,文章代表我个人看法,有错误不可避免,欢迎大家指正,共同学习,本文完整代码在github,跪求大家star

python10min系列之多线程下载器的更多相关文章

  1. <基于Qt与POSIX线程>多线程下载器的简易搭建

    原创博客,转载请联系博主! 本项目已托管到本人Git远程库:https://github.com/yue9944882/Snow 项目目标  Major Functionality 开发环境:  Ce ...

  2. 06-python进阶-多线程下载器练手

    我们需要用python 写一个多线程的下载器 我们要先获取这个文件的大小 然后将其分片 然后启动多线程 分别去下载 然后将其拼接起来 #!/usr/bin/env python#coding:utf- ...

  3. Java多线程下载器FileDownloader(支持断点续传、代理等功能)

    前言 在我的任务清单中,很早就有了一个文件下载器,但一直忙着没空去写.最近刚好放假,便抽了些时间完成了下文中的这个下载器. 介绍 同样的,还是先上效果图吧. Jar包地址位于 FileDownload ...

  4. Android版多线程下载器核心代码分享

    首先给大家分享多线程下载核心类: package com.example.urltest; import java.io.IOException; import java.io.InputStream ...

  5. java编写的Http协议的多线程下载器

    断点下载器还在实现中...... //////////////////////////////////界面/////////////////////////////////////////// pac ...

  6. Ubuntu下的图形化多线程下载器XDM

    目录 1.下载 2.安装 3.浏览器支持 使用Ubuntu下载东西经常过于缓慢,因此需要多进程下载器. 1.下载 下载链接:http://xdman.sourceforge.net/#download ...

  7. Linux下的多线程下载工具mwget

    之前在做项目的时候,遇到一个难题,需要一个多线程下载器,于是阴差阳错的看到了这款工具--mwget,之所以是阴差阳错,是因为mwget的多线程下载功能,并不是我们想要的多线程. wget大家都知道吧, ...

  8. Chrome开启多线程下载

    Chrome多线程下载也和标签页预览一样属于Google测试中的功能,可通过在地址栏输入chrome://flags/,然后在搜索框中输入Parallel downloading,选择enabled, ...

  9. Http系列:断点续传与多线程下载

    前言 当下载电影时,我常常会想中断下载后,为什么点击开始时会在中断的地方继续下载呢?又或者在看在线电影时,为什么可以按着播放条拖动就能看到想看的片段呢? http的range请求将解决以上困惑. 多线 ...

随机推荐

  1. freemaker

    FreeMarker模板文件主要由如下4个部分组成:  1,文本:直接输出的部分  2,注释:<#-- ... -->格式部分,不会输出  3,插值:即${...}或#{...}格式的部分 ...

  2. JMS详细的工作原理【转】

    如果手机只能进行实时通话,没有留言和短信功能会怎么样?一个电话打过来,正好没有来得及接上,那么这个电话要传递的信息肯定就收不到了.为什么不能先将信息存下来,当用户需要查看信息的时候再去获得信息呢?伴随 ...

  3. JS中小数的差,比较大小

    var a = 0.3-0.2; -0.3; alert(a + "&" + b); if (a == b) { alert("true"); } el ...

  4. 风行一时瀑布流网页布局,实现无限加载(jquery)

    今天跟大家分享一个瀑布流网页布局,先跟大家分析下实现的思路吧 主要思路:一.根据屏幕宽度和单个浮动层的宽度来确定浮动层列数 var $boxs = $("#main>div" ...

  5. 中国 省会 地级市 经纬度 city array

    <?php $city_arr = array ( '北京' => array ( 'gis_lng' => '116.405285', 'gis_lat' => '39.90 ...

  6. iOS开发的准备

    一.程序设计语言 上一讲已经说到:要想开发一款软件,首先得学习一些相应的程序设计语言.至于iOS开发,需要学习的语言主要有:C.C++.Objective-C. 二.是否需要计算机专业知识 可能很多人 ...

  7. JSP打印九九乘法表

    ##index.jsp: <%@ page language="java" import="java.util.*" pageEncoding=" ...

  8. Azure SQL 数据库引入了新的服务级别

     新的级别将提升客户体验,并提供更多的业务连续性选项 为了更好地满足您在灵活性提升方面的需求,MicrosoftAzure SQL 数据库添加了新的服务级别(基础级和标准级),以与当前处于预览状态 ...

  9. 《Linux命令行与shell脚本编程大全》 第十五章 学习笔记

    第十五章:控制脚本 处理信号 重温Linux信号 信号 名称 描述 1 HUP 挂起 2 INT 中断 3 QUIT 结束运行 9 KILL 无条件终止 11 SEGV 段错误 15 TERM 尽可能 ...

  10. MyEclipse13中修改Servlet.java源代码

    Servlet.java源代码想要修改的步骤,与低版本的不同废话少说,直接来步骤: 1,在myEclipse的安装目录中搜索com.genuitec.eclipse.wizards文件,如图:选择co ...