爬蟲從入門到入獄(5)——多執行緒爬蟲與常見搜尋演算法
文章內容均出自《python爬蟲開發》
文章目錄
5.1 多執行緒爬蟲
5.1.1 多執行緒的優勢
5.1.2 多程序庫:multiprocessing
5.1.3 多執行緒爬蟲開發
5.2 爬蟲的常見搜尋演算法
5.2.1 深度優先搜尋
5.2.2 廣度優先搜尋
5.2.3 演算法選擇
5.1 多執行緒爬蟲
5.1.1 多執行緒的優勢
在掌握了requests與正則表示式以後,就可以開始實戰爬取一些簡單的網址了。
但是,此時的爬蟲只有一個程序、一個執行緒,因此稱為單執行緒爬蟲。單執行緒爬蟲每次只訪問一個頁面,不能充分利用計算機的網路頻寬。一個頁面最多也就幾百KB,所以爬蟲在爬取一個頁面的時候,多出來的網速和從發起請求到得到原始碼中間的時間都被浪費了。如果可以讓爬蟲同時訪問10個頁面,就相當於爬取速度提高了10倍。為了達到這個目的,就需要使用多執行緒技術了。
Python這門語言,有一個全域性直譯器鎖(Global Interpreter Lock, GIL)。這導致Python的多執行緒都是偽多執行緒,即本質上還是一個執行緒,但是這個執行緒每個事情只做幾毫秒,幾毫秒以後就儲存現場,換做其他事情,幾毫秒後再做其他事情,一輪之後回到第一件事上,恢復現場再做幾毫秒,繼續換……微觀上的單執行緒,在巨集觀上就像同時在做幾件事。這種機制在I/O(Input/Output,輸入/輸出)密集型的操作上影響不大,但是在CPU計算密集型的操作上面,由於只能使用CPU的一個核,就會對效能產生非常大的影響。所以涉及計算密集型的程式,就需要使用多程序,Python的多程序不受GIL的影響。爬蟲屬於I/O密集型的程式,所以使用多執行緒可以大大提高爬取效率。
5.1.2 多程序庫:multiprocessing
multiprocessing本身是Python的多程序庫,用來處理與多程序相關的操作。但是由於程序與程序之間不能直接共享記憶體和堆疊資源,而且啟動新的程序開銷也比執行緒大得多,因此使用多執行緒來爬取比使用多程序有更多的優勢。
multiprocessing下面有一個dummy模組,它可以讓Python的執行緒使用multiprocessing的各種方法。
dummy下面有一個Pool類,它用來實現執行緒池。
這個執行緒池有一個map()方法,可以讓執行緒池裡面的所有執行緒都“同時”執行一個函式。
例如:
在學習了for迴圈後
for i in range(10):
print(i*i)
1
2
這種寫法當然可以得到結果,但是程式碼是一個數一個數地計算,效率並不高。而如果使用多執行緒的技術,讓程式碼同時計算很多個數的平方,就需要使用multiprocessing.dummy來實現:
多執行緒的使用範例:
from multiprocessing.dummy import Pool
def cal_pow(num):
return num*num
pool=Pool(3)
num=[x for x in range(10)]
result=pool.map(cal_pow,num)
print('{}'.format(result))
1
2
3
4
5
6
7
在上面的程式碼中,先定義了一個函式用來計算平方,然後初始化了一個有3個執行緒的執行緒池。這3個執行緒負責計算10個數字的平方,誰先計算完手上的這個數,誰就先取下一個數繼續計算,直到把所有的數字都計算完成為止。
在這個例子中,執行緒池的map()方法接收兩個引數,第1個引數是函式名,第2個引數是一個列表。注意:第1個引數僅僅是函式的名字,是不能帶括號的。第2個引數是一個可迭代的物件,這個可迭代物件裡面的每一個元素都會被函式clac_power2()接收來作為引數。除了列表以外,元組、集合或者字典都可以作為map()的第2個引數。
5.1.3 多執行緒爬蟲開發
由於爬蟲是I/O密集型的操作,特別是在請求網頁原始碼的時候,如果使用單執行緒來開發,會浪費大量的時間來等待網頁返回,所以把多執行緒技術應用到爬蟲中,可以大大提高爬蟲的執行效率。舉一個例子。洗衣機洗完衣服要50min,水壺燒水要15min,背單詞要1h。如果先等著洗衣機洗衣服,衣服洗完了再燒水,水燒開了再背單詞,一共需要125min。
但是如果換一種方式,從整體上看,3件事情是可以同時執行的,假設你突然分身出另外兩個人,其中一個人負責把衣服放進洗衣機並等待洗衣機洗完,另一個人負責燒水並等待水燒開,而你自己只需要背單詞就可以了。等到水燒開,負責燒水的分身先消失。等到洗衣機洗完衣服,負責洗衣服的分身再消失。最後你自己本體背完單詞。只需要60min就可以同時完成3件事。
當然,大家肯定會發現上面的例子並不是生活中的實際情況。現實中沒有人會分身。真實生活中的情況是,人背單詞的時候就專心背單詞;水燒開後,水壺會發出響聲提醒;衣服洗完了,洗衣機會發出“滴滴”的聲音。所以到提醒的時候再去做相應的動作就好,沒有必要每分鐘都去檢查。上面的兩種差異,其實就是多執行緒和事件驅動的非同步模型的差異。本小節講到的是多執行緒操作,後面會講到使用非同步操作的爬蟲框架。現在只需要記住,在需要操作的動作數量不大的時候,這兩種方式的效能沒有什麼區別,但是一旦動作的數量大量增長,多執行緒的效率提升就會下降,甚至比單執行緒還差。而到那個時候,只有非同步操作才是解決問題的辦法。
下面通過兩段程式碼來對比單執行緒爬蟲和多執行緒爬蟲爬取bd首頁的效能差異:
從執行結果可以看到,一個執行緒用時約16.2s,5個執行緒用時約3.5s,時間是單執行緒的五分之一左右。從時間上也可以看到5個執行緒“同時執行”的效果。但並不是說執行緒池設定得越大越好。從上面的結果也可以看到,5個執行緒執行的時間其實比一個執行緒執行時間的五分之一要多一點。這多出來的一點其實就是執行緒切換的時間。這也從側面反映了Python的多執行緒在微觀上還是序列的。因此,如果執行緒池設定得過大,執行緒切換導致的開銷可能會抵消多執行緒帶來的效能提升。執行緒池的大小需要根據實際情況來確定,並沒有確切的資料。讀者可以在具體的應用場景下設定不同的大小進行測試對比,找到一個最合適的資料。
5.2 爬蟲的常見搜尋演算法
5.2.1 深度優先搜尋
某線上教育網站的課程分類,需要爬取上面的課程資訊。從首頁開始,課程有幾個大的分類,比如根據語言分為Python、Node.js和Golang。每個大分類下面又有很多的課程,比如Python下面有爬蟲、Django和機器學習。每個課程又分為很多的課時。
在深度優先搜尋的情況下,爬取路線如圖所示(序號從小到大)
5.2.2 廣度優先搜尋
先後順序如下
5.2.3 演算法選擇
例如要爬取某網站全國所有的餐館資訊和每個餐館的訂單資訊。假設使用深度優先演算法,那麼先從某個連結爬到了餐館A,再立刻去爬餐館A的訂單資訊。由於全國有十幾萬家餐館,全部爬完可能需要12小時。這樣導致的問題就是,餐館A的訂單量可能是早上8點爬到的,而餐館B是晚上8點爬到的。它們的訂單量差了12小時。而對於熱門餐館來說,12小時就有可能帶來幾百萬的收入差距。這樣在做資料分析時,12小時的時間差就會導致難以對比A和B兩個餐館的銷售業績。相對於訂單量來說,餐館的數量變化要小得多。所以如果採用廣度優先搜尋,先在半夜0點到第二天中午12點把所有的餐館都爬取一遍,第二天下午14點到20點再集中爬取每個餐館的訂單量。這樣做,只用了6個小時就完成了訂單爬取任務,縮小了由時間差異致的訂單量差異。同時由於店鋪隔幾天抓一次影響也不大,所以請求量也減小了,使爬蟲更難被網站發現。
又例如,要分析實時輿情,需要爬百度貼吧。一個熱門的貼吧可能有幾萬頁的帖子,假設最早的帖子可追溯到2010年。如果採用廣度優先搜尋,則先把這個貼吧所有帖子的標題和網址都獲取下來,然後根據這些網址進入每個帖子裡面以獲取每一層樓的資訊。可是,既然是實時輿情,那麼7年前的帖子對現在的分析意義不大,更重要的應該是新的帖子才對,所以應該優先抓取新的內容。相對於過往的內容,實時的內容才最為重要。因此,對於貼吧內容的爬取,應該採用深度優先搜尋。看到一個帖子就趕緊進去,爬取它的每個樓層資訊,一個帖子爬完了再爬下一個帖子。當然,這兩種搜尋演算法並非非此即彼,需要根據實際情況靈活選擇,很多時候也能夠同時使用。
————————————————
版權宣告:本文為CSDN博主「流鼻涕不用抽紙」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/weixin_55159605/article/details/124147908