Python 構建一個簡單爬蟲系統 (二)
本文是用 Python 構建一個簡單爬蟲系統的第二篇,上一篇介紹了通過 requests 和 Beautifulsoup 來做一個網頁的抓取和解析。本篇介紹通過 queue 和 threading 模塊,使用隊列和多線程來進行大規模數據的抓取。
目錄
- 0x00·背景簡介
- 0x01·queue、threading 基礎
- 0x02·多線程爬蟲實例
背景簡介
Q1: 據說由於 GIL(全局鎖) 的存在,Python 多線程很雞肋,多線程 Python 爬蟲能提高速度嗎?
A1: 要很好的回答這個問題,首先要搞清楚兩個概念:進程與線程,I/O 密集型與計算密集型任務。
進程與線程
操作系統的設計,可以歸結為三點:
- 以多進程形式,允許多個任務同時運行;
- 以多線程形式,允許單個任務分成不同的部分運行;
- 提供協調機制,一方面防止進程之間和線程之間產生衝突,另一方面允許進程之間和線程之間共享資源。
I/O 密集型與計算密集型任務
簡單的來說是頻繁的進行 CPU 的計算,還是文件的讀寫操作。
總結
對於計算密集型的任務,由於 GIL 的存在使用多進程,能提高計算效率
對於I/O 密集型的任務,例如網路爬蟲,大多數時間都是等待從網上下載文件寫入到本地,使用多線程能顯著提高爬蟲的爬取速度。queue、threading 基礎
queue
queue 模塊提供了一個適用於多線程編程的數據結構,可以用來在線程間安全地傳遞消息或者其他數據,它會為提供者處理鎖定,使多個線程可以安全地處理同一個 queue 實例。
下面介紹一些簡單的用法,更複雜的用法參考 queue 的文檔
# 創建一個 queue 實例nq = queue.Queue()nn# 向 queue 添加一個元素nitem = "xchaoinfo"nq.put(item)nn# 從 queue 取得一個元素ndata = q.get()nn# 獲取 queue 的元素的個數nq.qsize()nn# 判斷 queue 是否為空nq.empty()n
threading
threading 在 _thread 模塊的基礎的提供多線程處理的更高級別的介面。
下面介紹一些 threading 的簡單用法
不帶參數的線程創建
import threadingnndef do_work():n print("start working")nnt = threading.Thread(target=do_work)nt.start()n
帶參數的線程創建
import threadingnndef do_work(num):n print("start working of %s" % num)nn# args 是 tuple 類型的參數,nt = threading.Thread(target=do_work, args=(1,))nt.start()n
其實,更常用的方法是繼承 threading.Thread 類,然後重寫 run 方法
多線程爬蟲實例
假設我們已經把要爬取的 url 存放到 url.txt 文件里,並且每行存放一個 url, 每個 url 都是一張圖片的地址,我們要把所有圖片下載到本地,把 url 的後面部分來作為圖片的文件名
import requestsnimport threadingnimport queuenimport osnn# 從 txt 文件中獲取 url, 並且處理下載後的文件名ndef get_url_fn_from_txt():n path = "img/"n if not os.path.exists(path):n os.mkdir(path)n with open("url.txt") as fr:n url_list = [f.strip() for f in fr if f.strip()]nn url_fn_list = [(u, path + u.split("/")[-1]) for u in url_list]nn return url_fn_listnn# 下載 img 的函數,下載成功返回 1 否則返回 0ndef download_img(url, fn):n try:n if os.path.exists(fn):n return 1n img = requests.get(url, timeout=3)n with open(fn, wb) as fw:n fw.write(img.content)n fw.close()n return 1n except:n return 0nnclass DownloadThread(threading.Thread):n """重寫 run 函數"""nn def __init__(self, que):n super(Download, self).__init__()n self.que = quenn def run(self):n while not self.que.empty():n url, fn = self.que.get()n # for u in uid:n if download_img(url, fn):n passn else:n self.que.put(tuple(url, fn))nndef main():n url_fn_list = get_url_fn_from_txt()n que = queue.Queue()n for url_fn in url_fn_list:n que.put(url_fn)nn thread_num = 20 # 線程數n for i in range(thread_num):n download = DownloadThread(que)n download.start()nnif __name__ == __main__:n main()n
這是比較簡陋的一個實例,可以在此基礎上加入更多功能。如有錯誤,煩請指出,不勝感激。
首發於微信公眾號:xchaoinfo
參考:
- 進程與線程的一個簡單解釋 (進程與線程)
- CPU-bound(計算密集型) 和I/O bound(I/O密集型)-gflei-ChinaUnix博客 (I/O 密集型與計算密集型任務)
- queue 和 threading 的用法參考 Python 的文檔
推薦閱讀:
TAG:Python |