經(jīng)過上一篇文章外行學(xué) Python 爬蟲 第六篇 動態(tài)翻頁我們實現(xiàn)了網(wǎng)頁的動態(tài)的分頁,此時我們可以爬取立創(chuàng)商城所有的原件信息了囚衔,經(jīng)過幾十個小時的不懈努力喜滨,一共獲取了 16萬+ 條數(shù)據(jù)鳄抒,但是軟件的效率實在是有點低了知允,看了下獲取 10 萬條數(shù)據(jù)的時間超過了 56 個小時撒蟀,平均每分鐘才獲取 30 條數(shù)據(jù)。
注:軟件運行的環(huán)境是搬瓦工的虛擬主機温鸽,CPU: 2x Intel Xeon , RAM: 1024 MB
軟件的運行效率不高保屯,那么時間都花費在什么上面了,爬蟲軟件本身并不是計算密集型軟件,時間大多數(shù)花費在與遠(yuǎn)程主機的通信上了姑尺,要想提高軟件的運行效率竟终,就要減少等待時間,此時你想到了什么切蟋?沒錯就是多線程衡楞,在非計算密集型應(yīng)用中,使用多線程可以最大程度的節(jié)省資源同時提高軟件的效率敦姻,關(guān)于線程的基本應(yīng)用可以參考前面的文章 python 之進程與線程。
針對多線程的修改
使用多線程后歧杏,每個線程執(zhí)行的應(yīng)該是不同的任務(wù)镰惦,如果是相同的任務(wù)那就是兩個程序而不能說是多線程了。每個線程執(zhí)行不同的任務(wù)「即爬取不同的網(wǎng)頁」犬绒,需要線程間共享數(shù)據(jù)「在本程序中需要共享待爬隊列旺入、已獲取 url 的布隆濾波器等」。因此我們需要多當(dāng)前的軟件進行修改凯力,以使待爬隊列和布隆濾波器可以在多個線程之間共享數(shù)據(jù)茵瘾。
要想在多線程之間共享待爬隊列和布隆濾波器,需要將其從當(dāng)前的實例屬性修改為類屬性咐鹤,以使其可以通過類在多個線程中訪問該屬性拗秘。關(guān)于類屬性和實例屬性可以參考 Python 類和實例 這篇文章。
將待爬隊列和布隆濾波器設(shè)置為類屬性的代碼如下:
class Crawler:
url_queue = Queue()
bloomfilter = ScalableBloomFilter()
...
在使用的過程中通過類名來訪問類屬性的值祈惶,示例代碼如下:
def __init__(self, url_count = 1000, url = None):
if (Crawler.max_url_count < url_count):
Crawler.max_url_count = url_count
Crawler.url_queue.put(url)
在多線程中雕旨,當(dāng)前的類屬性有多個線程共享,任何一個類屬性都有可能被任何線程修改捧请,因此線程之間共享數(shù)據(jù)最大的危險在于多個線程同時修改一個數(shù)據(jù)凡涩,把數(shù)據(jù)給修改亂了。由于 Queue 是一個適用于多線程編程的先進先出的數(shù)據(jù)結(jié)構(gòu)疹蛉,可以在生產(chǎn)者和消費者線程之間安全的傳遞消息或數(shù)據(jù)活箕,因此我們無需對隊列進行操作,但是布隆濾波器是非線程安全的數(shù)據(jù)可款,此時我們就需要在修改布隆濾波器的地方加上線程鎖育韩,以保證在同一時刻只有一個線程能夠修改布隆濾波器的數(shù)據(jù),代碼如下:
def url_in_bloomfilter(self, url):
if url in Crawler.bloomfilter:
return True
return False
def url_add_bloomfilter(self, url):
Crawler.lock.acquire()
Crawler.bloomfilter.add(url)
Crawler.lock.release()
在所有需要判斷 url 是否已經(jīng)爬取過的地方調(diào)用 url_in_bloomfilter筑舅,當(dāng)需要向布隆濾波器中添加 url 時調(diào)用 url_add_bloomfilter 方法座慰,保證布隆濾波器的數(shù)據(jù)不會被錯誤修改。
對爬蟲類 Crawler 修改完成后翠拣,就是真正啟動多線程的時候版仔,在 main.py 文件中將代碼修改為如下內(nèi)容:
def main():
with open('database.conf','r') as confFile:
confStr = confFile.read()
conf = json.JSONDecoder().decode(confStr)
db.init_url(url=conf['mariadb_url'])
crawler1 = Crawler(1000, url='https://www.szlcsc.com/catalog.html')
crawler2 = Crawler(1000, url='https://www.szlcsc.com/catalog.html')
thread_one = threading.Thread(target=crawler1.run)
thread_two = threading.Thread(target=crawler2.run)
thread_one.start()
thread_two.start()
thread_one.join()
thread_two.join()
以上代碼中首先建立了對數(shù)據(jù)庫的連接,然后創(chuàng)建了兩個 Crawler 類的的實例,最后創(chuàng)建了兩個線程實例蛮粮,并啟動線程益缎。
修改后的執(zhí)行結(jié)果
本次軟件開啟了兩個線程同時運行,同樣獲取 10 萬條數(shù)據(jù)然想,一共花費了 29 個小時莺奔,平均每分鐘獲取了 57.5 條數(shù)據(jù),相比單線程效率提高了 191.7%变泄,總體來說效率提高還是非常明顯的令哟。
最終在花費 50 小時 30 分鐘,從立創(chuàng)商城上獲取十六萬五千條數(shù)據(jù)后妨蛹,程序執(zhí)行完成屏富。
從立創(chuàng)商城商品目錄頁面可知立創(chuàng)商城上共計有十六萬七千個元件。程序執(zhí)行完成后共計獲取十六萬五千條數(shù)據(jù)蛙卤,可以說完成了預(yù)期設(shè)計目標(biāo)狠半。