您好,登錄后才能下訂單哦!
本篇文章為大家展示了如何分析Python多線程在爬蟲中的應(yīng)用,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過這篇文章的詳細(xì)介紹希望你能有所收獲。
作為測(cè)試工程師經(jīng)常需要解決測(cè)試數(shù)據(jù)來源的問題,解決思路無非是三種:
1、直接從生產(chǎn)環(huán)境拷貝真實(shí)數(shù)據(jù)
2、從互聯(lián)網(wǎng)上爬取數(shù)據(jù)
3、自己用腳本或者工具造數(shù)據(jù)。
前段時(shí)間,為了獲取更多的測(cè)試數(shù)據(jù),筆者就做了一個(gè)從互聯(lián)網(wǎng)上爬取數(shù)據(jù)的爬蟲程序,雖然功能上基本滿足項(xiàng)目的需求,但是爬取的效率還是不太高。
作為一個(gè)精益求精的測(cè)試工程師,決定研究一下多線程在爬蟲領(lǐng)域的應(yīng)用,以提高爬蟲的效率。
一、為什么需要多線程
凡事知其然也要知其所以然。在了解多線程的相關(guān)知識(shí)之前,我們先來看看為什么需要多線程。打個(gè)比方吧,你要搬家了,單線程就類似于請(qǐng)了一個(gè)搬家工人,他一個(gè)人負(fù)責(zé)打包、搬運(yùn)、開車、卸貨等一系列操作流程,這個(gè)工作效率可想而知是很慢的;而多線程就相當(dāng)于請(qǐng)了四個(gè)搬家工人,甲打包完交給已搬運(yùn)到車上,然后丙開車送往目的地,最后由丁來卸貨。
由此可見多線程的好處就是高效、可以充分利用資源,壞處就是各個(gè)線程之間要相互協(xié)調(diào),否則容易亂套(類似于一個(gè)和尚挑水喝、兩個(gè)和尚抬水喝、三個(gè)和尚沒水喝的窘境)。所以為了提高爬蟲效率,我們?cè)谑褂枚嗑€程時(shí)要格外注意多線程的管理問題。
二、多線程的基本知識(shí)
進(jìn)程:由程序,數(shù)據(jù)集,進(jìn)程控制塊三部分組成,它是程序在數(shù)據(jù)集上的一次運(yùn)行過程。如果同一段程序在某個(gè)數(shù)據(jù)集上運(yùn)行了兩次,那就是開啟了兩個(gè)進(jìn)程。進(jìn)程是資源管理的基本單位。在操作系統(tǒng)中,每個(gè)進(jìn)程有一個(gè)地址空間,而且默認(rèn)就有一個(gè)控制進(jìn)程。
線程:是進(jìn)程的一個(gè)實(shí)體,是 CPU 調(diào)度和分派的基本單位,也是最小的執(zhí)行單位。它的出現(xiàn)降低了上下文切換的消耗,提高了系統(tǒng)的并發(fā)性,并克服了一個(gè)進(jìn)程只能干一件事的缺陷。線程由進(jìn)程來管理,多個(gè)線程共享父進(jìn)程的資源空間。
進(jìn)程和線程的關(guān)系:
一個(gè)線程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線程,但至少有一個(gè)線程。
資源分配給進(jìn)程,同一進(jìn)程的所有線程共享該進(jìn)程的所有資源。
CPU 分給線程,即真正在 CPU 上運(yùn)行的是線程。
線程的工作方式:
如下圖所示,串行指線程一個(gè)個(gè)地在 CPU 上執(zhí)行;并行是在多個(gè) CPU 上運(yùn)行多個(gè)
線程;而并發(fā)是一種“偽并行”,一個(gè) CPU 同一時(shí)刻只能執(zhí)行一個(gè)任務(wù),把 CPU 的時(shí)間
分片,一個(gè)線程只占用一個(gè)很短的時(shí)間片,然后各個(gè)線程輪流,由于時(shí)間片很短所以在
用戶看來所有線程都是“同時(shí)”的。并發(fā)也是大多數(shù)單 CPU 多線程的實(shí)際運(yùn)行方式。
點(diǎn)擊添加圖片描述(最多60個(gè)字)
進(jìn)程的工作狀態(tài):
一個(gè)進(jìn)程有三種狀態(tài):運(yùn)行、阻塞、就緒。三種狀態(tài)之間的轉(zhuǎn)換關(guān)系如下圖所示:運(yùn)行態(tài)的進(jìn)程可能由于等待輸入而主動(dòng)進(jìn)入阻塞狀態(tài),也可能由于調(diào)度程序選擇其他進(jìn)程而被動(dòng)進(jìn)入就緒狀態(tài)(一般是分給它的 CPU 時(shí)間到了);阻塞狀態(tài)的進(jìn)程由于等到了有效的輸入而進(jìn)入就緒狀態(tài);就緒狀態(tài)的進(jìn)程因?yàn)檎{(diào)度程序再次選擇了它而再次進(jìn)入運(yùn)行狀態(tài)。
點(diǎn)擊添加圖片描述(最多60個(gè)字)
三、多線程通信實(shí)例
還是回到爬蟲的問題上來,我們知道爬取博客文章的時(shí)候都是先爬取列表頁,然后根據(jù)列表頁的爬取結(jié)果再來爬取文章詳情內(nèi)容。而且列表頁的爬取速度肯定要比詳情頁的爬取速度快。
這樣的話,我們可以設(shè)計(jì)線程 A 負(fù)責(zé)爬取文章列表頁,線程 B、線程 C、線程 D 負(fù)責(zé)爬取文章詳情。A 將列表 URL 結(jié)果放到一個(gè)類似全局變量的結(jié)構(gòu)里,線程 B、C、D從這個(gè)結(jié)構(gòu)里取結(jié)果。
在 PYTHON 中,有兩個(gè)支持多線程的模塊:threading 模塊--負(fù)責(zé)線程的創(chuàng)建、開啟等操作;queque 模塊--負(fù)責(zé)維護(hù)那個(gè)類似于全局變量的結(jié)構(gòu)。
這里還要補(bǔ)充一點(diǎn):也許有同學(xué)會(huì)問直接用一個(gè)全局變量不就可以了么?干嘛非要用 queue?
因?yàn)槿肿兞坎⒉皇蔷€程安全的,比如說全局變量里(列表類型)只有一個(gè) url 了,線程 B 判斷了一下全局變量非空,在還沒有取出該 url 之前,cpu 把時(shí)間片給了線程 C,線程 C 將最后一個(gè)url 取走了,這時(shí) cpu 時(shí)間片又輪到了 B,B 就會(huì)因?yàn)樵谝粋€(gè)空的列表里取數(shù)據(jù)而報(bào)錯(cuò)。
而 queue 模塊實(shí)現(xiàn)了多生產(chǎn)者、多消費(fèi)者隊(duì)列,在放值取值時(shí)是線程安全的。
廢話不多說了,直接上代碼給大伙看看:
import threading # 導(dǎo)入 threading 模塊
from queue import Queue #導(dǎo)入 queue 模塊
import time #導(dǎo)入 time 模塊
# 爬取文章詳情頁
def get_detail_html(detail_url_list, id):
while True:
url = detail_url_list.get() #Queue 隊(duì)列的 get 方法用于從隊(duì)列中提取元素
time.sleep(2) # 延時(shí) 2s,模擬網(wǎng)絡(luò)請(qǐng)求和爬取文章詳情的過程
print("thread {id}: get {url} detail finished".format(id=id,url=url)) #打印線程 id 和被爬取了文章內(nèi)容的 url
# 爬取文章列表頁
def get_detail_url(queue):
for i in range(10000):
time.sleep(1) # 延時(shí) 1s,模擬比爬取文章詳情要快
queue.put("http://testedu.com/{id}".format(id=i))#Queue 隊(duì)列的 put 方法用于向 Queue 隊(duì)列中放置元素,由于 Queue 是先進(jìn)先出隊(duì)列,所以先被 Put 的 URL 也就會(huì)被先 get 出來。
print("get detail url {id} end".format(id=i))#打印出得到了哪些文章的 url
#主函數(shù)
if __name__ == "__main__":
detail_url_queue = Queue(maxsize=1000) #用 Queue 構(gòu)造一個(gè)大小為 1000 的線程安全的先進(jìn)先出隊(duì)列
# 先創(chuàng)造四個(gè)線程
thread = threading.Thread(target=get_detail_url, args=(detail_url_queue,)) #A 線程負(fù)責(zé)抓取列表
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()
# 啟動(dòng)四個(gè)線程
thread.start()
for i in range(3):
html_thread[i].start()
# 等待所有線程結(jié)束,thread.join()函數(shù)代表子線程完成之前,其父進(jìn)程一直處于阻塞狀態(tài)。
thread.join()
for i in range(3):
html_thread[i].join()
print("last time: {} s".format(time.time()-start_time))#等 ABCD 四個(gè)線程都結(jié)束后,在主進(jìn)程中計(jì)算總爬取時(shí)間。
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果可以看出各個(gè)線程之間井然有序地工作著,沒有出現(xiàn)任何報(bào)錯(cuò)和告警的情況??梢娛褂?Queue 隊(duì)列實(shí)現(xiàn)多線程間的通信比直接使用全局變量要安全很多。而且使用多線程比不使用多線程的話,爬取時(shí)間上也要少很多,在提高了爬蟲效率的同時(shí)也兼顧了線程的安全,可以說在爬取測(cè)試數(shù)據(jù)的過程中是非常實(shí)用的一種方式。
上述內(nèi)容就是如何分析Python多線程在爬蟲中的應(yīng)用,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。