爬蟲第六篇

爬蟲第六篇

python採用 多進程/多線程/協程 寫爬蟲

從操作系統的角度:

進程和線程,都是一種CPU的執行單元。

進程:表示一個程序的上下文執行活動(打開、執行、保存…)

線程:進程執行程序時候的最小調度單位(執行a,執行b…)

一個程序至少有一個進程,一個進程至少有一個線程。

並行:多個CPU核心,不同的程序就分配給不同的CPU來運行。可以讓多個程序同時執行

併發:單個CPU核心,在一個時間切片裏一次只能運行一個程序,如果需要運行多個程序,需要在多個程序間來回切換

多進程/多線程:表示可以同時執行多個任務,進程和線程的調度是由操作系統自動完成。

進程:每個進程都有自己獨立的內存空間,不同進程之間的內存空間不共享。
進程之間的通信有操作系統傳遞,導致通訊效率低,切換開銷大。

線程:一個進程可以有多個線程,所有線程共享進程的內存空間,通訊效率高,切換開銷小。

共享意味着競爭,導致數據不安全,爲了保護內存空間的數據安全,引入"互斥鎖"。

一個線程在訪問內存空間的時候,其他線程不允許訪問,必須等待之前的線程訪問結束,才能使用這個內存空間。

互斥鎖:一種安全有序的讓多個線程訪問內存空間的機制。

Python的多線程:

GIL 全局解釋器鎖:線程的執行權限,在Python的進程裏只有一個GIL。

一個線程需要執行任務,必須獲取GIL。

好處:直接杜絕了多個線程訪問內存空間的安全問題。
壞處:Python的多線程不是真正多線程,不能充分利用多核CPU的資源。

但是,在I/O阻塞的時候,解釋器會釋放GIL。

所以:

多進程:密集CPU任務,需要充分使用多核CPU資源(服務器,大量的並行計算)的時候,用多進程。 multiprocessing
缺陷:多個進程之間通信成本高,切換開銷大。

多線程:密集I/O任務(網絡I/O,磁盤I/O,數據庫I/O)使用多線程合適。
threading.Thread、multiprocessing.dummy
缺陷:同一個時間切片只能運行一個線程,不能做到高並行,但是可以做到高併發。

協程:又稱微線程,在單線程上執行多個任務,用函數切換,開銷極小。不通過操作系統調度,沒有進程、線程的切換開銷。genvent,monkey.patchall

多線程請求返回是無序的,那個線程有數據返回就處理那個線程,而協程返回的數據是有序的。

缺陷:單線程執行,處理密集CPU和本地磁盤IO的時候,性能較低。處理網絡I/O性能還是比較高.

多線程多進程爬蟲

from multiprocessing import Process, Queue

# 多進程
p = Process(target=func, name, args, kwargs)

# 隊列
q = Queue()
q.put(url)
q.get()
q.empty()

多進程/多線程爬蟲的流程圖

在這裏插入圖片描述

import requests
from multiprocessing import Queue
from threading import Thread
from lxml import etree
import time 

class XiaomiSpider:
    def __init__(self):
        self.baseurl = 'http://app.mi.com/category/12#page='
        self.mainurl = 'http://app.mi.com'
        self.headers = {"User-Agent":"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)"}
        # URL隊列
        self.urlQueue = Queue()
        # 解析隊列
        self.parseQueue = Queue()
    
    # URL入隊列
    def getUrl(self):
        for page in range(20):
            url = self.baseurl + str(page)
            # 把拼接的url放到url隊列中
            self.urlQueue.put(url)

    # 採集線程函數,get出URL發請求,把html給解析隊列
    def getHtml(self):
        while True:
            # 先判斷隊列是否爲空
            if not self.urlQueue.empty():
                url = self.urlQueue.get()
                # 三步走
                res = requests.get(url,headers=self.headers)
                res.encoding = "utf-8"
                html = res.text
                # 把html放到解析隊列
                self.parseQueue.put(html)
            else:
                break
            
    # 解析線程函數,get出html源碼,提取並處理數據
    def getData(self):
        while True:
            if not self.parseQueue.empty():
                html = self.parseQueue.get()
                # 創建解析對象,調用xpath
                parseHtml = etree.HTML(html)
                # [li1對象,li2對象]
                baseList = parseHtml.xpath('//ul[@id="all-applist"]//li')
                for base in baseList:
                    # 應用名稱
                    name = base.xpath('./h5/a/text()')[0]
                    # 應用鏈接
                    link = self.mainurl + base.xpath('./h5/a/@href')[0]
                    d = {
                         "分類":"學習教育",
                         "名稱":name,
                         "鏈接":link,
                      }
                    with open("XM.json","a", encoding="utf-8") as f:
                        f.write(str(d)+'\n')    
            else:
                break
    # 主函數
    def workOn(self):
        # url先入隊列
        self.getUrl()
        # 存放所有采集線程對象和解析線程對象列表
        tList = []

        # 統一創建線程,採集和解析一起幹活
        for i in range(5):
            t1 = Thread(target=self.getHtml)
            t2 = Thread(target=self.getData)
            tList.append(t1)
            tList.append(t2)
            t1.start()
            t2.start()
            
        # 統一回收採集線程和解析線程
        for i in tList:
            i.join()
        
if __name__ == "__main__":
    start = time.time()
    spider = XiaomiSpider()
    spider.workOn()
    end = time.time()
    print("執行時間:%.2f" % (end - start))

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章