安装
# Python 版本大于 3.5.3
pip install asyncio -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install aiohttp -i https://pypi.tuna.tsinghua.edu.cn/simple
使用aiohttp作为服务端
之前对aiohttp的印象只停留在异步爬虫,直到最近看了它的文档,才发现它也可以作为服务端使用。
aiohttp版本:3.6.2
import time
from aiohttp import web
# 处理query请求
async def handle_query(request):
time.sleep(3)
name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name
return web.Response(text=text)
# 处理json请求(接收和返回都使用json)
async def handle_json(request):
json_data = await request.json()
print('data', json_data)
return web.json_response(data=json_data)
app = web.Application()
app.add_routes([web.get('/', handle_query),
web.get('/{name}', handle_query),
web.post('/json', handle_json),
])
if __name__ == '__main__':
web.run_app(app, port=5000)
使用aiohttp作为客户端
大部分人使用aiohttp,应该是将其作为客户端使用,常见的有异步爬虫、异步代理池(本质也是爬虫)等。下面将会讲述aiohttp的基本使用以及如何限制并发数量
请求单个URL
async def fetch(session, url):
# with语句保证在处理session的时候,总是能正确的关闭它
async with session.get(url) as resp:
# 1.如果想要得到结果,则必须使用await关键字等待请求结束,如果没有await关键字,得到的是一个生成器
# 2.text()返回的是字符串的文本,read()返回的是二进制的文本
data = await resp.text()
print('data', data)
return data
async def run():
async with aiohttp.ClientSession() as session:
html_data = await fetch(session, "http://127.0.0.1:5000/")
print('html', html_data)
if __name__ == '__main__':
# 创建一个事件循环
loop = asyncio.get_event_loop()
# run函数是一个异步函数,同时也是一个future对象,run_until_complete会等待future对象结束后才退出
loop.run_until_complete(run())
请求多个URL(会重复创建session)
关于重复创建session的问题,官方文档提到,不建议每一个请求都创建一个session,建议同一个网站使用同一个session,当然还要看每个人的实际情况。
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
data = await resp.text()
return data
if __name__ == '__main__':
loop = asyncio.get_event_loop()
tasks = [] # I'm using test server localhost, but you can use any url
url = "http://127.0.0.1:5000/"
for i in range(10):
task = asyncio.ensure_future(fetch(url))
tasks.append(task)
# 得到返回结果
results = loop.run_until_complete(asyncio.wait(tasks))
for r in results[0]:
print('r', r.result())
请求多个URL(不会重复创建session)
此处代码摘抄至:stackoverflow.com/questions/3…
async def fetch(session, url):
async with session.get(url) as resp:
if resp.status != 200:
resp.raise_for_status()
data = await resp.text()
return data
async def fetch_multi(session, urls):
tasks = []
for url in urls:
task = asyncio.create_task(fetch(session, url))
tasks.append(task)
# gather: 搜集所有future对象,并等待返回
results = await asyncio.gather(*tasks)
return results
async def main():
urls = ["http://127.0.0.1:5000/" for _ in range(10)]
async with aiohttp.ClientSession() as session:
datas = await fetch_multi(session, urls)
print(datas)
if __name__ == '__main__':
asyncio.run(main())
请求多个URL(错误做法)
其实如果你尝试运行这段代码,你会发现也执行了10次请求,那为什么说是错误做法呢?其实错不错误取决于你有没有加return语句,如果加上了,只会执行一次。
async def fetch():
async with aiohttp.ClientSession() as session:
for i in range(1, 10):
url = "http://127.0.0.1:5000/"
async with session.get(url) as resp:
data = await resp.text()
print('data', data)
# 加上return语句只会执行一次
# return data
if __name__ == '__main__':
# 创建一个事件循环
loop = asyncio.get_event_loop()
loop.run_until_complete(fetch())
控制aiohttp并发数量
使用TCPConnector控制并发
这里请求的服务端所使用的代码可以参见上文。
为了更好地理解,建议调整 TCPConnector 的数量,比如一次是3,另一次是10,比较两次打印的时间是怎样的,应该能够更好地理解
async def fetch(session, url):
async with session.get(url) as resp:
if resp.status != 200:
resp.raise_for_status()
data = await resp.text()
print('data', data + " " + time.ctime())
return data
async def fetch_multi(session, urls):
tasks = []
for url in urls:
task = asyncio.create_task(fetch(session, url))
tasks.append(task)
# gather: 搜集所有future对象,并等待返回
results = await asyncio.gather(*tasks)
return results
async def main():
urls = ["http://127.0.0.1:5000/" for _ in range(10)]
conn = aiohttp.TCPConnector(limit=3)
async with aiohttp.ClientSession(connector=conn) as session:
datas = await fetch_multi(session, urls)
print(datas)
if __name__ == '__main__':
asyncio.run(main())
使用Semaphore控制并发
Semaphore可以理解为一个线程计数器,它内部会维护一个值,比如下文的2。它的核心是 acquire() 和 release() 方法。
当执行 acquire() 方法时,会先判断内部维护的值是否小于0,如果大于0,获得锁,内部的值减一;如果小于0,阻塞,直到执行 release() 方法释放锁,内部值加一。
详情可以查看这篇文章:www.cnblogs.com/renpingshen…
async def fetch(url, semaphore):
async with semaphore:
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
data = await response.text()
print('data', data + " " + time.ctime())
return data
async def run():
url = 'http://127.0.0.1:5000'
semaphore = asyncio.Semaphore(2) # 限制并发量为2
to_get = [fetch(url, semaphore) for _ in range(10)]
await asyncio.wait(to_get)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()