摘要:圖片下載屬于操作,比較耗時,基于此,可以利用中的多線程將其實現(xiàn)。更多精彩滾雪球?qū)W完結(jié)滾雪球?qū)W第二輪完結(jié)滾雪球?qū)W第三輪滾雪球?qū)W番外篇完結(jié)
在 python 編碼過程中,有時存在這樣的一個需求,同時下載 N 張圖片,并且要快。
一般這樣的需求,只需要編寫一個 for 循環(huán)即可實現(xiàn),但是加上 快 這個要求,就不好實現(xiàn)了。
圖片下載屬于 I/O 操作,比較耗時,基于此,可以利用 python 中的多線程將其實現(xiàn)。
為了不讓大家學(xué)的太困倦,特意找來 6 張美麗的圖片,本次學(xué)習(xí)將圍繞這幾張圖片進(jìn)行。
https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpghttps://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg
使用 for 循環(huán),同步代碼如下所示:
import timeimport requestsurls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]# 文件保存路徑SAVE_DIR = "./928/"def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": # 下載開始時間 start_time = time.perf_counter() for url in urls: save_img(url) print("下載 6 張圖片消耗時間為:", time.perf_counter() - start_time) # 下載 6 張圖片消耗時間為: 1.911142665
接下來使用 concurrent.futures 模塊
實現(xiàn)對 6 張圖片的下載,這個模塊實現(xiàn)了 ThreadPoolExecutor
類和 ProcessPoolExecutor
類,都繼承自Executor
,分別被用來創(chuàng)建 線程池 和 進(jìn)程池,接受 max_workers
參數(shù),代表創(chuàng)建的線程數(shù)或者進(jìn)程數(shù)。
這兩個類可以在不同的線程或進(jìn)程中執(zhí)行 可調(diào)用對象,ProcessPoolExecutor
的 max_workers
參數(shù)可以為空,程序會自動創(chuàng)建與電腦 CPU
數(shù)目相同的進(jìn)程數(shù)。
使用 ThreadPoolExecutor
實現(xiàn)多線程下載
import timeimport requestsfrom concurrent import futuresMAX_WORKERS = 20 # 最大線程數(shù)SAVE_DIR = "./928/" # 文件保存路徑urls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": start_time = time.perf_counter() # 下載開始時間 with futures.ThreadPoolExecutor(MAX_WORKERS) as executor: res = executor.map(save_img, urls) # executor.map() 方法會返回一個生成器,后續(xù)代碼可以迭代獲取每個線程的執(zhí)行結(jié)果 print("下載 6 張圖片消耗時間為:", time.perf_counter() - start_time) # 下載 6 張圖片消耗時間為: 0.415939759
當(dāng)使用多線程代碼之后,時間從單線程的 1.9s
變?yōu)榱硕嗑€程的 0.4s
,能看到效率的提升。
在上述多線程代碼中,使用了 concurrent
庫中的 future
對象,該對象是 Future
類的對象,它的實力表示可能已經(jīng)完成或尚未完成的 延遲計算,該類具備 done()
方法,返回調(diào)用對象是否已經(jīng)執(zhí)行,有該方法的同時還具備一個 add_done_callback()
方法,表示調(diào)用對象執(zhí)行完畢的回調(diào)函數(shù)。
from concurrent.futures import ThreadPoolExecutordef print_name(): return "橡皮擦"def say_hello(obj): """可調(diào)用對象執(zhí)行完畢,綁定的回調(diào)函數(shù)""" w_name = obj.result() s = w_name + "你好" print(s) return swith ThreadPoolExecutor(1) as executor: executor.submit(print_name).add_done_callback(say_hello)
在上述代碼中用到了如下知識點:
executor.map()
:該方法類似 map
函數(shù),原型為 map(func, *iterables, timeout=None, chunksize=1)
,異步執(zhí)行 func
,并支持多次并發(fā)調(diào)用;executor.submit()
:方法原型為 submit(fn, *args, **kwargs)
,安排可調(diào)用對象 fn
以 fn(*args, **kwargs)
的形式執(zhí)行,并返回 Future
對象來表示它的執(zhí)行結(jié)果,該方法只能進(jìn)行單個任務(wù),如果需要并發(fā)多個任務(wù),需要使用 map
或者 as_completed
;future對象.result()
:返回調(diào)用返回的值,有個等待時間參數(shù) timeout
可以設(shè)置;future對象add_done_callback()
:該方法中綁定的回調(diào)函數(shù)在 future
取消或者完成后運行,表示 future
本身as_completed() 方法
該方法參數(shù)是一個 Future
列表,返回值是一個 Future
組成的生成器,在調(diào)用 as_completed()
方法時不會阻塞,只有當(dāng)對迭代器進(jìn)行循環(huán)時,每調(diào)用一次 next()
方法,如果當(dāng)前 Future
對象還未完成,則會阻塞。
修改下載 6 張圖片的代碼,使用 as_completed()
進(jìn)行實現(xiàn),最后得到的時間優(yōu)于單線程,但如果對結(jié)果進(jìn)行迭代,調(diào)用 result()
方法,則時間會加長。
import timeimport requestsfrom concurrent.futures import ThreadPoolExecutor, as_completedMAX_WORKERS = 20 # 最大線程數(shù)SAVE_DIR = "./928/" # 文件保存路徑urls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": start_time = time.perf_counter() # 下載開始時間 with ThreadPoolExecutor(MAX_WORKERS) as executor: tasks = [executor.submit(save_img, url) for url in urls] # 去除下部分代碼,時間基本與 map 一致。 for future in as_completed(tasks): print(future.result()) print("下載 6 張圖片消耗時間為:", time.perf_counter() - start_time) # 下載 6 張圖片消耗時間為: 0.840261401
wait
方法
wait
方法可以讓主線程阻塞,直到滿足設(shè)定的要求,該要求為 return_when
參數(shù),其值有 ALL_COMPLETED
,FIRST_COMPLETED
,FIRST_EXCEPTION
。
import timeimport requestsfrom concurrent.futures import ThreadPoolExecutor, as_completed, wait, ALL_COMPLETED, FIRST_COMPLETEDMAX_WORKERS = 20 # 最大線程數(shù)SAVE_DIR = "./928/" # 文件保存路徑urls = [ "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-004.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-012.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-013.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-016.jpg", "https://img-pre.ivsky.com/img/tupian/pre/202102/21/oumei_meinv-010.jpg"]def save_img(url): res = requests.get(url) with open(F"{SAVE_DIR}{time.time()}.jpg", "wb+") as f: f.write(res.content)if __name__ == "__main__": start_time = time.perf_counter() # 下載開始時間 with ThreadPoolExecutor(MAX_WORKERS) as executor: tasks = [executor.submit(save_img, url) for url in urls] wait(tasks, return_when=ALL_COMPLETED) print("程序運行完畢") print("下載 6 張圖片消耗時間為:", time.perf_counter() - start_time) # 下載 6 張圖片消耗時間為: 0.48876672
最后一句:ProcessPoolExecutor
的用法與 ThreadPoolExecutor
的用法基本一致,所以可以互通。
以上內(nèi)容就是本文的全部內(nèi)容,希望對學(xué)習(xí)路上的你有所幫助~
今天是持續(xù)寫作的第 235 / 365 天。
期待 關(guān)注,點贊、評論、收藏。
更多精彩
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/122205.html
摘要:的安裝博客補充知識年最新安裝教程,滾雪球?qū)W第四季。操作操作數(shù)據(jù)庫一般被程序員成為操作增刪改查,其中各個字符分別代表新增,讀取,更新,刪除。可以返回受影響行數(shù),可以直接通過該值判斷是否修改成功。 ...
摘要:看起來好像是廢話,它還有一個補充的說明,在函數(shù)式編程中要避免狀態(tài)變化和使用可變對象。函數(shù)式編程的特點在中,函數(shù)即對象,例如聲明一個函數(shù)之后,你可以調(diào)用其屬性。 ...
摘要:在流程控制中,你將同步學(xué)到關(guān)系運算符與邏輯運算符。關(guān)系運算符在中關(guān)系運算符其實就是比大小的概念,所以要學(xué)習(xí)的就是大于小于等于等內(nèi)容。邏輯運算符邏輯運算符在中有個,分別是。含有邏輯運算符的式子,最終返回的結(jié)果也是布爾值。 滾雪球?qū)W Python,目標(biāo)就是讓 Python 學(xué)起來之后,越滾越大。三、無轉(zhuǎn)折不編程如果...
摘要:時間永遠(yuǎn)都過得那么快,一晃從年注冊,到現(xiàn)在已經(jīng)過去了年那些被我藏在收藏夾吃灰的文章,已經(jīng)太多了,是時候把他們整理一下了。那是因為收藏夾太亂,橡皮擦給設(shè)置私密了,不收拾不好看呀。 ...
摘要:返回值,非必須,返回多個值使用逗號分隔即可。注意第一行末尾的分號無參數(shù)無返回值的函數(shù)該內(nèi)容將演示函數(shù)的使用便捷性。函數(shù)的返回值可以賦值給一個變量,通過打印該變量,即可知道返回的具體內(nèi)容。先學(xué)習(xí)一下局部變量與全局變量。 Python 學(xué)習(xí)的第一個難關(guān) -- 函數(shù),這個地方學(xué)會的人覺得沒有啥,沒學(xué)過的學(xué)的時候迷迷瞪...
閱讀 1122·2021-10-11 10:59
閱讀 3711·2021-09-26 09:55
閱讀 1017·2019-08-30 15:55
閱讀 2738·2019-08-30 15:44
閱讀 502·2019-08-30 14:06
閱讀 815·2019-08-30 11:26
閱讀 3420·2019-08-30 10:49
閱讀 2737·2019-08-29 12:53