1. 程式人生 > >python爬蟲進階使用多執行緒爬取小說

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