高性能異步爬蟲

asyncio異步攜程模塊python

在python3.4以後新增了asyncio模塊,能夠幫咱們檢測IO(只能是網絡IO【HTTP鏈接就是網絡IO操做】),實現應用程序級別的切換(異步IO)。注意:asyncio只能發tcp級別的請求,不能發http協議。 flask

- 異步IO:所謂「異步 IO」,就是你發起一個 網絡IO 操做,卻不用等它結束,你能夠繼續作其餘事情,當它結束時,你會獲得通知。

  - 實現方式:單線程+協程實現異步IO操做。

  - 異步協程用法

    協程的實現,從 Python 3.4 開始,Python 中加入了協程的概念,但這個版本的協程仍是以生成器對象爲基礎的,在 Python 3.5 則增長了 async/await,使得協程的實現更加方便。首先須要瞭解下面幾個概念: 

event_loop:事件循環,至關於一個無限循環,咱們能夠把一些函數註冊到這個事件循環上,當知足條件發生的時候,就會調用對應的處理方法。

coroutine:中文翻譯叫協程,在 Python 中常指代爲協程對象類型,咱們能夠將協程對象註冊到時間循環中,它會被事件循環調用。咱們可使用 async 關鍵字來定義一個方法,這個方法在調用時不會當即被執行,而是返回一個協程對象。

task:任務,它是對協程對象的進一步封裝,包含了任務的各個狀態。

future:表明未來執行或沒有執行的任務的結果,實際上和 task 沒有本質區別。

  從 Python 3.5 出現的async/await 關鍵字,專門用於定義協程。其中,async 定義一個協程,await 用來掛起阻塞方法的執行。
相關概念

aiohttp異步網絡請求模塊服務器

  aiohttp是基於asyncio實現的HTTP框架,pip install aiohttp。 網絡

 1 import aiohttp
 2 
 3 #簡單的基本架構:
 4 async def request(url):
 5    async with aiohttp.ClientSession() as s:
 6        #s.get/post和requests中的get/post用法幾乎同樣:url,headers,data/prames
 7        #在s.get中若是使用代理操做:proxy="http://ip:port"
 8        async with await s.get(url) as response:
 9            #獲取字符串形式的響應數據:response.text()
10            #獲取byte類型的:response.read()
11            page_text = await response.text()
12            return page_text
aiohttp使用事例

單線程+多任務異步協程架構

  協程協程對象。使用async關鍵字修飾一個函數的定義(特殊的函數),當該特殊的函數被調用後,就能夠返回一個協程對象。當函數調用後,函數內部的實現語句不會被當即執行。協程就是一個用async關鍵字修飾的特殊函數。協程定義:async def func():pass,建立協程對象:c=func() app

 1 '''
 2 協程是一個用async關鍵字修飾的特殊函數;
 3 在這個特殊函數內不容許出現不支持異步模塊的代碼,不然異步非阻塞將失效;
 4 在特殊函數中遇到阻塞的代碼前必須使用await關鍵字手動掛起(後續示例)
 5 
 6 '''
 7 #定義一個協程
 8 async def func():
 9     print('一個協程對象')
10 
11 #建立協程對象
12 c=func()
協程/協程對象---async/await

   任務對象:本質上就是對協程對象進一步封裝,比如是一個特殊函數。任務對象task=asyncio.ensure_future(c)能夠綁定一個回調函數:task.add_done_callback(func_callback)-----func_callback回調函數只能接受task任務對象執行結果返回值,類比進程池/線程池中submit提交任務後的回調函數使用! 框架

 1 '''
 2 任務對象是對協程對象進行封裝後的一個特殊函數;
 3 任務對象能夠綁定一個回調函數;
 4 '''
 5 import  asyncio
 6 #定義一個協程
 7 async def func(n):
 8     print(n)
 9 
