最令人頭疼的Python問題:Python多執行緒在爬蟲中的應用

博為峰網校發表於2019-11-05

作為測試工程師經常需要解決測試資料來源的問題,解決思路無非是三種:

1、直接從生產環境複製真實資料

2、從網際網路上爬取資料

3、自己用指令碼或者工具造資料。

前段時間,為了獲取更多的測試資料,筆者就做了一個從網際網路上爬取資料的爬蟲程式,雖然功能上基本滿足專案的需求,但是爬取的效率還是不太高。

作為一個精益求精的測試工程師,決定研究一下多執行緒在爬蟲領域的應用,以提高爬蟲的效率。

最令人頭疼的Python問題:Python多執行緒在爬蟲中的應用 點選新增圖片描述(最多60個字)

一、為什麼需要多執行緒

凡事知其然也要知其所以然。在瞭解多執行緒的相關知識之前,我們先來看看為什麼需要多執行緒。打個比方吧,你要搬家了,單執行緒就類似於請了一個搬家工人,他一個人負責打包、搬運、開車、卸貨等一系列操作流程,這個工作效率可想而知是很慢的;而多執行緒就相當於請了四個搬家工人,甲打包完交給已搬運到車上,然後丙開車送往目的地,最後由丁來卸貨。

由此可見多執行緒的好處就是高效、可以充分利用資源,壞處就是各個執行緒之間要相互協調,否則容易亂套(類似於一個和尚挑水喝、兩個和尚抬水喝、三個和尚沒水喝的窘境)。所以為了提高爬蟲效率,我們在使用多執行緒時要格外注意多執行緒的管理問題。

二、多執行緒的基本知識

程式:由程式,資料集,程式控制塊三部分組成,它是程式在資料集上的一次執行過程。如果同一段程式在某個資料集上執行了兩次,那就是開啟了兩個程式。程式是資源管理的基本單位。在作業系統中,每個程式有一個地址空間,而且預設就有一個控制程式。

執行緒:是程式的一個實體,是 CPU 排程和分派的基本單位,也是最小的執行單位。它的出現降低了上下文切換的消耗,提高了系統的併發性,並克服了一個程式只能幹一件事的缺陷。執行緒由程式來管理,多個執行緒共享父程式的資源空間。

程式和執行緒的關係:

一個執行緒只能屬於一個程式,而一個程式可以有多個執行緒,但至少有一個執行緒。

資源分配給程式,同一程式的所有執行緒共享該程式的所有資源。

CPU 分給執行緒,即真正在 CPU 上執行的是執行緒。

執行緒的工作方式:

如下圖所示,序列指執行緒一個個地在 CPU 上執行;並行是在多個 CPU 上執行多個

執行緒;而併發是一種“偽並行”,一個 CPU 同一時刻只能執行一個任務,把 CPU 的時間

分片,一個執行緒只佔用一個很短的時間片,然後各個執行緒輪流,由於時間片很短所以在

使用者看來所有執行緒都是“同時”的。併發也是大多數單 CPU 多執行緒的實際執行方式。

最令人頭疼的Python問題:Python多執行緒在爬蟲中的應用 點選新增圖片描述(最多60個字)

程式的工作狀態:

一個程式有三種狀態:執行、阻塞、就緒。三種狀態之間的轉換關係如下圖所示:執行態的程式可能由於等待輸入而主動進入阻塞狀態,也可能由於排程程式選擇其他程式而被動進入就緒狀態(一般是分給它的 CPU 時間到了);阻塞狀態的程式由於等到了有效的輸入而進入就緒狀態;就緒狀態的程式因為排程程式再次選擇了它而再次進入執行狀態。

最令人頭疼的Python問題:Python多執行緒在爬蟲中的應用 點選新增圖片描述(最多60個字)

三、多執行緒通訊例項

還是回到爬蟲的問題上來,我們知道爬取部落格文章的時候都是先爬取列表頁,然後根據列表頁的爬取結果再來爬取文章詳情內容。而且列表頁的爬取速度肯定要比詳情頁的爬取速度快。

這樣的話,我們可以設計執行緒 A 負責爬取文章列表頁,執行緒 B、執行緒 C、執行緒 D 負責爬取文章詳情。A 將列表 URL 結果放到一個類似全域性變數的結構裡,執行緒 B、C、D從這個結構裡取結果。

在 PYTHON 中,有兩個支援多執行緒的模組:threading 模組--負責執行緒的建立、開啟等操作;queque 模組--負責維護那個類似於全域性變數的結構。

這裡還要補充一點:也許有同學會問直接用一個全域性變數不就可以了麼?幹嘛非要用 queue?

因為全域性變數並不是執行緒安全的,比如說全域性變數裡(列表型別)只有一個 url 了,執行緒 B 判斷了一下全域性變數非空,在還沒有取出該 url 之前,cpu 把時間片給了執行緒 C,執行緒 C 將最後一個url 取走了,這時 cpu 時間片又輪到了 B,B 就會因為在一個空的列表裡取資料而報錯。

而 queue 模組實現了多生產者、多消費者佇列,在放值取值時是執行緒安全的。

廢話不多說了,直接上程式碼給大夥看看:

import threading # 匯入 threading 模組

from queue import Queue #匯入 queue 模組

import time #匯入 time 模組

# 爬取文章詳情頁

def get_detail_html(detail_url_list, id):

while True:

url = detail_url_list.get() #Queue 佇列的 get 方法用於從佇列中提取元素

time.sleep(2) # 延時 2s,模擬網路請求和爬取文章詳情的過程

print("thread {id}: get {url} detail finished".format(id=id,url=url)) #列印執行緒 id 和被爬取了文章內容的 url

# 爬取文章列表頁

def get_detail_url(queue):

for i in range(10000):

time.sleep(1) # 延時 1s,模擬比爬取文章詳情要快

queue.put("{id}".format(id=i))#Queue 佇列的 put 方法用於向 Queue 佇列中放置元素,由於 Queue 是先進先出佇列,所以先被 Put 的 URL 也就會被先 get 出來。

print("get detail url {id} end".format(id=i))#列印出得到了哪些文章的 url

#主函式

if __name__ == "__main__":

detail_url_queue = Queue(maxsize=1000) #用 Queue 構造一個大小為 1000 的執行緒安全的先進先出佇列

# 先創造四個執行緒

thread = threading.Thread(target=get_detail_url, args=(detail_url_queue,)) #A 執行緒負責抓取列表

url

html_thread= []

for i in range(3):

thread2 = threading.Thread(target=get_detail_html, args=(detail_url_queue,i))

html_thread.append(thread2)#B C D 執行緒抓取文章詳情

start_time = time.time()

# 啟動四個執行緒

thread.start()

for i in range(3):

html_thread[i].start()

# 等待所有執行緒結束,thread.join()函式代表子執行緒完成之前,其父程式一直處於阻塞狀態。

thread.join()

for i in range(3):

html_thread[i].join()

print("last time: {} s".format(time.time()-start_time))#等 ABCD 四個執行緒都結束後,在主程式中計算總爬取時間。

執行結果:

最令人頭疼的Python問題:Python多執行緒在爬蟲中的應用 點選新增圖片描述(最多60個字)

後記:

從執行結果可以看出各個執行緒之間井然有序地工作著,沒有出現任何報錯和告警的情況。可見使用 Queue 佇列實現多執行緒間的通訊比直接使用全域性變數要安全很多。而且使用多執行緒比不使用多執行緒的話,爬取時間上也要少很多,在提高了爬蟲效率的同時也兼顧了執行緒的安全,可以說在爬取測試資料的過程中是非常實用的一種方式。

希望小夥伴們能夠 GET 到哦!

加我VX:ww-51testing   回覆關鍵詞“測試”領取限量軟體測試學習資料哦~~

最令人頭疼的Python問題:Python多執行緒在爬蟲中的應用


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31407649/viewspace-2662778/,如需轉載,請註明出處,否則將追究法律責任。

相關文章