簡(jiǎn)單使用python多進(jìn)程并發(fā)下載大量圖片

如果有大量圖片想要下載,肯定希望速度越快越好,那么就要使用多任務(wù)。

python支持多線程和多進(jìn)程。但是解釋器中的GIL鎖導(dǎo)致任何Python線程執(zhí)行前,必須先獲得GIL鎖,然后,每執(zhí)行100條字節(jié)碼,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行。所以多線程并不能達(dá)到理想的效果。

使用多進(jìn)程的話,mutilprocessing是個(gè)很好用的庫。如果是一個(gè)進(jìn)程一個(gè)進(jìn)程的創(chuàng)建,使用其中的Process類;如果是大量的,要使用其中的Pool類。如果涉及到進(jìn)程間的通訊,還要使用其中Queue類、Pipes類。

綜合考慮,選用Pool。

首先要獲取所有圖片的url(這一步也可以使用多進(jìn)程),然后創(chuàng)建Pool,其中的子進(jìn)程從前往后讀取url,然后將圖片信息保存到本地。

其中主要的兩個(gè)函數(shù)如下所示,在save_pictures(urls)里面創(chuàng)建容量為40的Pool對(duì)象(默認(rèn)情況下是和系統(tǒng)的CPU核數(shù)相同,可用通過multiprocessing.cput_count()來得知此數(shù)值),然后不停創(chuàng)建子進(jìn)程,進(jìn)行下載任務(wù)。p.close()表示不能再增加子進(jìn)程,p.join()表示要等到所有子進(jìn)程任務(wù)都完成后才會(huì)繼續(xù)往下執(zhí)行。

在save_pic(pic_url,filename)函數(shù)中進(jìn)行單進(jìn)程的下載圖片操作。使用url lib.urlretrieve(url,filename,reporthook,data)函數(shù)來下載,這個(gè)函數(shù)是一塊一塊下載的,比較方便。report hook回調(diào)函數(shù)可以用來計(jì)算下載進(jìn)度,此處沒有使用。具體實(shí)現(xiàn)方法可以看源碼。

屏幕快照 2015-10-25 下午12.52.52.png

![Uploading 屏幕快照 2015-10-25 下午12.52.52_787403.png . . .]](http://upload-images.jianshu.io/upload_images/207122-73a0facdefd1b687.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

在主函數(shù)中,通過以下形式來計(jì)算耗時(shí)。
d1 = datetime.datetime.now()
save_pictures(result)
d2 = datetime.datetime.now()

這是簡(jiǎn)單的一個(gè)多進(jìn)程和單進(jìn)程的對(duì)比結(jié)果。實(shí)際中是開了40個(gè)進(jìn)程,下載了5000+張圖片,共5.56G。
#單任務(wù),urlopen:0:15:26.152427 38 pictures 38.1MB 42.12kb/s
#8進(jìn)程,urlretrieve:0:09:47.217917 38 pictures 38.1MB 66.46kb/s
#40進(jìn)程,urlretrieve:1:43:06.125514 5.56G 943.38kb/s

其實(shí)可以從Mac的活動(dòng)監(jiān)視器中看到實(shí)時(shí)的網(wǎng)絡(luò)下載速度,半夜三更時(shí)40個(gè)進(jìn)程可以達(dá)到4M/s。所以說對(duì)于處理這種重復(fù)大量的任務(wù),一定要選用多任務(wù)。

然而代碼不是一下子就寫成這樣的,即使這個(gè)場(chǎng)景一點(diǎn)都不復(fù)雜。

首先第一次跑的時(shí)候,跑了一個(gè)晚上都沒下載完,我查看了下文件的修改時(shí)間,發(fā)現(xiàn)都是第二天早上,猜測(cè)某個(gè)部分循環(huán)錯(cuò)誤,導(dǎo)致文件不停地重新下載寫入。后來的確發(fā)現(xiàn)是某個(gè)地方多了個(gè)循環(huán),導(dǎo)致每個(gè)圖片能多下載一百多次。

后來發(fā)現(xiàn)剛開始下載很快,然后就慢得不可思議。同時(shí)還有個(gè)現(xiàn)象就是在下載之前創(chuàng)建目錄,只能創(chuàng)建一部分就停止了,也就是說下載循環(huán)停止了。后來我把創(chuàng)建目錄分離,防止受到下載圖片任務(wù)的影響。慢的原因,我懷疑是之前Pool對(duì)象創(chuàng)建有問題,導(dǎo)致大量子進(jìn)程被創(chuàng)建,然后又因?yàn)橥ㄓ嵆瑫r(shí)、進(jìn)程太多導(dǎo)致調(diào)度困難等等。這個(gè)問題后來改了代碼后就不再出現(xiàn),也就沒有深挖。

