python爬蟲多線程請求示例(生產者消費者模型)

多線程是提升爬蟲採集速度一個非常好的方式之一。

首先我們要引用兩個python內置模塊,threading和queue

import threading
from queue import Queue

threading用於操作線程
queue用於創建隊列

對於簡單的項目來說,不需要類似於scrapy的多層回調,也就是在列表頁的請求過程中,異步獲取詳情頁的數據。
我們可以簡單的將,列表頁,詳情頁採集分開。在獲取所有的詳情頁url過後,用多線程優化詳情頁採集。
我們可以定義一個待採集詳情頁url列表。

url_ls = []   # 我們的待採集url列表

然後 我們將列表傳入隊列,使用隊列的原因是隊列是線程安全的。
隊列相當於多線程的任務列表,每個線程可以去隊列中安全的拿出任務,在爬蟲中就是url,然後併發的去執行。

for i in range(0, len(url, ls)):
      url_queue.put(url_list[i])

使用threading創建線程

threads = []   # 線程列表
threadNum = 5
for i in range(1, threadNum+1):
    thread = Spider("{}_{}".format("Thread", i), worker)   # 線程工作方法
    thread.start()  # 啓動線程
    threads.append(thread)  # 線程列表中加入線程

這時候 我們創建了五個線程,並且一一啓動。
注意:這裏面的spider是我們定義的類,worker是每個線程的工作方法。這個類和方法我們暫時沒有定義,後續我們會一一創建。

對於多線程同時修改同一個數據來說,可能會出現不可預料的風險。我們需要讓每個線程,運行過後,再去執行下一個線程,這個叫做阻塞。

for thread in threads:
    thread.join()   # 每個線程加入阻塞

然後 我們來每個線程的工作方法 也就是之前spider類

class Spider(threading.Thread):
    """多線程類"""
    def __init__(self, Thread_name, func):
        super().__init__()
        self.Thread_name = Thread_name

    def run(self):
        self.func(self.Thread_name)
def worker(Thread_name):
	while not url_queue.empty():   # 若隊列不爲空繼續運行
		url = url_queue.get()      # 從隊列中彈出任務
		print("{}:執行任務{}".format(Thread_name, url))
	print("{}:無任務 結束".format(Thread_name))

可以看到我們每個線程都繼承了threading.Thread, 並且重寫了工作方法worker
這樣在啓動線程後 每個線程都會將進入worker 並提取出它的任務 然後繼續請求或者解析

我們將以上代碼整合一下 並且加入測速模塊

import threading
from queue import Queue
import time

url_queue = Queue()       # 構造一個不限大小的隊列
threads = []              # 構造工作線程池

class Spider(threading.Thread):
    """多線程類"""
    def __init__(self, Thread_name, func):
        super().__init__()
        self.Thread_name = Thread_name

    def run(self):
        self.func(self.Thread_name)
        
def worker(Thread_name)

	while not url_queue.empty():   # 若隊列不爲空繼續運行
		url = url_queue.get()      # 從隊列中彈出任務
		print("{}:執行任務{}".format(Thread_name, url))
		
	print("{}:無任務 結束".format(Thread_name))

if __name__ == "__main__":

	url_ls = []           # 我們的待採集url列表
	start = time.time()   # 程序啓動時間
	
	for i in range(0, len(url, ls)):
		url_queue.put(url_list[i])
	
	threadNum = 5
	
	for i in range(1, threadNum+1):
	    thread = Spider("{}_{}".format("Thread", i), worker)   # 線程工作方法
	    thread.start()          # 啓動線程
	    threads.append(thread)  # 線程列表中加入線程
	   
	for thread in threads:
    	thread.join()     # 每個線程加入阻塞
	
	end = time.time()     # 結束時間
    print("Tread_main:下載完成. 用時{}秒".format(end - start))
	

當然 由於python全局解釋鎖GIL的存在,導致在每個時間節點,只有一個線程在操作解釋器,只不過切換線程的時間可以忽略不記,使其達到一個僞併發的效果。所以python多線程的效果並沒有那麼好。

1.對於cpu密集型問題,多線程反而會消耗更多資源,因爲計算機不停切換線程也需要消耗算力。
2.對於IO密集型問題,例如網路阻塞,文件讀取,有更好的效果。

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