天天看點

python爬蟲進階使用多線程爬取小說Priority Queue優先隊列

Python多線程,thread标準庫。都說Python的多線程是雞肋,推薦使用多程序。

python爬蟲進階使用多線程爬取小說Priority Queue優先隊列

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")
           
python爬蟲進階使用多線程爬取小說Priority Queue優先隊列

上面的例子用了FIFO隊列。當然你也可以換成其他類型的隊列.

LifoQueue 

後進先出

Priority Queue

優先隊列

Python多程序,multiprocessing,下次使用多程序跑這個代碼。

參考: https://cuiqingcai.com/3325.html

python爬蟲進階使用多線程爬取小說Priority Queue優先隊列

繼續閱讀