10 #定義任務對象的回調函數:只能接受以個參數,該參數爲任務對象執行結果返回值
11 def call_back(task):
12     print(task.result())
13 
14 #建立協程對象
15 c=func(-1)
16 
17 
18 #建立任務對象
19 task=asyncio.ensure_future(c)
20 #任務對象綁定回調函數
21 task.add_done_callback(call_back)
22 
23 # 建立多個任務對象併爲其綁定回調函數
24 task_list=[]
25 for i in range(5):
26     c=func(i)
27     task=asyncio.ensure_future(c)
28     task.add_done_callback(task)
29     task_list.append(task)
單個/多個任務對象建立與回調函數綁定

   事件循環:事件循環對象的建立loop=asyncio.get_event_loop()必須將任務對象註冊到事件循環對象中,自動開啓事件循環對象,在執行任務對象的時候是基於異步的。單個任務對象的註冊:loop.run_until_complete(task);多個任務對象的註冊:loop.run_and_complete(asyncio.wait(task_list))。 異步

 1 '''
 2 建立事件循環對象對任務對象進行註冊調用;
 3 註冊單個任務對象/註冊多個任務對象並自動調用執行;
 4 
 5 '''
 6 import  asyncio
 7 #定義一個協程
 8 import time
 9 
10 
11 async def func(n):
12     await asyncio.sleep(2)#必須使用支持異步模塊的代碼,同時在遇到阻塞的代碼前必須使用await手動掛起
13     print(n)
14     return n
15 
16 #定義任務對象的回調函數:只能接受以個參數,該參數爲任務對象執行結果返回值
17 def call_back(task):
18     print(task.result())
19 
20 #建立協程對象
21 c=func(-1)
22 
23 start=time.time()
24 #建立任務對象
25 task0=asyncio.ensure_future(c)
26 #任務對象綁定回調函數
27 task0.add_done_callback(call_back)
28 
29 # 建立多個任務對象併爲其綁定回調函數
30 task_list=[]
31 for i in range(5):
32     c=func(i)
33     task=asyncio.ensure_future(c)
34     task.add_done_callback(call_back)
35     task_list.append(task)
36 
37 #建立事件循環對象
38 loop=asyncio.get_event_loop()
39 #註冊單個任務對象
40 loop.run_until_complete(task0)
41 #註冊多個任務對象
42 loop.run_until_complete(asyncio.wait(task_list))
43 
44 loop.close()
45 print(time.time()-start)#2.00264573097229
事件循環對象及任務對象註冊

   注意事項:async

  •   在特殊函數(協程)中不能出現不支持異步模塊對應的代碼;
  •   在特殊函數(協程)遇到阻塞的代碼必須添加關鍵字await對其手動掛起;
  •   若是將多個任務對象註冊到事件循環對象中,先將多個任務對象添加到一個列表中,在註冊時必須使用asyncio.wait()將列表中的任務對象掛起後再進行註冊!

 單線程多任務異步爬蟲實例tcp

  flask服務器腳本構建:

import time
from flask import Flask
app=Flask(__name__)
@app.route('/<id>')
def func1(id):
    time.sleep(2)
    return id


if __name__ == '__main__':
    app.run()
server構建

  單線程多任務異步數據採集代碼

 1 import time
 2 import asyncio  # pip install asyncio
 3 import aiohttp  #pip install aiohttp
 4 headers = {
 5     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
 6 }
 7 #建立urls列表
 8 urls = []
 9 for i in range(500):
10     urls.append(f'http://127.0.0.1:5000/{i}')
11 
12 #定義攜程函數-----簡單的基本架構
13 async def request(url):
14    async with aiohttp.ClientSession() as s:
15        #s.get/post和requests中的get/post用法幾乎同樣:url,headers,data/prames
16        #在s.get中若是使用代理操做:proxy="http://ip:port"
17        async with await s.get(url) as response:
18            #獲取字符串形式的響應數據:response.text()
19            #獲取byte類型的:response.read()
20            page_text = await response.text()
21            return page_text
22 
23 #定義任務對象回調函數
24 def parse(task):
25     page_text = task.result()
26     print(f'響應結果:{page_text}')
27 
28 
29 #建立任務列表
30 start = time.time()
31 tasks = []
32 for url in urls:
33     c = request(url)
34     task = asyncio.ensure_future(c)
35     task.add_done_callback(parse)
36     tasks.append(task)
37 #建立事件循環對象並將任務對象列表進行註冊
38 loop = asyncio.get_event_loop()
39 loop.run_until_complete(asyncio.wait(tasks))
40 loop.close()
41 print(time.time()-start)#3.4196295738220215
單線程多任務異步數據採集代碼
相關文章
相關標籤/搜索