python爬蟲進階使用多執行緒爬取小說
Python多執行緒,thread標準庫。都說Python的多執行緒是雞肋,推薦使用多程序。
Python為了安全考慮有一個GIL。每個CPU在同一時間只能執行一個執行緒
GIL的全稱是Global Interpreter Lock(全域性直譯器鎖),就相當於通行證,每一次執行緒會先要去申請通行證,通行證申請下來了,才能進入CPU執行。
每個執行緒的執行方式:
-
1、獲取GIL
2、執行程式碼直到sleep或者是python虛擬機器將其掛起。
-
3、釋放GIL
每次釋放GIL鎖,執行緒進行鎖競爭、切換執行緒,會消耗資源。並且由於GIL鎖存在,python裡一個程序永遠只能同時執行一個執行緒(拿到GIL的執行緒才能執行),這就是為什麼在多核CPU上,python的多執行緒效率並不高。
下面使用多執行緒加佇列做的一個demo。爬取的是筆趣閣的小說,只是做了一個列印,沒有做具體的儲存。爬取用的selenium。Chrome的無頭模式。有點慢,可以直接用庫,或者跑整站的話用scrapy.
使用Threading模組建立執行緒,直接從threading.Thread繼承,然後重寫init方法和run方法:
執行緒同步
如果多個執行緒共同對某個資料修改,則可能出現不可預料的結果,為了保證資料的正確性,需要對多個執行緒進行同步。
使用Thread物件的Lock和Rlock可以實現簡單的執行緒同步,這兩個物件都有acquire方法和release方法,對於那些需要每次只允許一個執行緒操作的資料,可以將其操作放到acquire和release方法之間。如下:
多執行緒的優勢在於可以同時執行多個任務(至少感覺起來是這樣)。但是當執行緒需要共享資料時,可能存在資料不同步的問題。
考慮這樣一種情況:一個列表裡所有元素都是0,執行緒”set”從後向前把所有元素改成1,而執行緒”print”負責從前往後讀取列表並列印。
那麼,可能執行緒”set”開始改的時候,執行緒”print”便來列印列表了,輸出就成了一半0一半1,這就是資料的不同步。為了避免這種情況,引入了鎖的概念。
鎖有兩種狀態——鎖定和未鎖定。每當一個執行緒比如”set”要訪問共享資料時,必須先獲得鎖定;如果已經有別的執行緒比如”print”獲得鎖定了,那麼就讓執行緒”set”暫停,也就是同步阻塞;等到執行緒”print”訪問完畢,釋放鎖以後,再讓執行緒”set”繼續。
經過這樣的處理,列印列表時要麼全部輸出0,要麼全部輸出1,不會再出現一半0一半1的尷尬場面。
執行緒優先順序佇列
Python的Queue模組中提供了同步的、執行緒安全的佇列類,包括FIFO(先入先出)佇列Queue,LIFO(後入先出)佇列LifoQueue,和優先順序佇列PriorityQueue。這些佇列都實現了鎖原語,能夠在多執行緒中直接使用。可以使用佇列來實現執行緒間的同步。
Queue模組中的常用方法:
-
Queue.qsize() 返回佇列的大小
-
Queue.empty() 如果佇列為空,返回True,反之False
-
Queue.full() 如果佇列滿了,返回True,反之False
-
Queue.full 與 maxsize 大小對應
-
Queue.get([block[, timeout]])獲取佇列,timeout等待時間
-
Queue.get_nowait() 相當Queue.get(False)
-
Queue.put(item) 寫入佇列,timeout等待時間
-
Queue.put_nowait(item) 相當Queue.put(item, False)
-
Queue.task_done() 在完成一項工作之後,Queue.task_done()函式向任務已經完成的佇列傳送一個訊號
-
Queue.join() 實際上意味著等到佇列為空,再執行別的操作
import queue
import threading
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
exitFlag = 0
q = queue.Queue()
chrome_options = Options()
chrome_options.add_argument('--headless')
class scrapy_biquge():
def get_url(self):
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get('http://www.xbiquge.la/xuanhuanxiaoshuo/')
content = browser.find_element_by_class_name("r")
content = content.find_elements_by_xpath('//ul/li/span[@class="s2"]/a')
for i in content:
title = i.text
href = i.get_attribute('href')
print(title+'+'+href)
q.put(title+'+'+href)
browser.close()
browser.quit()
class myThread (threading.Thread): # 繼承父類threading.Thread
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self): #把要執行的程式碼寫到run函式裡面 執行緒在建立後會直接執行run函式
print(self.name)
while not exitFlag:
queueLock.acquire()
if not q.empty():
item = q.get()
queueLock.release()
title = item.split('+')[0]
href = item.split('+')[1]
get_content(title, href)
else:
print('資料全部結束')
queueLock.release()
def get_content(title, href):
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get(href)
browser.find_element_by_id('list')
novel_content = browser.find_elements_by_xpath('//dl/dd/a')
for novel in novel_content:
novel_dir = novel.text
novel_dir_href = novel.get_attribute('href')
print(title, novel_dir, novel_dir_href)
browser.close()
browser.quit()
if __name__ == '__main__':
# 所有url進佇列以後,啟動執行緒
scrapy_biquge().get_url()
threadList = ["Thread-1", "Thread-2", "Thread-3"]
queueLock = threading.Lock()
threads = []
threadID = 1
# 建立新執行緒
for tName in threadList:
thread = myThread(threadID, tName, q)
thread.start()
threads.append(thread)
threadID += 1
# 等待佇列清空
while not q.empty():
pass
# 通知執行緒是時候退出
exitFlag = 1
# 等待所有執行緒完成
for t in threads:
t.join()
print("Exiting Main Thread")
上面的例子用了FIFO佇列。當然你也可以換成其他型別的佇列.
LifoQueue
後進先出
Priority Queue
優先佇列
Python多程序,multiprocessing,下次使用多程序跑這個程式碼。
參考: https://cuiqingcai.com/3325.html