aiohttp 基于异步库的请求替代品

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)    

可以到后台看到并发受到了信号量的限制,同一时刻基本只处理三个请求。

posted @ 2017-09-28 09:34  糕同学  阅读(1250)  评论(0编辑  收藏  举报