异步爬虫: async/await 与 aiohttp的使用,以及例子

来源:互联网 发布:淘宝网宽松牛仔裤女款 编辑:程序博客网 时间:2024/06/08 17:56

在python3.5中,加入了asyncio/await 关键字,使得回调的写法更加直观和人性化。而aiohttp是一个提供异步web服务的库,分为服务器端和客户端。这里主要使用其客户端。本文分为三步分,第一部分简单介绍python3.5的异步,asyncio/await 关键字。第二部分介绍aiohttp客户端部分的使用。第三部分是一个例子,列举了如何爬取CSDN某个博客中的所有文章。

1. async/await

async/await 是 python3.5中新加入的特性, 将异步从原来的yield 写法中解放出来,变得更加直观。
在3.5之前,如果想要使用异步,主要使用yield语法。举例如下:

import asyncio@asyncio.coroutine  # 修饰符,等同于 asyncio.coroutine(hello())def hello():    print('Hello world! (%s)' % threading.currentThread())    yield from asyncio.sleep(1)  # 执行到这一步以后,直接切换到下一个任务,等到一秒后再切回来    print('Hello again! (%s)' % threading.currentThread())loop = asyncio.get_event_loop()  tasks = [hello(), hello()]loop.run_until_complete(asyncio.wait(tasks))loop.close()

其实上面的例子由于使用了asyncio库以及用coroutine进行修饰,已经比较简化了。而引入了async/await以后,hello()可以写成这样:

async def hello():    print("Hello world!")    r = await asyncio.sleep(1)    print("Hello again!")

注意此时已经不需要使用@asyncio.coroutin进行修饰,而是在def之前加async表示这是个异步函数,其内有异步操作。此外,使用await 替换了yield from, 表示这一步为异步操作。

2. aiohttp


aiohttp是一个用于web服务的库,网上的资料,包括廖雪峰的网站中的资料大部分是关于服务器端(server)的,关于客户端(client)的资料不多。事实上aiohttp的资料并不完善,官网上只有一些例子,我就也就照着例子依葫芦画瓢了。

2.1 基本用法

async with aiohttp.get('https://github.com') as r:        await r.text()

其中r.text(), 可以在括号中指定解码方式,编码方式,例如

await resp.text(encoding='windows-1251')

或者也可以选择不编码,适合读取图像等,是无法编码的

await resp.read()

这里要注意的是with…as的语法,不过这个跟本文无关,读者可以自行搜索了解。

2.2 设置timeout


需要加一个with aiohttp.Timeout(x)

with aiohttp.Timeout(0.001):    async with aiohttp.get('https://github.com') as r:        await r.text()

2.3 使用session获取数据

这里要引入一个类,aiohttp.ClientSession. 首先要建立一个session对象,然后用该session对象去打开网页。session可以进行多项操作,比如post, get, put, head等等,如下面所示

async with aiohttp.ClientSession() as session:    async with session.get('https://api.github.com/events') as resp:        print(resp.status)        print(await resp.text())

如果要使用post方法,则相应的语句要改成

session.post('http://httpbin.org/post', data=b'data')

2.4 自定义headers

这个比较简单,将headers放于session.get/post的选项中即可。注意headers数据要是dict格式

url = 'https://api.github.com/some/endpoint'headers = {'content-type': 'application/json'}await session.get(url, headers=headers)

显然,该方法对于post等其他方法也都有效

2.5 使用代理

要实现这个功能,需要在生产session对象的过程中做一些修改。

conn = aiohttp.ProxyConnector(proxy="http://some.proxy.com")session = aiohttp.ClientSession(connector=conn)async with session.get('http://python.org') as resp:    print(resp.status)

这边没有写成with….. as….形式,但是原理是一样的,也可以很容易的改写成之前的格式
如果代理需要认证,则需要再加一个proxy_auth选项。

conn = aiohttp.ProxyConnector(    proxy="http://some.proxy.com",    proxy_auth=aiohttp.BasicAuth('user', 'pass'))session = aiohttp.ClientSession(connector=conn)async with session.get('http://python.org') as r:    assert r.status == 200

2.6 自定义cookie

同样是在session中做修改。

url = 'http://httpbin.org/cookies'async with ClientSession({'cookies_are': 'working'}) as session:    async with session.get(url) as resp:        assert await resp.json() == {"cookies":                                         {"cookies_are": "working"}}

3. 样例

在看完第二部分的各功能的用法以后,完成一个例子其实已经很简单了,无非就是将各个功能排列组合而已。下面这个简单的爬虫,是用来爬取我博客下所有的文章的

import urllib.request as requestfrom bs4 import BeautifulSoup as bsimport asyncioimport aiohttp@asyncio.coroutineasync def getPage(url,res_list):    print(url)    headers = {'User-Agent':'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}    # conn = aiohttp.ProxyConnector(proxy="http://127.0.0.1:8087")    async with aiohttp.ClientSession() as session:        async with session.get(url,headers=headers) as resp:            assert resp.status==200            res_list.append(await resp.text())class parseListPage():    def __init__(self,page_str):        self.page_str = page_str    def __enter__(self):        page_str = self.page_str        page = bs(page_str,'lxml')        # 获取文章链接        articles = page.find_all('div',attrs={'class':'article_title'})        art_urls = []        for a in articles:            x = a.find('a')['href']            art_urls.append('http://blog.csdn.net'+x)        return art_urls    def __exit__(self, exc_type, exc_val, exc_tb):        passpage_num = 5page_url_base = 'http://blog.csdn.net/u014595019/article/list/'page_urls = [page_url_base + str(i+1) for i in range(page_num)]loop = asyncio.get_event_loop()ret_list = []tasks = [getPage(host,ret_list) for host in page_urls]loop.run_until_complete(asyncio.wait(tasks))articles_url = []for ret in ret_list:    with parseListPage(ret) as tmp:        articles_url += tmpret_list = []tasks = [getPage(url, ret_list) for url in articles_url]loop.run_until_complete(asyncio.wait(tasks))loop.close()
0 0
原创粉丝点击