Python爬虫類4.1—threading(マルチスレッド)使い方チュートリアル
60361 ワード
Python爬虫類4.1—threading[マルチスレッド]使い方チュートリアル総説 マルチスレッド爬虫 マルチスレッド紹介 threadingモジュール紹介 Threadクラスの使用 マルチスレッド共有グローバル変数問題 ロック機構 ロック版生産者と消費者モデル Condition版生産者と消費者モデル Queueスレッドセキュリティキュー 使用例 単一スレッドは表情パケットを抽出し、インスタンスコードは以下の通りである: マルチスレッドは表情パケットを抽出し、インスタンスコードは以下の通りである: GILグローバルインタプリタロック その他のブログリンク 総括する
このシリーズのドキュメントは、Python爬虫類技術の学習を簡単にチュートリアルし、自分の技術知識を強固にすると同時に、うっかりしてちょうどあなたに役に立つとしたらもっといいです.Pythonバージョンは3.7.4です
前の記事では,ネットワークリクエスト(urllib,requests),データ抽出(beautiful,xpath,正則),データストレージ(json,csv)の学習を記録し,次にマルチスレッドの学習を行う.
マルチスレッド爬虫類
画像をダウンロードするなど、時間のかかる操作なので、以前の同期方式でダウンロードすると、効率が非常に遅くなります.このとき,マルチスレッドを用いて画像をダウンロードすることを考えることができる.
マルチスレッドの説明
マルチスレッドは複数のタスクを同期的に完了するために、資源の使用を高めることによってシステムの効率を高めるために、スレッドは同じ時間に複数のタスクを完了する必要があるときにシナであり、最も簡単な比喩マルチスレッドは列車の各車両のように、2つのプロセスは列車である.車両が列車から離れるのは走ることができなくて、同じ理屈の列車は複数の車両を持つことができて、マルチスレッドの出現は効率を高めるためで、同時に彼の出現もいくつかの問題をもたらします.
簡単に言えば、マルチスレッドはあなたがもともと1つのウィンドウを開いて這い出すことに相当し、10つのウィンドウを開いて這い出すことを制限します.
threadingモジュールの紹介
スレッド数の表示:
現在のプロセス名を表示:
Threadクラスの使用
スレッドコードをよりよくカプセル化するために、
マルチスレッド共有グローバル変数の問題
マルチスレッドは同じプロセスで実行されるため、プロセス内のグローバル変数はすべてのスレッドで共有できます.これにより、スレッドの実行順序が無秩序であり、データエラーを引き起こす可能性があるため、問題が発生します.たとえば、次のコードがあります.
以上のコードの結果は、通常、次のようになります.
しかし,マルチスレッド実行の不確実性のため,結果はランダムである可能性がある.
ロックメカニズム
上記の問題を解決するために、マルチスレッド実行の不確実性のため、
ロック版生産者と消費者モデル
生産者と消費者モデルの場合,マルチスレッド開発でよく見られるモデルである.生産者のスレッドは、いくつかのデータを生産し、中間の変数に格納するために使用されます.消費者はこの中間変数からデータを取り出して消費するが、中間変数を使用するため、中間変数は常にいくつかのグローバル変数であるため、ロックを使用してデータの完全性を保証する必要がある.以下に、
Condition版生産者と消費者モデル
まず を実行し続ける. です. を取り外す必要がある.
Queueスレッドセキュリティキュー
スレッドでは、いくつかのグローバル変数にアクセスし、ロックをかけるのはよくあるプロセスです.モグキューにデータを格納したい場合は、Pythonには初期化 を作成する. で満たされているかどうかを判断する をキューから取得する. 使用コードの例:
インスタンスの使用
単一スレッドで表情パケットを取得します.インスタンスコードは次のとおりです.
マルチスレッドは、次のようなインスタンスコードで表情パケットを取得します.
GILグローバルインタプリタロック
Pythonが持参した解釈器は
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用法教程
このシリーズのドキュメントは、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.Condition
はthreading.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計算操作ではマルチスレッドの使用は推奨されず、マルチプロセスの使用を推奨します.
その他のブログリンク