Python爬虫類4.1—threading(マルチスレッド)使い方チュートリアル


Python爬虫類4.1—threading[マルチスレッド]使い方チュートリアル
  • 総説
  • マルチスレッド爬虫
  • マルチスレッド紹介
  • threadingモジュール紹介
  • Threadクラスの使用
  • マルチスレッド共有グローバル変数問題
  • ロック機構
  • ロック版生産者と消費者モデル
  • Condition版生産者と消費者モデル
  • Queueスレッドセキュリティキュー
  • 使用例
  • 単一スレッドは表情パケットを抽出し、インスタンスコードは以下の通りである:
  • マルチスレッドは表情パケットを抽出し、インスタンスコードは以下の通りである:
  • GILグローバルインタプリタロック
  • その他のブログリンク
  • 総括する
    このシリーズのドキュメントは、Python爬虫類技術の学習を簡単にチュートリアルし、自分の技術知識を強固にすると同時に、うっかりしてちょうどあなたに役に立つとしたらもっといいです.Pythonバージョンは3.7.4です
    前の記事では,ネットワークリクエスト(urllib,requests),データ抽出(beautiful,xpath,正則),データストレージ(json,csv)の学習を記録し,次にマルチスレッドの学習を行う.
    マルチスレッド爬虫類
    画像をダウンロードするなど、時間のかかる操作なので、以前の同期方式でダウンロードすると、効率が非常に遅くなります.このとき,マルチスレッドを用いて画像をダウンロードすることを考えることができる.
    マルチスレッドの説明
    マルチスレッドは複数のタスクを同期的に完了するために、資源の使用を高めることによってシステムの効率を高めるために、スレッドは同じ時間に複数のタスクを完了する必要があるときにシナであり、最も簡単な比喩マルチスレッドは列車の各車両のように、2つのプロセスは列車である.車両が列車から離れるのは走ることができなくて、同じ理屈の列車は複数の車両を持つことができて、マルチスレッドの出現は効率を高めるためで、同時に彼の出現もいくつかの問題をもたらします.
    簡単に言えば、マルチスレッドはあなたがもともと1つのウィンドウを開いて這い出すことに相当し、10つのウィンドウを開いて這い出すことを制限します.
    threadingモジュールの紹介threadingモジュールは、Pythonでマルチスレッドを作成するために特別に提供されているモジュールです.threadingモジュールで最も一般的なクラスはThreadです.次の簡単なマルチスレッドプログラム:
    #      
    import threading
    import time
    
    
    def coding():
        """
        coding  
        :return:
        """
        for x in range(5):
            print('%s          ...' % x)
            time.sleep(1)
    
    
    def drawing():
        """
        drawing  
        :return:
        """
        for x in range(5):
            print('%s           ...' % x)
            time.sleep(1)
    
    
    def single_thread():
        """
             
        :return:
        """
        coding()
        drawing()
    
    
    def multi_thread():
        """
             
        :return:
        """
        #     
        #   :target      ,     
        t1 = threading.Thread(target=coding, name='coding')
        t2 = threading.Thread(target=drawing, name='drawing')
        #     
        t1.start()
        t2.start()
    
    
    if __name__ == '__main__':
        # single_thread()
        multi_thread()
    

    スレッド数の表示:
        num = threading.enumerate()
        print(num)
    

    現在のプロセス名を表示:
        threading.current_thread()
    

    Threadクラスの使用
    スレッドコードをよりよくカプセル化するために、threadingモジュールの下のThreadクラスを使用して、このクラスから継承し、run()メソッドを実装すると、スレッドはrun()メソッドのコードを自動的に実行し、サンプルコードは以下の通りです.
    #      
    import threading
    import time
    
    
    class CodingThread(threading.Thread):
        """
              
        """
    
        def run(self):
            for x in range(5):
                print('%s          ...' % threading.current_thread())
                time.sleep(1)
    
    
    class DrawingThread(threading.Thread):
        """
             
        """
    
        def run(self):
            for x in range(5):
                print('%s           ...' % threading.current_thread())
                time.sleep(1)
    
    
    def multi_thread():
        t1 = CodingThread()
        t2 = DrawingThread()
    
        t1.start()
        t2.start()
    
    
    if __name__ == '__main__':
        multi_thread()
    

    マルチスレッド共有グローバル変数の問題
    マルチスレッドは同じプロセスで実行されるため、プロセス内のグローバル変数はすべてのスレッドで共有できます.これにより、スレッドの実行順序が無秩序であり、データエラーを引き起こす可能性があるため、問題が発生します.たとえば、次のコードがあります.
    #   threading 
    import threading
    
    #       
    VALUE = 0
    
    
    def add_value():
        """
            
        :return:
        """
        global VALUE
        for x in range(1000000):
            VALUE += 1
        print(VALUE)
    
    
    def main():
        for x in range(2):
            t = threading.Thread(target=add_value)
            t.start()
    
    
    if __name__ == '__main__':
        main()
    

    以上のコードの結果は、通常、次のようになります.
    1000000
    2000000
    

    しかし,マルチスレッド実行の不確実性のため,結果はランダムである可能性がある.
    ロックメカニズム
    上記の問題を解決するために、マルチスレッド実行の不確実性のため、threadingライブラリはLock ロック機構を追加して処理し、あるスレッドがグローバル変数を修正すると、この変数 は他のスレッドを修正することを許さず、現在のスレッドがこの変数を修正したことを知ってから 解放を行い、その後、他のスレッドが修正できるようになる.これにより、データのセキュリティが保証されます.上記のコードを次のように変更します.
    #   threading 
    import threading
    
    #       
    VALUE = 0
    #    
    gLock = threading.Lock()
    
    
    def add_value():
        """
            
        :return:
        """
        global VALUE
        #   
        gLock.acquire()
        for x in range(1000000):
            VALUE += 1
        #   
        gLock.release()
        print(VALUE)
    
    
    def main():
        for x in range(2):
            t = threading.Thread(target=add_value)
            t.start()
    
    
    if __name__ == '__main__':
        main()
    

    ロック版生産者と消費者モデル
    生産者と消費者モデルの場合,マルチスレッド開発でよく見られるモデルである.生産者のスレッドは、いくつかのデータを生産し、中間の変数に格納するために使用されます.消費者はこの中間変数からデータを取り出して消費するが、中間変数を使用するため、中間変数は常にいくつかのグローバル変数であるため、ロックを使用してデータの完全性を保証する必要がある.以下に、threading.Lock()ロックを使用して「生産者および消費者モード」を実装する例を示す.
    #      
    import random
    import threading
    import time
    
    gMoney = 1000
    gTimes = 0
    #    
    gLock = threading.Lock()
    
    
    class Producer(threading.Thread):
        """
           
        """
    
        def run(self):
            global gMoney
            global gTimes
            while True:
                money = random.randint(100, 1000)
                gLock.acquire()
                #      10 
                if gTimes >= 10:
                    gLock.release()
                    break
                gMoney += money
                print('%s   %d  ,  %d  ' % (threading.current_thread(), money, gMoney))
                gTimes += 1
                gLock.release()
                time.sleep(0.5)
    
    
    class Consumer(threading.Thread):
        """
           
        """
    
        def run(self):
            global gMoney
            while True:
                money = random.randint(100, 1000)
                gLock.acquire()
                if gMoney >= money:
                    gMoney -= money
                    print('%s   %d  ,  %d  ' % (threading.current_thread(), money, gMoney))
                else:
                    if gTimes >= 10:
                        gLock.release()
                        break
                    print("%s        ,   " % threading.current_thread())
                gLock.release()
                time.sleep(0.5)
    
    
    def main():
        #        
        for x in range(3):
            t = Consumer(name='     %d' % x)
            t.start()
        #        
        for x in range(5):
            t = Producer(name='     %d' % x)
            t.start()
    
    
    if __name__ == '__main__':
        main()
    

    Condition版生産者と消費者モデルLock()版の生産者と消費者モデルは正常に動作することができるが、不足があり、消費者の中では、常にwhile Trueの死循環と鍵をかける方法でお金が足りないと判断し、鍵をかけるのはCPU資源の行為である.したがって、この方法は最良ではなく、threading.Conditionを用いて実現されるより良い方法がある.threading.Conditionは、データがないときにブロックされるなどの状態であってもよい.適切なデータがあれば、notifyに関連する関数を使用して、他の待機状態にあるスレッドに通知することもでき、無駄なロックやロック解除の操作をすることなく、プログラムのパフォーマンスを向上させることができます.
    まずthreading.Conditionに関する関数を紹介します.threading.Conditionthreading.Lockに似ていて、すべてのデータを修正するときに鍵をかけることもできますし、修正が終わったらロックを解除することもできます.一般的な関数について簡単に説明します.
  • acquire:施錠
  • release:ロック解除
  • wait:現在のスレッドを待機状態にし、ロックを解除します.他のスレッドによりnotifyおよびnotify_allの関数を使用して起動することができ、起動後もロックを待機し続け、ロック後も後続のコード
  • を実行し続ける.
  • notify:待機中のスレッドを通知します.デフォルトは1番目の待機スレッド
  • です.
  • notify_all:待機中のすべてのスレッドに通知します.notifyおよびnotify_allはロックを解除することなく、releaseの前に
  • を取り外す必要がある.Condition版生産者と消費者モデルのサンプルコードは以下の通りである.
    #      
    import random
    import threading
    import time
    
    gMoney = 1000
    gTimes = 0
    #   Condition
    gCondition = threading.Condition()
    
    
    class Producer(threading.Thread):
        """
           
        """
    
        def run(self):
            global gMoney
            global gTimes
            while True:
                money = random.randint(100, 1000)
                gCondition.acquire()
                #      10 
                if gTimes >= 10:
                    gCondition.release()
                    break
                gMoney += money
                print('%s   %d  ,  %d  ' % (threading.current_thread(), money, gMoney))
                gTimes += 1
                gCondition.notify_all()
                gCondition.release()
                time.sleep(0.5)
    
    
    class Consumer(threading.Thread):
        """
           
        """
    
        def run(self):
            global gMoney
            while True:
                money = random.randint(100, 1000)
                gCondition.acquire()
                while gMoney < money:
                    if gTimes >= 10:
                        gCondition.release()
                        return
                    print("%s        ,   " % threading.current_thread())
    
                    gCondition.wait()
                gMoney -= money
                print('%s   %d  ,  %d  ' % (threading.current_thread(), money, gMoney))
                gCondition.release()
                time.sleep(0.5)
    
    
    def main():
        #        
        for x in range(3):
            t = Consumer(name='     %d' % x)
            t.start()
        #        
        for x in range(5):
            t = Producer(name='     %d' % x)
            t.start()
    
    
    if __name__ == '__main__':
        main()
    

    Queueスレッドセキュリティキュー
    スレッドでは、いくつかのグローバル変数にアクセスし、ロックをかけるのはよくあるプロセスです.モグキューにデータを格納したい場合は、Pythonにはqueueモジュールというスレッドセキュリティモジュールが内蔵されています.Pythonのqueueモジュールには、FIFO(先進先出)キューQueue、LIFO(後入先出)キューLifoQueuを含む同期的でスレッドの安全なペアが提供されています.これらのキューは,原理(原子操作,すなわち,やらないか,全部やり終えるかと理解できる)を実現し,マルチスレッドで直接使用できる.スレッド間の同期は、キューを使用して実行できます.関連する関数は次のとおりです.
  • 初期化Queue(maxsize):先進的な先頭キュー
  • を作成する.
  • qsize():戻りキューのサイズ
  • empty():キューが空かどうかを判断する
  • full():キューが
  • で満たされているかどうかを判断する
  • get():最後のデータ
  • をキューから取得する.
  • put():1つのデータをキューに入れる
  • 使用コードの例:
    #      
    import threading
    import time
    from queue import Queue
    
    
    def set_value(q):
        """
            
        :param q:
        :return:
        """
        index = 1
        while True:
            q.put(index)
            index += 1
            time.sleep(3)
    
    
    def get_value(q):
        """
             
        :param q:
        :return:
        """
        while True:
            print(q.get())
            # time.sleep(4)
            # print("qsize:", q.qsize())
    
    
    def main():
        """
           
        :return:
        """
        q = Queue(5)
        t1 = threading.Thread(target=set_value, args=[q])
        t2 = threading.Thread(target=get_value, args=[q])
    
        t1.start()
        t2.start()
    
    
    if __name__ == '__main__':
        main()
    

    インスタンスの使用
    単一スレッドで表情パケットを取得します.インスタンスコードは次のとおりです.
    #      
    import os
    import re
    import requests
    from lxml import etree
    
    
    def parse_page(url):
        """
                
        :param url: 
        :return: 
        """
        #        
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
        }
        req = requests.get(url=url, headers=headers)
        html = req.text
        tree = etree.HTML(html)
        imgs = tree.xpath('//div[@class="page-content text-center"]//img[@class!="gif"]')
        for img in imgs:
            img_url = img.get('data-original')
            alt = img.get('alt')
            alt = re.sub(r'[\??\..,!!]]', '', alt)
            suffix = os.path.splitext(img_url)[1]
            file_name = alt + suffix
            req_img = requests.get(url=img_url, headers=headers)
            with open('images/' + file_name, 'wb') as fp:
                fp.write(req_img.content)
            print(file_name)
    
    
    def main():
        """
           
        :return: 
        """
        for x in range(1, 101):
            print(" %d     ..." % x)
            url = 'http://www.doutula.com/photo/list/?page=%d' % x
            parse_page(url)
            print(" %d     ..." % x)
    
    
    if __name__ == '__main__':
        main()
    

    マルチスレッドは、次のようなインスタンスコードで表情パケットを取得します.
    #      
    import os
    import re
    import threading
    from queue import Queue
    import requests
    from lxml import etree
    
    
    class Producer(threading.Thread):
        """
            -          
        """
    
        def __init__(self, page_queue, img_queue):
            super(Producer, self).__init__()
            self.page_queue = page_queue
            self.img_queue = img_queue
    
        def run(self):
            while True:
                if self.page_queue.empty():
                    break
                url = self.page_queue.get()
                self.parse_page(url)
    
        def parse_page(self, url):
            """
                    
            :param url:
            :return:
            """
            #        
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
            }
            req = requests.get(url=url, headers=headers)
            html = req.text
            tree = etree.HTML(html)
            imgs = tree.xpath('//div[@class="page-content text-center"]//img[@class!="gif"]')
            for img in imgs:
                img_url = img.get('data-original')
                alt = img.get('alt')
                alt = re.sub(r'[\??\..,!!\*]]', '', alt)
                suffix = os.path.splitext(img_url)[1]
                file_name = alt + suffix
                self.img_queue.put((img_url, file_name))
    
    
    class Consumer(threading.Thread):
        """
            -        
        """
    
        def __init__(self, page_queue, img_queue):
            super(Consumer, self).__init__()
            self.page_queue = page_queue
            self.img_queue = img_queue
    
        def run(self):
            #        
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
            }
            while True:
                if self.img_queue.empty() and self.page_queue.empty():
                    break
                img_url, file_name = self.img_queue.get()
                req_img = requests.get(url=img_url, headers=headers)
                with open('images/' + file_name, 'wb') as fp:
                    fp.write(req_img.content)
                print(file_name + '    ...')
    
    
    def main():
        """
           
        :return:
        """
        page_queue = Queue(100)
        img_queue = Queue(1000)
        for x in range(1, 101):
            url = 'http://www.doutula.com/photo/list/?page=%d' % x
            page_queue.put(url)
    
        #        
        for x in range(6):
            t = Producer(page_queue=page_queue, img_queue=img_queue)
            t.start()
    
        #        
        for x in range(4):
            t = Consumer(page_queue=page_queue, img_queue=img_queue)
            t.start()
    
    
    if __name__ == '__main__':
        main()
    

    GILグローバルインタプリタロック
    Pythonが持参した解釈器はCPythonです.CPythonインタプリタのマルチスレッドは実際には1つのホームのマルチスレッドである(マルチコアCPUでは1コアしか利用できず、マルチコアは利用できない).同じ時刻に1つのスレッドのみが実行され、同じ時刻に1つのスレッドのみが実行されることを保証するために、CPython解釈器にはGILという機能があり、グローバル解釈器ロックと呼ばれています.この解釈器ロックは必要です.CPython解釈器のメモリ管理はスレッドセキュリティではありません.もちろん、CPython解釈器のほかに、GILロックがない解釈器もあります.以下を参照してください.
  • Jpython:Javaで実現されたPythonインタプリタで、GILロックは存在しません.詳細については、以下を参照してください.https://zh.wikipedia.org/wiki/Jpython
  • IronPython:.NETで実現されたPython解釈器では、GILロックは存在しない.詳細については、以下を参照してください.https://zh.wikipedia.org/wiki/IronPython
  • PyPy:Pythonで実現されたPythonインタプリタには、GILロックが存在する.詳細については、以下を参照してください.https://zh.wikipedia.org/wiki/PyPy

  • GILは偽のマルチスレッドであるが,IO操作を処理する際に効率を向上させることができる.CPU計算操作ではマルチスレッドの使用は推奨されず、マルチプロセスの使用を推奨します.
    その他のブログリンク
  • Python爬虫1.1—urllib基礎用法教程
  • Python爬虫類1.2—urllib高級用法教程
  • Python爬虫1.3-requests基礎用法教程
  • Python爬虫類1.4-requests上級用法教程
  • Python爬虫2.1—BeautifulSoup用法教程
  • Python爬虫類2.2—xpath用法教程
  • Python爬虫3.1—json用法教程
  • Python爬虫類3.2—csv用法教程
  • Python爬虫3.3—txt用法教程