python爬蟲入門(四)利用多線程爬蟲


 多線程爬蟲

 先回顧前面學過的一些知識

1.一個cpu一次只能執行一個任務,多個cpu同時可以執行多個任務
2.一個cpu一次只能執行一個進程,其它進程處於非運行狀態
3.進程里包含的執行單元叫線程,一個進程可以包含多個線程
4.一個進程的內存空間是共享的,每個進程里的線程都可以使用這個共享空間
5.一個線程在使用這個共享空間的時候,其它的線程必須等待(阻塞狀態)
6.互斥鎖作用就是防止多個線程同時使用這塊內存空間,先使用的線程會將空間上鎖,其它的線程處於等待狀態。等鎖開了才能進
7.進程:表示程序的一次執行
8.線程:CPU運算的基本調度單位
9.GIL(全局鎖):python里的執行通行證,而且只有一個。拿到通行證的線程就可以進入CPU執行任務。沒有GIL的線程就不能執行任務
10.python的多線程適用於大量密集的I/O處理
11.python的多進程適用於大量的密集並行計算

 

 多線程爬取糗事百科

#!/usr/bin/env python
# -*- coding:utf-8 -*-

# 使用了線程庫
import threading
# 隊列
from Queue import Queue
# 解析庫
from lxml import etree
# 請求處理
import requests
# json處理
import json
import time

class ThreadCrawl(threading.Thread):
    def __init__(self, threadName, pageQueue, dataQueue):
        #threading.Thread.__init__(self)
        # 調用父類初始化方法
        super(ThreadCrawl, self).__init__()
        # 線程名
        self.threadName = threadName
        # 頁碼隊列
        self.pageQueue = pageQueue
        # 數據隊列
        self.dataQueue = dataQueue
        # 請求報頭
        self.headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36'}

    def run(self):
        print "啟動 " + self.threadName
        while not CRAWL_EXIT:
            try:
                # 取出一個數字,先進先出
                # 可選參數block,默認值為True
                #1. 如果對列為空,block為True的話,不會結束,會進入阻塞狀態,直到隊列有新的數據
                #2. 如果隊列為空,block為False的話,就彈出一個Queue.empty()異常,
                page = self.pageQueue.get(False)
                url = "http://www.qiushibaike.com/8hr/page/" + str(page) +"/"
                #print url
                content = requests.get(url, headers = self.headers).text
                time.sleep(1)
                self.dataQueue.put(content)
                #print len(content)
            except:
                pass
        print "結束 " + self.threadName

class ThreadParse(threading.Thread):
    def __init__(self, threadName, dataQueue, filename, lock):
        super(ThreadParse, self).__init__()
        # 線程名
        self.threadName = threadName
        # 數據隊列
        self.dataQueue = dataQueue
        # 保存解析后數據的文件名
        self.filename = filename
        #
        self.lock = lock

    def run(self):
        print "啟動" + self.threadName
        while not PARSE_EXIT:
            try:
                html = self.dataQueue.get(False)
                self.parse(html)
            except:
                pass
        print "退出" + self.threadName

    def parse(self, html):
        # 解析為HTML DOM
        html = etree.HTML(html)

        node_list = html.xpath('//div[contains(@id, "qiushi_tag")]')

        for node in node_list:
            # xpath返回的列表,這個列表就這一個參數,用索引方式取出來,用戶名
            username = node.xpath('./div/a/@title')[0]
            # 圖片連接
            image = node.xpath('.//div[@class="thumb"]//@src')#[0]
            # 取出標簽下的內容,段子內容
            content = node.xpath('.//div[@class="content"]/span')[0].text
            # 取出標簽里包含的內容,點贊
            zan = node.xpath('.//i')[0].text
            # 評論
            comments = node.xpath('.//i')[1].text

            items = {
                "username" : username,
                "image" : image,
                "content" : content,
                "zan" : zan,
                "comments" : comments
            }

            # with 后面有兩個必須執行的操作:__enter__ 和 _exit__
            # 不管里面的操作結果如何,都會執行打開、關閉
            # 打開鎖、處理內容、釋放鎖
            with self.lock:
                # 寫入存儲的解析后的數據
                self.filename.write(json.dumps(items, ensure_ascii = False).encode("utf-8") + "\n")

CRAWL_EXIT = False
PARSE_EXIT = False


def main():
    # 頁碼的隊列,表示20個頁面
    pageQueue = Queue(20)
    # 放入1~10的數字,先進先出
    for i in range(1, 21):
        pageQueue.put(i)

    # 采集結果(每頁的HTML源碼)的數據隊列,參數為空表示不限制
    dataQueue = Queue()

    filename = open("duanzi.json", "a")
    # 創建鎖
    lock = threading.Lock()

    # 三個采集線程的名字
    crawlList = ["采集線程1號", "采集線程2號", "采集線程3號"]
    # 存儲三個采集線程的列表集合
    threadcrawl = []
    for threadName in crawlList:
        thread = ThreadCrawl(threadName, pageQueue, dataQueue)
        thread.start()
        threadcrawl.append(thread)


    # 三個解析線程的名字
    parseList = ["解析線程1號","解析線程2號","解析線程3號"]
    # 存儲三個解析線程
    threadparse = []
    for threadName in parseList:
        thread = ThreadParse(threadName, dataQueue, filename, lock)
        thread.start()
        threadparse.append(thread)

    # 等待pageQueue隊列為空,也就是等待之前的操作執行完畢
    while not pageQueue.empty():
        pass

    # 如果pageQueue為空,采集線程退出循環
    global CRAWL_EXIT
    CRAWL_EXIT = True

    print "pageQueue為空"

    for thread in threadcrawl:
        thread.join()
        print "1"

    while not dataQueue.empty():
        pass

    global PARSE_EXIT
    PARSE_EXIT = True

    for thread in threadparse:
        thread.join()
        print "2"

    with lock:
        # 關閉文件
        filename.close()
    print "謝謝使用!"

if __name__ == "__main__":
    main()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM