多線程是提升爬蟲採集速度一個非常好的方式之一。
首先我們要引用兩個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密集型問題,例如網路阻塞,文件讀取,有更好的效果。