python多线程下载文件
看到一篇多線程下載的文章,這里把自己的理解寫一篇多線程下載的文章。
?
我們訪問http://192.168.10.7/a.jpg時是get請求,response的head包含Content-Length: 37694
這個就是a.jpg文件的大小
抓包的話,server端是發送多個數據包(PDU)和一個文件信息,然后拼裝成了a.jpg圖片:
,部分截圖。
如果我用requests.head("http://192.168.10.7/a.jpg")時,server端只返回文件信息,而不會發送文件數據。
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字節
保存a.jpg文件后查看文件大小也是
?
好了,我們知道文件大小了的話,那如何多線程下載了?
假如我們用3個線程去下載a.jpg,那么我們會用線程1去下載1260x10=12600字節,線程2下載12601-25200字節,以此類推,還不夠就用線程1再去下載。
但是get請求不是會直接下載a.jpg文件了?怎么只獲取一部分文件的數據了?
我們可以在get請求的head部分加入“Range: bytes=0-12599”, 先測試下
# res.text 是將get獲取的byte類型數據自動編碼,是str類型, res.content是原始的byte類型數據# 所以下面是直接write(res.content) headers = {"Range":"bytes=0-12599"}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數據自動編碼,是str類型, res.content是原始的byte類型數據# 所以下面是直接write(res.content)with open(self.filename,'wb') as f:f.write(res.content)
然后可以看到下載獲取的一部分圖片:
我們再獲取下一部分數據,
headers = {"Range":"bytes=12600-25199"}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數據自動編碼,是str類型, res.content是原始的byte類型數據# 所以下面是直接write(res.content)with open(self.filename,'ab+') as f:print(f.tell())f.write(res.content)可以看到文件:
我們知道:
r或rt 默認模式,文本模式讀 rb 二進制文件w或wt 文本模式寫,打開前文件存儲被清空 wb 二進制寫,文件存儲同樣被清空a 追加模式,只能寫在文件末尾 a+ 可讀寫模式,寫只能寫在文件末尾w+ 可讀寫,與a+的區別是要清空文件內容 r+ 可讀寫,與a+的區別是可以寫到文件任何位置如果是多線程的而下載的話,我們用open('file','rb+'),我先用這種模式繼續上面下載文件,上面下載到了25199字節,
那這次我從26000開始下載,f.seek(26000)后開始保存下載的文件,看文件是否能保存,看到的文件是否會中間出現空白:
headers = {"Range":"bytes=26000-37694"}res = requests.get(self.url,headers=headers)# res.text 是將get獲取的byte類型數據自動編碼,是str類型, res.content是原始的byte類型數據# 所以下面是直接write(res.content)with open(self.filename,'rb+') as f:f.seek(26000)f.write(res.content)下載后的文件:
這個,可能圖片顯示可能跟我們想象的不一樣,但是rb+肯定是可以從任意位置讀寫的。
還介紹一個知識點,可能在自己測試的時候用的到,就是:
?f.truncate(n):? 從文件的首行首字符開始截斷,截斷文件為n個字符;無n表示從當前位置起截斷;截斷之后n后面的所有字符被刪除。
?好了,現在我們開始使用多線程下載文件:
設計思路是:
1、每個線程下載一部分數據
2、每個線程用rb+模式打開文件
3、每個線程下載數據后,用f.seek()到相應的位置,然后再寫數據。
直接f=open(),再多線程f.write()時會出現文件寫錯誤。
我們可以用os.dup()復制文件符合os.fsopen(fd,mode,buffer)來打開處理文件。
os.dup()和os.fdopen()的好處個人理解是os.dup()復制文件句柄,os.fdopen()先寫緩存,具體官方文檔還有待查證。
代碼:
版本 python3,
pip install requests
下面代碼可以拿來直接跑
#! -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類型數據自動編碼,是str類型, res.content是原始的byte類型數據# 所以下面是直接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))#線程數threadnum = 3#信號量,同時只允許3個線程運行 threading.BoundedSemaphore(threadnum)# 默認3線程現在,也可以通過傳參的方式設置線程數step = filesize // threadnummtd_list = []start = 0end = -1# 請空并生成文件tempf = open(filename,'w')tempf.close()# rb+ ,二進制打開,可任意位置讀寫with open(filename,'rb+') as f:fileno = f.fileno()# 如果文件大小為11字節,那就是獲取文件0-10的位置的數據。如果end = 10,說明數據已經獲取完了。while end < filesize -1:start = end +1end = start + step -1if end > filesize:end = filesize# print("start:%s, end:%s"%(start,end))# 復制文件句柄dup = os.dup(fileno)# print(dup)# 打開文件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()執行結果:
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多次測試,下載后的文件都可以正常打開。
如果有多個站點有of.tar.gz文件,那更可以體現多線程下載的體驗。
根據上面的理論,我們應該可以做一個類似p2p的下載,比如10臺機器,每臺啟動一個agent,每個agent給server上報自己目錄下的文件信息,當有一個agent有下載文件時,會去server查詢哪些agent有這個文件,然后計算去哪些agent下載哪段數據。
?
轉載于:https://www.cnblogs.com/owasp/p/6413480.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的python多线程下载文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PDF编辑工具
- 下一篇: git——^和~的区别(转)