外行學 Python 爬蟲 第七篇 開啓多線程加快爬取速度

經過上一篇文章外行學 Python 爬蟲 第六篇 動態翻頁我們實現了網頁的動態的分頁,此時我們可以爬取立創商城所有的原件信息了,經過幾十個小時的不懈努力,一共獲取了 16萬+ 條數據,但是軟件的效率實在是有點低了,看了下獲取 10 萬條數據的時間超過了 56 個小時,平均每分鐘才獲取 30 條數據。


注:軟件運行的環境是搬瓦工的虛擬主機,CPU: 2x Intel Xeon , RAM: 1024 MB

軟件的運行效率不高,那麼時間都花費在什麼上面了,爬蟲軟件本身並不是計算密集型軟件,時間大多數花費在與遠程主機的通信上了,要想提高軟件的運行效率,就要減少等待時間,此時你想到了什麼?沒錯就是多線程,在非計算密集型應用中,使用多線程可以最大程度的節省資源同時提高軟件的效率,關於線程的基本應用可以參考前面的文章 python 之進程與線程

針對多線程的修改

使用多線程後,每個線程執行的應該是不同的任務,如果是相同的任務那就是兩個程序而不能說是多線程了。每個線程執行不同的任務「即爬取不同的網頁」,需要線程間共享數據「在本程序中需要共享待爬隊列、已獲取 url 的布隆濾波器等」。因此我們需要多當前的軟件進行修改,以使待爬隊列和布隆濾波器可以在多個線程之間共享數據。

要想在多線程之間共享待爬隊列和布隆濾波器,需要將其從當前的實例屬性修改爲類屬性,以使其可以通過類在多個線程中訪問該屬性。關於類屬性和實例屬性可以參考 Python 類和實例 這篇文章。

將待爬隊列和布隆濾波器設置爲類屬性的代碼如下:

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)

在多線程中,當前的類屬性有多個線程共享,任何一個類屬性都有可能被任何線程修改,因此線程之間共享數據最大的危險在於多個線程同時修改一個數據,把數據給修改亂了。由於 Queue 是一個適用於多線程編程的先進先出的數據結構,可以在生產者和消費者線程之間安全的傳遞消息或數據,因此我們無需對隊列進行操作,但是布隆濾波器是非線程安全的數據,此時我們就需要在修改布隆濾波器的地方加上線程鎖,以保證在同一時刻只有一個線程能夠修改布隆濾波器的數據,代碼如下:

    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 是否已經爬取過的地方調用 url_in_bloomfilter,當需要向布隆濾波器中添加 url 時調用 url_add_bloomfilter 方法,保證布隆濾波器的數據不會被錯誤修改。

對爬蟲類 Crawler 修改完成後,就是真正啓動多線程的時候,在 main.py 文件中將代碼修改爲如下內容:

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()

以上代碼中首先建立了對數據庫的連接,然後創建了兩個 Crawler 類的的實例,最後創建了兩個線程實例,並啓動線程。

修改後的執行結果



本次軟件開啓了兩個線程同時運行,同樣獲取 10 萬條數據,一共花費了 29 個小時,平均每分鐘獲取了 57.5 條數據,相比單線程效率提高了 191.7%,總體來說效率提高還是非常明顯的。


最終在花費 50 小時 30 分鐘,從立創商城上獲取十六萬五千條數據後,程序執行完成。

從立創商城商品目錄頁面可知立創商城上共計有十六萬七千個元件。程序執行完成後共計獲取十六萬五千條數據,可以說完成了預期設計目標。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章