python多线程下载文件
看到一篇多線程下載的文章,這里把自己的理解寫(xiě)一篇多線程下載的文章。
?
我們?cè)L問(wèn)http://192.168.10.7/a.jpg時(shí)是get請(qǐng)求,response的head包含Content-Length: 37694
這個(gè)就是a.jpg文件的大小
抓包的話,server端是發(fā)送多個(gè)數(shù)據(jù)包(PDU)和一個(gè)文件信息,然后拼裝成了a.jpg圖片:
,部分截圖。
如果我用requests.head("http://192.168.10.7/a.jpg")時(shí),server端只返回文件信息,而不會(huì)發(fā)送文件數(shù)據(jù)。
response = requests.head(self.url)print(response.headers)# {'Keep-Alive': 'timeout=5, max=100', 'Accept-Ranges': 'bytes', 'Date': 'Sat, 18 Feb 2017 02:56:08 GMT', 'ETag': '"933e-548c4b0beff53"', 'Content-Type': 'image/jpeg', 'Content-Length': '37694', 'Last-Modified': 'S at, 18 Feb 2017 02:21:39 GMT', 'Connection': 'Keep-Alive', 'Server': 'Apache/2.4.18 (Ubuntu)'}文件a.jpg大小是37964字節(jié)
保存a.jpg文件后查看文件大小也是
?
好了,我們知道文件大小了的話,那如何多線程下載了?
假如我們用3個(gè)線程去下載a.jpg,那么我們會(huì)用線程1去下載1260x10=12600字節(jié),線程2下載12601-25200字節(jié),以此類推,還不夠就用線程1再去下載。
但是get請(qǐng)求不是會(huì)直接下載a.jpg文件了?怎么只獲取一部分文件的數(shù)據(jù)了?
我們可以在get請(qǐng)求的head部分加入“Range: bytes=0-12599”, 先測(cè)試下
# res.text 是將get獲取的byte類型數(shù)據(jù)自動(dòng)編碼,是str類型, res.content是原始的byte類型數(shù)據(jù)# 所以下面是直接write(res.content) headers = {"Range":"bytes=0-12599"}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數(shù)據(jù)自動(dòng)編碼,是str類型, res.content是原始的byte類型數(shù)據(jù)# 所以下面是直接write(res.content)with open(self.filename,'wb') as f:f.write(res.content)
然后可以看到下載獲取的一部分圖片:
我們?cè)佾@取下一部分?jǐn)?shù)據(jù),
headers = {"Range":"bytes=12600-25199"}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數(shù)據(jù)自動(dòng)編碼,是str類型, res.content是原始的byte類型數(shù)據(jù)# 所以下面是直接write(res.content)with open(self.filename,'ab+') as f:print(f.tell())f.write(res.content)可以看到文件:
我們知道:
r或rt 默認(rèn)模式,文本模式讀 rb 二進(jìn)制文件w或wt 文本模式寫(xiě),打開(kāi)前文件存儲(chǔ)被清空 wb 二進(jìn)制寫(xiě),文件存儲(chǔ)同樣被清空a 追加模式,只能寫(xiě)在文件末尾 a+ 可讀寫(xiě)模式,寫(xiě)只能寫(xiě)在文件末尾w+ 可讀寫(xiě),與a+的區(qū)別是要清空文件內(nèi)容 r+ 可讀寫(xiě),與a+的區(qū)別是可以寫(xiě)到文件任何位置如果是多線程的而下載的話,我們用open('file','rb+'),我先用這種模式繼續(xù)上面下載文件,上面下載到了25199字節(jié),
那這次我從26000開(kāi)始下載,f.seek(26000)后開(kāi)始保存下載的文件,看文件是否能保存,看到的文件是否會(huì)中間出現(xiàn)空白:
headers = {"Range":"bytes=26000-37694"}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數(shù)據(jù)自動(dòng)編碼,是str類型, res.content是原始的byte類型數(shù)據(jù)# 所以下面是直接write(res.content)with open(self.filename,'rb+') as f:f.seek(26000)f.write(res.content)下載后的文件:
這個(gè),可能圖片顯示可能跟我們想象的不一樣,但是rb+肯定是可以從任意位置讀寫(xiě)的。
還介紹一個(gè)知識(shí)點(diǎn),可能在自己測(cè)試的時(shí)候用的到,就是:
?f.truncate(n):? 從文件的首行首字符開(kāi)始截?cái)?#xff0c;截?cái)辔募閚個(gè)字符;無(wú)n表示從當(dāng)前位置起截?cái)?#xff1b;截?cái)嘀髇后面的所有字符被刪除。
?好了,現(xiàn)在我們開(kāi)始使用多線程下載文件:
設(shè)計(jì)思路是:
1、每個(gè)線程下載一部分?jǐn)?shù)據(jù)
2、每個(gè)線程用rb+模式打開(kāi)文件
3、每個(gè)線程下載數(shù)據(jù)后,用f.seek()到相應(yīng)的位置,然后再寫(xiě)數(shù)據(jù)。
直接f=open(),再多線程f.write()時(shí)會(huì)出現(xiàn)文件寫(xiě)錯(cuò)誤。
我們可以用os.dup()復(fù)制文件符合os.fsopen(fd,mode,buffer)來(lái)打開(kāi)處理文件。
os.dup()和os.fdopen()的好處個(gè)人理解是os.dup()復(fù)制文件句柄,os.fdopen()先寫(xiě)緩存,具體官方文檔還有待查證。
代碼:
版本 python3,
pip install requests
下面代碼可以拿來(lái)直接跑
#! -coding:utf8 -*- import threading,sys import requests import time import osclass MulThreadDownload(threading.Thread):def __init__(self,url,startpos,endpos,f):super(MulThreadDownload,self).__init__()self.url = urlself.startpos = startposself.endpos = endposself.fd = fdef download(self):print("start thread:%s at %s" % (self.getName(), time.time()))headers = {"Range":"bytes=%s-%s"%(self.startpos,self.endpos)}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數(shù)據(jù)自動(dòng)編碼,是str類型, res.content是原始的byte類型數(shù)據(jù)# 所以下面是直接write(res.content) self.fd.seek(self.startpos)self.fd.write(res.content)print("stop thread:%s at %s" % (self.getName(), time.time()))# f.close()def run(self):self.download()if __name__ == "__main__":url = sys.argv[1]#獲取文件的大小和文件名filename = url.split('/')[-1]filesize = int(requests.head(url).headers['Content-Length'])print("%s filesize:%s"%(filename,filesize))#線程數(shù)threadnum = 3#信號(hào)量,同時(shí)只允許3個(gè)線程運(yùn)行 threading.BoundedSemaphore(threadnum)# 默認(rèn)3線程現(xiàn)在,也可以通過(guò)傳參的方式設(shè)置線程數(shù)step = filesize // threadnummtd_list = []start = 0end = -1# 請(qǐng)空并生成文件tempf = open(filename,'w')tempf.close()# rb+ ,二進(jìn)制打開(kāi),可任意位置讀寫(xiě)with open(filename,'rb+') as f:fileno = f.fileno()# 如果文件大小為11字節(jié),那就是獲取文件0-10的位置的數(shù)據(jù)。如果end = 10,說(shuō)明數(shù)據(jù)已經(jīng)獲取完了。while end < filesize -1:start = end +1end = start + step -1if end > filesize:end = filesize# print("start:%s, end:%s"%(start,end))# 復(fù)制文件句柄dup = os.dup(fileno)# print(dup)# 打開(kāi)文件fd = os.fdopen(dup,'rb+',-1)# print(fd)t = MulThreadDownload(url,start,end,fd)t.start()mtd_list.append(t)for i in mtd_list:i.join()執(zhí)行結(jié)果:
python multiprocess_download.py http://192.168.10.7/of.tar.gz of.tar.gz filesize:36578022 start thread:Thread-1 at 1487405833.7353075 start thread:Thread-2 at 1487405833.736311 start thread:Thread-3 at 1487405833.7378094 stop thread:Thread-1 at 1487405836.9561603 stop thread:Thread-3 at 1487405837.0016065 stop thread:Thread-2 at 1487405837.0116146多次測(cè)試,下載后的文件都可以正常打開(kāi)。
如果有多個(gè)站點(diǎn)有of.tar.gz文件,那更可以體現(xiàn)多線程下載的體驗(yàn)。
根據(jù)上面的理論,我們應(yīng)該可以做一個(gè)類似p2p的下載,比如10臺(tái)機(jī)器,每臺(tái)啟動(dòng)一個(gè)agent,每個(gè)agent給server上報(bào)自己目錄下的文件信息,當(dāng)有一個(gè)agent有下載文件時(shí),會(huì)去server查詢哪些agent有這個(gè)文件,然后計(jì)算去哪些agent下載哪段數(shù)據(jù)。
?
轉(zhuǎn)載于:https://www.cnblogs.com/owasp/p/6413480.html
與50位技術(shù)專家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的python多线程下载文件的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: PDF编辑工具
- 下一篇: python学习之正则表达式练习:编写一