了解aiohttp
aiohttp是一个基于asyncio的异步http网络模块,它既提供了服务端,有提供了客户端。其中我们可以用服务端搭建一个支持异步处理的服务器,用于处理请求并返回响应,类似于Django、Flask等一些Web服务器,而客户端我们就可以用来发起请求,就类似于requests来发起一个http请求然后获得响应,但requests发起的是同步网络请求,而aiohttp则发起的是异步的。
我们先了解一下aiohttp客户端部分使用。
基本使用
基本实例
我们先看一个基本的aiohttp请求案例,【代码】如下:
import aiohttp
import asyncio
async def get(session, url):
async with session.get(url) as response:
return await response.text(), response.status
async def main():
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64, verify_ssl=False)) as session:
html, status = await get(session, 'https://baidu.com')
print(f"html:{html}....")
print(f"status:{status}")
if __name__ == '__main__':
loop =asyncio.get_event_loop()
loop.run_until_complete(main())
在这里使用aiohttp来爬取百度首页,获得源码和响应状态码并输出,运行结果如下:
html:
<!DOCTYPE html><!--STATUS OK-->
<html><head><meta http-eq
........
status:200
这里源码比较长,只复制一部分,可以看到已成功获取网页源代码以及响应状态码200,完成了一次HTTP请求,即我们成功使用aiohttp通过异步方式进行了网页爬取,当然这个操作,requests同样可以做到,
在使用aiohttp请求是,需要注意以下几点:
- 首先在导入库的时候,除了必须要引用aiohttp这个库外,还必须要引入asyncio这个库,因此要实现异步爬取需要启动协程,而协程则需要借助于asyncio里面的事件循环来执行,除了事件循环,asyncio里面也提供了很多基础的异步操作。
- 异步爬取的方法的定义和同步请求有所不同,在每一个异步方法前面加async来修饰。
- with as 语句前面同样需要加async来修饰,在Python中,with as 语句用于声明一个上下文管理器,能够帮我们自动分配和释放资源,而在异步方法中,with as 前面加上 async代表声明一个支持异步的上下文管理器。
- 对于一些返回coroutine的操作,前面需要加await来修饰,如response调用text方法,查询API(官方文档)可以发现其返回的是coroutine对象, 那么前面就要加await;而对于状态码来说,其返回值就是一个数值类型,那么前面就不要加await。所以,这里可以按照实际情况处理,具体可以查官方文档看看其对应的返回值是怎样的类型,然后决定加不加await就可以来(也可以type看下类型)
- 最后,定义完爬取方法后,实际上是main方法调用了get方法,要运行的话,必须要启用事件循环,事件循环就需要使用asyncio库,然后使用run_until_complete方法来运行。
注意:在python3.7以上的版本,我们可以使用asyncio.run(main())来替代最后的启动操作,不需要显示声明事件循环,run方法内部会自动启动一个事件循环。
异步-URL参数设置
GET传入params参数
对于URL参数的设置,可以借助于params参数,传入一个字典即可
【代码示例】:
import aiohttp
import asyncio
async def main():
params = {'name': 'name', 'age': 22}
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
async with session.get('https://xxx/xx/xx', params=params) as response:
print(await response.text())
if __name__ == '__main__':
loop=asyncio.get_event_loop()
loop.run_until_complete(main())
其实际请求的URL为 https://xxx/xx/xx?name=name&age=22
其它请求类型
另外aiohttp还支持其它的请求类型,如:POST、PUT、DELETE等等,这个和requests的使用方式有点类似。
【示例如下】:
- session.post('https://xx/xx/post',data=b"data")
- session.put('https://xx/xx/put',data=b"data")
- session.delete('https://xx/xx/delete')
- session.head('https://xx/xx/get')
- session.options('https://xx/xx/get')
- session.patch('https://xx/xx/patch',data=b"data")
POST数据
对于POST表单提交。
【示范】如下:
import aiohttp
import asyncio
async def main():
data = {'data': 'xxx'}
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
#如果请求头Content-type为application/x-www-form-urlencoded,用下方来实现
async with session.post('https://xxx/post', data=data) as response:
##如果请求头Content-type为application/application/json,我们只需要把参数修改为:
# async with session.post('https://xxx/post', json=data) as response:
print(await response.text())
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
注意,post请求代码含有两种方式
超时设置
超时设置,我们需要借助aiohttp里面ClientTimeout对象来实现。
【示例】:
import aiohttp
import asyncio
async def main():
timeout=aiohttp.ClientTimeout(total=1) #设置一秒超时
async with aiohttp.ClientSession(timeout=timeout,connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
async with session.get('http://xxx/xxx') as response:
print(await response.text())
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
如果超时的话,会抛出TimeoutError异常,其类型为asyncio.TimeoutError,我们可以异常捕获即可
另外ClienTimeout对象声明还有其它参数,如,connect、socket_connent等等,详细可以参考官方文档:https://docs.aiohttp.org/en/stable/client_quickstart.html#timeouts
并发限制
由于aiohttp可以支持非常大的并发,比如上万、十万、百万都是能做到的,但这么大的并发量,目标网站是很可能在短时间无法响应,可能瞬间将目标网站挂掉,所以我们需要控制一下并发的量。
我们可以借助于asyncio的Semaphore来控制并发量,【示例】如下:
import aiohttp
import asyncio
CONCURRENCY = 5 # 声明并发数,
URL='https://www.baidu.com'
semaphore=asyncio.Semaphore(CONCURRENCY) #设置控制异步并发数
session=None
async def scrape_api():
async with semaphore:
print('scraping',URL)
async with session.get(URL) as response:
await asyncio.sleep(1)
return await response.text()
async def main():
global session
session=aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False))
acrape_index_tasks=[asyncio.ensure_future(scrape_api()) for _ in range(10000)]
await asyncio.gather(*acrape_index_tasks)
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
在这里我们声明CONCURRENCY代表爬取的最大并发量为5,同时声明爬取的目标URL为百度。接着我们借助于Semaphore创建一个信号量对象。赋值为semaphore,这样我们就可以用它来控制最大并发量了,我们这里把它直接放置对应的爬取方式里面,使用async with 语句将semaphore作为上下文对象即可。这样,信号量可以控制进入爬取最大协程数量,最大数量就是我们声明的CONCURRENCY的值。
在main方法里面,我们声明了10000个task,传递给gather方法运行,倘若不加以限制,这1000个task会被同时执行,并发量太大,但有信号量的控制之后,同时运行的task的数量最大会被控制在5个,这样就能给aiohttp限制速度了。
点击查看 异步案例使用,