asyncio
可以實現單線程的並發IO操作,如果僅用在客戶端,發揮的威力並不大,把asyncio
用在服務端,例如WEB服務器,由於HTTP連接就是IO操作,因此可以用單線程+coroutine
實現多用戶的高並發支持。
asyncio
實現了TCP、UDP、SSL等協議,aiohttp
則是基於asyncio
實現的HTTP框架。
首先我們定義一個協同程序用來獲取頁面,並打印出來。我們使用asyncio.coroutine將一個方法裝飾成一個協同程序,aiohttp.request是一個協同程序,所以他是一個可讀方法。
@asyncio.coroutine
def print_page(url):
response = yield from aiohttp.request('GET', url)
#close斷開的是網頁與服務器的Connection:keep-alive
body = yield from response.read_and_close(dcode=True)
print(body)
如你所見,我們可以使用yield from從另一個協程中調用一個協程。為了從同步代碼中調用一個協程,我們需要一個時間循環,我們可以通過asyncio.get_event_loop()得到一個標准的時間循環,之后使用它的loop.run_until_complete()方法啟動協程,所以,為了使之前的協程運行我們只需要做:
loop = asyncio.get_evemnt_loop()
loop.run_until_complete(print_page('http://xxxxx'))
一個有用的方法是asyncio.wait,通過它可以獲取一個協程的列表,同時返回一個將它們包括在內的單獨的協程,所以我們可以這樣寫:
loop.run_until_complete(asyncio.wait([print_page(url) for url in url_list]))
數據抓取
現在我們已經知道如何做異步請求,因此我們可以寫一個數據抓取器,我們僅僅還需要一些工具來解析HTML
import aiohttp
import asyncio
def get_content(html):
'''
處理HTML獲取所需信息
'''
async def print_magnet(page):
headers = {'key':'value'}
cookies = {'cookies_are': 'working'}
url = 'http://www.cnbligs.com/#p{}'.format(page)
async with aiohttp.ClientSession(cookies=cookies) as session:
async with session.get(url, headers=headers) as response:
content = get_content(await response.text())
print(await response.text())
loop = asyncio.get_event_loop()
tasks = asyncio.wait([print_magnet(page) for page in range(10)])
loop.run_until_complete(tasks)
為了避免爬蟲一次性的產生過多的請求導致賬號/IP被封可以考慮使Semaphore控制同時的並發量,與我們熟悉的threading模塊中的Semaphore(信號量)用法類似。
import aiohttp
impoer asyncio
NUMBERS = range(12)
URL = 'http://httpbin.org/get?a={}'
sema = asyncio.Semaphore(3)
async def fetch_async(a):
async with aiohttp.request('GET', URL.format(a)) as res:
data = await r.json()
return data['args']['a']
async def print_result(a):
with (await sema):
r = await fetch_async(a)
print('fetch({}) = {}'.foemat(a, r))
loop = asyncio.get_event_loop()
f = asyncio.wait([print_result(num) for num in NUMBERS])
loop.run_until_complete(f)
可以到后台看到並發受到了信號量的限制,同一時刻基本只處理三個請求。