再跑的時(shí)候跑完了,但是有的圖片下載完全,有的就下載了部分,剩下的都是黑塊,并且這種現(xiàn)象不是零星出現(xiàn),而是大規(guī)模出現(xiàn)。還有的圖片根本打不開,只有1k或者2k。
這種現(xiàn)象我懷疑是超時(shí)導(dǎo)致的,因?yàn)閡rlretrieve并沒有超時(shí)設(shè)置參數(shù),所以通過socket.setdefaulttimeout(30)來強(qiáng)制設(shè)置。同時(shí)使用異常處理來處理超時(shí)時(shí)的循環(huán)操作,采用的結(jié)構(gòu)如下。

while:
try:
...
except ...:
...
else:
...

然而這種現(xiàn)象并沒有消除,后來我嘗試使用terminal來復(fù)現(xiàn)這些照片的下載流程時(shí)發(fā)現(xiàn),還會(huì)拋出其他的一些錯(cuò)誤,主要的原因是我要下載的這個(gè)服務(wù)器不太穩(wěn)定,一不小心就訪問失敗了(我懷疑是由于我的大量進(jìn)程訪問導(dǎo)致的)。所以看我的save_pic函數(shù)中是有2個(gè)except的,一個(gè)單獨(dú)處理socket.timeout異常,另一個(gè)用來處理其他異常。
其實(shí)這種現(xiàn)象沒經(jīng)驗(yàn)的話是很難判斷的,因?yàn)槲以O(shè)置的是30s超時(shí),那么按照理想情況,所有正在下載的圖片在文件系統(tǒng)的修改時(shí)間應(yīng)該都在30s之內(nèi),只有下載好了的才會(huì)超過這個(gè)時(shí)間。然而實(shí)際情況那些只下載了部分的圖片的修改時(shí)間也是好幾分鐘之前了。查看打印消息,也發(fā)現(xiàn)這些圖片只有try部分的沒有else部分的也沒有except socket.timeout部分。
那為什么打印消息中沒有拋出的異常信息,程序沒有崩潰?我覺得由于是子進(jìn)程出錯(cuò),所以并不會(huì)對(duì)主進(jìn)程造成影響導(dǎo)致。
所以說,異常處理一定要對(duì)每種場(chǎng)景都包括,要有對(duì)默認(rèn)出錯(cuò)的處理方法,特別是多進(jìn)程時(shí),無聲無息被kill,讓人摸不著頭腦。

另外還犯了一個(gè)錯(cuò),就是在except中把urllib.urlretrieve也寫到下面,這樣導(dǎo)致在except中出異常時(shí),不再能夠處理異常。后續(xù)把urlretrieve只放到try中,其他的except只進(jìn)行打印和計(jì)數(shù),然后讓循環(huán)來幫我重新下載。

修改完畢后基本跑通,各種異常信息都能夠打印出來并處理,整體無比和諧,除了個(gè)別圖片依然是1k或2k的大小,查了下網(wǎng)頁代碼,發(fā)現(xiàn)是bug,也就不作理會(huì)了。

總結(jié):多任務(wù)很好用,異常處理要用心。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容