本文希望達到的目標:
1. 學習Queue模塊
2. 將Queue模塊與多線程編程相結合
3. 通過Queue和threading模塊, 重構爬蟲, 實現多線程爬蟲,
4. 通過以上學習希望總結出一個通用的多線程爬蟲小模版
# 1\. Queue模塊
* * *
`Queue`模塊實現了多生產者多消費者隊列, 尤其適合多線程編程.`Queue`類中`實現了所有需要的鎖原語`(這句話非常重要), Queue模塊實現了三種類型隊列:
* FIFO(先進先出)隊列, 第一加入隊列的任務, 被第一個取出
* LIFO(后進先出)隊列,最后加入隊列的任務, 被第一個取出(操作類似與棧, 總是從棧頂取出, 這個隊列還不清楚內部的實現)
* PriorityQueue(優先級)隊列, 保持隊列數據有序, 最小值被先取出(在C++中我記得優先級隊列是可以自己重寫排序規則的, Python不知道可以嗎)
## 1.1\. 類和異常
~~~
import Queue
#類
Queue.Queue(maxsize = 0) #構造一個FIFO隊列,maxsize設置隊列大小的上界, 如果插入數據時, 達到上界會發生阻塞, 直到隊列可以放入數據. 當maxsize小于或者等于0, 表示不限制隊列的大小(默認)
Queue.LifoQueue(maxsize = 0) #構造一LIFO隊列,maxsize設置隊列大小的上界, 如果插入數據時, 達到上界會發生阻塞, 直到隊列可以放入數據. 當maxsize小于或者等于0, 表示不限制隊列的大小(默認)
Queue.PriorityQueue(maxsize = 0) #構造一個優先級隊列,,maxsize設置隊列大小的上界, 如果插入數據時, 達到上界會發生阻塞, 直到隊列可以放入數據. 當maxsize小于或者等于0, 表示不限制隊列的大小(默認). 優先級隊列中, 最小值被最先取出
#異常
Queue.Empty #當調用非阻塞的get()獲取空隊列的元素時, 引發異常
Queue.Full #當調用非阻塞的put()向滿隊列中添加元素時, 引發異常
~~~
## 1.2\. Queue對象
三種隊列對象`提供公共的方法`
~~~
Queue.empty() #如果隊列為空, 返回True(注意隊列為空時, 并不能保證調用put()不會阻塞); 隊列不空返回False(不空時, 不能保證調用get()不會阻塞)
Queue.full() #如果隊列為滿, 返回True(不能保證調用get()不會阻塞), 如果隊列不滿, 返回False(并不能保證調用put()不會阻塞)
Queue.put(item[, block[, timeout]]) #向隊列中放入元素, 如果可選參數block為True并且timeout參數為None(默認), 為阻塞型put(). 如果timeout是正數, 會阻塞timeout時間并引發Queue.Full異常. 如果block為False為非阻塞put
Queue.put_nowait(item) #等價于put(itme, False)
Queue.get([block[, timeout]]) #移除列隊元素并將元素返回, block = True為阻塞函數, block = False為非阻塞函數. 可能返回Queue.Empty異常
Queue.get_nowait() #等價于get(False)
Queue.task_done() #在完成一項工作之后,Queue.task_done()函數向任務已經完成的隊列發送一個信號
Queue.join() #實際上意味著等到隊列為空,再執行別的操作
~~~
下面是官方文檔給多出的多線程模型:
~~~
def worker():
while True:
item = q.get()
do_work(item)
q.task_done()
q = Queue()
for i in range(num_worker_threads):
t = Thread(target=worker)
t.daemon = True
t.start()
for item in source():
q.put(item)
q.join() # block until all tasks are done
~~~
# 2\. Queue模塊與線程相結合
* * *
簡單寫了一個Queue和線程結合的小程序
~~~
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time
import Queue
SHARE_Q = Queue.Queue() #構造一個不限制大小的的隊列
_WORKER_THREAD_NUM = 3 #設置線程個數
class MyThread(threading.Thread) :
def __init__(self, func) :
super(MyThread, self).__init__()
self.func = func
def run(self) :
self.func()
def worker() :
global SHARE_Q
while not SHARE_Q.empty():
item = SHARE_Q.get() #獲得任務
print "Processing : ", item
time.sleep(1)
def main() :
global SHARE_Q
threads = []
for task in xrange(5) : #向隊列中放入任務
SHARE_Q.put(task)
for i in xrange(_WORKER_THREAD_NUM) :
thread = MyThread(worker)
thread.start()
threads.append(thread)
for thread in threads :
thread.join()
if __name__ == '__main__':
main()
~~~
# 3\. 重構爬蟲
* * *
主要針對之間寫過的豆瓣爬蟲進行重構:
* [Python網絡爬蟲(二)--豆瓣抓站小計](http://andrewliu.tk/2014/12/05/Python%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB-%E4%BA%8C-%E8%B1%86%E7%93%A3%E6%8A%93%E7%AB%99%E5%B0%8F%E8%AE%A1/)
## 3.1\. 豆瓣電影爬蟲重構
通過對Queue和線程模型進行改寫, 可以寫出下面的爬蟲程序 :
~~~
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 多線程爬取豆瓣Top250的電影名稱
import urllib2, re, string
import threading, Queue, time
import sys
reload(sys)
sys.setdefaultencoding('utf8')
_DATA = []
FILE_LOCK = threading.Lock()
SHARE_Q = Queue.Queue() #構造一個不限制大小的的隊列
_WORKER_THREAD_NUM = 3 #設置線程的個數
class MyThread(threading.Thread) :
def __init__(self, func) :
super(MyThread, self).__init__() #調用父類的構造函數
self.func = func #傳入線程函數邏輯
def run(self) :
self.func()
def worker() :
global SHARE_Q
while not SHARE_Q.empty():
url = SHARE_Q.get() #獲得任務
my_page = get_page(url) #爬取整個網頁的HTML代碼
find_title(my_page) #獲得當前頁面的電影名
time.sleep(1)
SHARE_Q.task_done()
~~~
完整代碼請查看[Github豆瓣多線程爬蟲](https://github.com/Andrew-liu/dou_ban_spider/blob/master/threading_douban.py)
完成這個程序后, 又出現了新的問題:
> 無法保證數據的順序性, 因為線程是并發的, 思考的方法是: 設置一個主線程進行管理, 然后他們的線程工作
# 4\. 通用的多線程爬蟲小模版
* * *
下面是根據上面的爬蟲做了點小改動后形成的模板
~~~
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time
import Queue
SHARE_Q = Queue.Queue() #構造一個不限制大小的的隊列
_WORKER_THREAD_NUM = 3 #設置線程的個數
class MyThread(threading.Thread) :
"""
doc of class
Attributess:
func: 線程函數邏輯
"""
def __init__(self, func) :
super(MyThread, self).__init__() #調用父類的構造函數
self.func = func #傳入線程函數邏輯
def run(self) :
"""
重寫基類的run方法
"""
self.func()
def do_something(item) :
"""
運行邏輯, 比如抓站
"""
print item
def worker() :
"""
主要用來寫工作邏輯, 只要隊列不空持續處理
隊列為空時, 檢查隊列, 由于Queue中已經包含了wait,
notify和鎖, 所以不需要在取任務或者放任務的時候加鎖解鎖
"""
global SHARE_Q
while True :
if not SHARE_Q.empty():
item = SHARE_Q.get() #獲得任務
do_something(item)
time.sleep(1)
SHARE_Q.task_done()
def main() :
global SHARE_Q
threads = []
#向隊列中放入任務, 真正使用時, 應該設置為可持續的放入任務
for task in xrange(5) :
SHARE_Q.put(task)
#開啟_WORKER_THREAD_NUM個線程
for i in xrange(_WORKER_THREAD_NUM) :
thread = MyThread(worker)
thread.start() #線程開始處理任務
threads.append(thread)
for thread in threads :
thread.join()
#等待所有任務完成
SHARE_Q.join()
if __name__ == '__main__':
main()
~~~
> 我感覺其實這個多線程挺凌亂的, 希望以后自己能重構
# 5\. 思考更高效的爬蟲方法
* * *
* 使用[twisted](https://twistedmatrix.com/trac/wiki/Documentation)進行異步IO抓取
* 使用`Scrapy`框架(Scrapy 使用了 Twisted 異步網絡庫來處理網絡通訊)
# 6\. 參考鏈接
* * *
[Queue官方文檔](https://docs.python.org/2/library/queue.html)
[Twisted英文入門指南](http://krondo.com/blog/?page_id=1327)
[Twisted中文入門指南](http://turtlerbender007.appspot.com/twisted/index.html)
- Python爬蟲入門
- (1):綜述
- (2):爬蟲基礎了解
- (3):Urllib庫的基本使用
- (4):Urllib庫的高級用法
- (5):URLError異常處理
- (6):Cookie的使用
- (7):正則表達式
- (8):Beautiful Soup的用法
- Python爬蟲進階
- Python爬蟲進階一之爬蟲框架概述
- Python爬蟲進階二之PySpider框架安裝配置
- Python爬蟲進階三之Scrapy框架安裝配置
- Python爬蟲進階四之PySpider的用法
- Python爬蟲實戰
- Python爬蟲實戰(1):爬取糗事百科段子
- Python爬蟲實戰(2):百度貼吧帖子
- Python爬蟲實戰(3):計算大學本學期績點
- Python爬蟲實戰(4):模擬登錄淘寶并獲取所有訂單
- Python爬蟲實戰(5):抓取淘寶MM照片
- Python爬蟲實戰(6):抓取愛問知識人問題并保存至數據庫
- Python爬蟲利器
- Python爬蟲文章
- Python爬蟲(一)--豆瓣電影抓站小結(成功抓取Top100電影)
- Python爬蟲(二)--Coursera抓站小結
- Python爬蟲(三)-Socket網絡編程
- Python爬蟲(四)--多線程
- Python爬蟲(五)--多線程續(Queue)
- Python爬蟲(六)--Scrapy框架學習
- Python爬蟲(七)--Scrapy模擬登錄
- Python筆記
- python 知乎爬蟲
- Python 爬蟲之——模擬登陸
- python的urllib2 模塊解析
- 蜘蛛項目要用的數據庫操作
- gzip 壓縮格式的網站處理方法
- 通過瀏覽器的調試得出 headers轉換成字典
- Python登錄到weibo.com
- weibo v1.4.5 支持 RSA協議(模擬微博登錄)
- 搭建Scrapy爬蟲的開發環境
- 知乎精華回答的非專業大數據統計
- 基于PySpider的weibo.cn爬蟲
- Python-實現批量抓取妹子圖片
- Python庫
- python數據庫-mysql
- 圖片處理庫PIL
- Mac OS X安裝 Scrapy、PIL、BeautifulSoup
- 正則表達式 re模塊
- 郵件正則
- 正則匹配,但過濾某些字符串
- dict使用方法和快捷查找
- httplib2 庫的使用