高性能コルーチンを構築するために使用される爬虫類
I.はじめに
他のIO集約型のタスクを実行し、プログラムがしばしばIOを待つのでブロックしました。我々は要求にライブラリの要求を使用して、サイトの応答速度が遅すぎる場合、プログラムはサイトの応答を待っている場合たとえば、ウェブクローラは、そのクロール効率につながることは非常に、非常に低いです。この問題を解決するために、我々は協会のプロセスをスピードアップするために非同期的にPythonの方法を模索するつもりだ、この方法はIO集約型のタスクのために非常に有効です。ウェブクローラへの応用として、クロール効率はさらに倍増持ち上げることができます。ここで使用されるように、非同期/のawait実装するために、Pythonの3.5以降が必要です。
第二に、コンセプトの紹介
1.閉塞
コンピューティングリソースがプログラム状態のために必要とされる場合には、中断されていません。自分の中に完了するための操作がブロックされているプログラムの動作に呼ばれる他の事を、し続けることができないためにプログラムが待っています。フォームをブロック:ネットワークI / Oブロック、ディスクI / Oブロック、ブロックおよび他のユーザ入力。CPUコンテキストスイッチを含む、マルチコアCPUは、コアのコンテキストスイッチ動作は利用できない行っています。
2.ノンブロッキング
それ自体がアクションプログラムを待っているがブロックされていないが、それはノンブロッキングプログラムの動作にあると呼ばれる他の事を、し続けることができます。時間のかかる操作の閉塞によって引き起こされる低効率の、私たちは効率を向上させるために、非ブロッキングにそれを作るため、非ブロッキングが、ために障害物の存在があります。
3.同期
実装プロセスでは、タスクを完了するために、異なるプログラム単位は、これらの手順を同期ユニットに行われていることを言って、協調して通信のいくつかの種類に依存する必要があります。このような在庫商品ショッピングシステムを更新するとして、あなたは別の更新要求は実行の順序を強制するためにキューに入れられたように、通信信号として「ロック」を使用する必要があり、それは、インベントリ操作が同期されて更新されます。同期は秩序を意味します。
4.非同期
異なるプログラムユニット間の調整なしでプロセス間通信、タスクは、非同期であってもよい無関係プログラム要素間、方法を完了することができます。爬虫類ダウンロードページ。プログラムをダウンロードすると、スケジューラを呼び出した後は、ダウンロードタスクの動作を調整するために通信を維持することなく、他のタスクをスケジュールすることができます。別のページには、ダウンロードし保存し、他の操作は独立しており、コーディネート予告なしにします。これらの非同期操作完了時間は、非同期が乱れ、決定されません。
5.コルーチン
コルーチンは、また、マイクロスレッドと呼ばれ、細断処理コルーチンは、ユーザ軽量スレッド状態です。これは、独自のレジスタコンテキストとスタックを持っています。コルーチンは、ハンドオーバ、レジスタコンテキストの退避を予定し、別の場所にスタックすると戻って切断した場合、コンテキストは、以前に保存されたレジスタとスタックを復元します。したがってコルーチンはそれぞれリエントラント中の最後の呼び出し、すなわち、局所的な状態の全ての特定の組合せ、通話状態に入るの等価物として残すことができます。コルーチンマルチプロセスに対して、コンテキストスイッチのオーバーヘッドなしにスレッドは、プログラミングモデルのオーバーヘッドなしロックおよびアトミック操作の同期は非常に簡単であり、本質的にコルーチンに単一プロセスです。
Webクローラーのシナリオの下で、我々は応答を取得するためにいくつかの時間を待つように要求した後に発行されますが、待機中のプロセスで実際には、応答が治療を継続するためにスイッチバックする前に待機することであった後のプログラムは、他の多くのことを行うことができますので、あなたはをフルに活用することができますCPUや他のリソース、およびこれは、非同期コルーチンの利点であります
6.非同期コルーチンasyncio
asyncioより最も一般的に使用されるライブラリを使用してPythonでコルーチン。
- event_loop:イベントループ、無限ループの等価な、我々は条件が発生した場合、それは、対応する治療を呼び出します。このイベントループにいくつかの機能を登録することができます。
- コルーチン:中国語の翻訳は、協会を代表して、コルーチンと呼ばれているが、我々は、それがサイクルイベントを呼び出すことになるサイクルタイムにコルーチンオブジェクトを登録することができ、多くの場合、Pythonでのプロセスオブジェクトの種類を指します。私たちは、あなたが呼び出したとき、すぐに実行されませんメソッドを定義するために非同期キーワードを使用しますが、コルーチンオブジェクトを返すことができます。
- タスク:さらにコルーチンオブジェクトにカプセル化されたタスクは、各タスクのステータスを含んでいます。
- 未来:将来の業績へのガイドまたは実行するタスクが行われず、タスクは実際には本質的な違いはありません。
また、我々はまた、それだけで、特にコルーチンを定義するために、Pythonの3.5から登場し、非同期/待つのキーワードを知っている必要があります。ここで、非同期コルーチンを定義し、保留中の実行をブロックするための方法を待っています。
第三に、コードの実装
1.まず、我々は単純な実装フラスコサーバーを使用します
あなたはフラスコをインストールしない場合は、インストールするには、次のコマンドを実行することができます。
PIP3フラスコをインストール
次のようにサーバーコードを記述することです:
フラスコインポートフラスコ のインポート時 のAppは、フラスコ(= __name__ ) @ app.route(' / ' ) DEFの:インデックス() #のIO処理されたアナログ time.sleep(2 ) リターン ' こんにちは' IF __name__ == ' __main__ ' : #をマルチスレッド起動モード app.run(ねじ= TRUE)
ここでは、この方法は、最初の2秒以上かかり、各要求に対して、すなわち、インタフェース、結果を返し、その後2秒スリープ()メソッド睡眠を呼び出し、サービスフラスコ、正面玄関であるインデックス()メソッドを定義私たちは、低速サービスのインターフェイスをシミュレートします。
2. asyncioテスト
import asyncio import requests import time start = time.time() async def get(url): return requests.get(url) async def request(): url = 'http://127.0.0.1:5000' print('Waiting for ', url) response = await get(url) print('Get response from ', url, 'Result:', response.text) tasks = [asyncio.ensure_future(request()) for _ in range(5)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print( 'Cost time:', end - start )
在这里我们还是创建了五个 task,然后将 task 列表传给 wait() 方法并注册到时间循环中执行
输出结果:
Waiting for http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: hello Waiting for http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: hello Waiting for http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: hello Waiting for http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: hello Waiting for http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: hello Cost time: 10.043976068496704
可以发现和正常的请求并没有什么两样,依然还是顺次执行的,耗时 10 秒,平均一个请求耗时 2 秒,说好的异步处理呢?其实,要实现异步处理,我们得先要有挂起的操作,当一个任务需要等待 IO 结果的时候,可以挂起当前任务,转而去执行其他任务,这样我们才能充分利用好资源,上面方法都是一本正经的串行走下来,连个挂起都没有,怎么可能实现异步?想太多了。要实现异步,接下来我们再了解一下 await 的用法,使用 await 可以将耗时等待的操作挂起,让出控制权。当协程执行的时候遇到 await,时间循环就会将本协程挂起,转而去执行别的协程,直到其他的协程挂起或执行完毕。
仅仅将涉及 IO 操作的代码封装到 async 修饰的方法里面是不可行的!我们必须要使用支持异步操作的请求方式才可以实现真正的异步,所以这里就需要 aiohttp 派上用场了。
3.使用aiohttp
aiohttp 是一个支持异步请求的库,利用它和 asyncio 配合我们可以非常方便地实现异步请求操作。
安装方式如下:
pip3 install aiohttp
官方文档链接为:https://aiohttp.readthedocs.io/,它分为两部分,一部分是 Client,一部分是 Server,详细的内容可以参考官方文档。
import aiohttp import asyncio import time start = time.time() async def get(url): session = aiohttp.ClientSession() # 实例化Clientsession()对象 response = await session.get(url) # 支持get(),post(),params/data,proxy='..'等参数 result = await response.text() # text()字符串,json()json类型,read()二进制 await session.close() # 关闭资源,使用with语句可以自动释放 return result async def request(): url = 'http://127.0.0.1:5000' print('Waiting fro ', url) # result = await get(url) result = await get_w(url) print('Get response from ', url, 'Result:', result) tasks = [asyncio.ensure_future(request()) for _ in range(5)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks)) end = time.time() print('Cost time:', end - start)
输出结果如下:
Waiting fro http://127.0.0.1:5000 Waiting fro http://127.0.0.1:5000 Waiting fro http://127.0.0.1:5000 Waiting fro http://127.0.0.1:5000 Waiting fro http://127.0.0.1:5000 Get response from http://127.0.0.1:5000 Result: hello Get response from http://127.0.0.1:5000 Result: hello Get response from http://127.0.0.1:5000 Result: hello Get response from http://127.0.0.1:5000 Result: hello Get response from http://127.0.0.1:5000 Result: hello Cost time: 2.012542963027954
开始运行时,时间循环会运行第一个 task,针对第一个 task 来说,当执行到第一个 await 跟着的 get() 方法时,它被挂起,但这个 get() 方法第一步的执行是非阻塞的,挂起之后立马被唤醒,所以立即又进入执行,创建了 ClientSession 对象,接着遇到了第二个 await,调用了 session.get() 请求方法,然后就被挂起了,由于请求需要耗时很久,所以一直没有被唤醒,好第一个 task 被挂起了,那接下来该怎么办呢?事件循环会寻找当前未被挂起的协程继续执行,于是就转而执行第二个 task 了,也是一样的流程操作,直到执行了第五个 task 的 session.get() 方法之后,全部的 task 都被挂起了。所有 task 都已经处于挂起状态,那咋办?只好等待了。2 秒之后,几个请求几乎同时都有了响应,然后几个 task 也被唤醒接着执行,输出请求结果,最后耗时,2秒!
上面的代码也可以配合with使用,
# 使用with语句 async def get_w(rul): async with aiohttp.ClientSession() as session: async with await session.get(rul) as response: result = await response.text() return result
4.与多进程进行结合使用aiomultiprocess,Python3.6以上版本适用
安装方式:
pip3 install aiomultiprocess
import asyncio import aiohttp import time from aiomultiprocess import Pool start = time.time() async def get(url): session = aiohttp.ClientSession() response = await session.get(url) result = await response.text() session.close() return result async def request(): url = 'http://127.0.0.1:5000' urls = [url for _ in range(100)] async with Pool() as pool: result = await pool.map(get, urls) return result coroutine = request() task = asyncio.ensure_future(coroutine) loop = asyncio.get_event_loop() loop.run_until_complete(task) end = time.time() print('Cost time:', end - start)
当然最后的耗时结果其实和异步是差不多的
做爬取的时候遇到的情况千变万化,一方面我们使用异步协程来防止阻塞,另一方面我们使用 multiprocessing 来利用多核成倍加速,节省时间其实还是非常可观的。