aiohttp 与异步实战
本教程共 70 篇 · 第 64 篇 · 更新于 2026-07-22 · 约 4 分钟阅读
64. aiohttp 与异步实战
本节目标:学会用 aiohttp 写异步 HTTP 客户端和服务端,能把 asyncio 应用到真实网络场景中。
asyncio 本身只提供事件循环和协程调度,不直接发 HTTP 请求。实际做网络开发时,需要搭配专门的异步库。aiohttp 是目前最流行的选择,同时支持客户端和服务端。
异步 HTTP 客户端
安装:
pip install aiohttp
发起 GET 请求:
import aiohttp
import asyncio
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'https://www.baidu.com')
print(html[:500])
asyncio.run(main())
ClientSession()是 HTTP 请求的会话对象,管理连接池和 Cookie。async with确保会话用完自动关闭,释放底层连接。response.text()也是异步的,因为读取响应体可能涉及网络 IO。
并发抓取多个页面:
async def main():
urls = [
'https://www.baidu.com',
'https://www.bing.com',
'https://www.sogou.com'
]
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
for url, html in zip(urls, results):
print(f"{url}: {len(html)} 字节")
asyncio.run(main())
三个请求同时发出,总耗时接近最慢的那个请求,而不是三者之和。
Tip默认超时可能太长,建议通过
timeout参数控制:timeout = aiohttp.ClientTimeout(total=10) async with aiohttp.ClientSession(timeout=timeout) as session: ...
异步 HTTP 服务端
aiohttp 也能写 Web 服务,语法和 Flask 有点像,但底层是异步的:
from aiohttp import web
async def hello(request):
return web.Response(text='Hello, aiohttp!')
async def greet(request):
name = request.match_info.get('name', 'Anonymous')
return web.Response(text=f'你好, {name}')
app = web.Application()
app.router.add_get('/', hello)
app.router.add_get('/{name}', greet)
web.run_app(app, host='127.0.0.1', port=8080)
运行后访问 http://127.0.0.1:8080/ 和 http://127.0.0.1:8080/Alice 测试效果。
web.Application()创建应用实例。app.router.add_get()注册路由,支持add_post、add_put等。- 视图函数必须是
async def,返回web.Response对象。
处理 POST 请求:
async def login(request):
data = await request.post()
username = data.get('username')
password = data.get('password')
return web.json_response({
'success': True,
'user': username
})
app.router.add_post('/login', login)
web.json_response() 自动把字典转成 JSON 并设置正确的 Content-Type。
Noteaiohttp 服务端的并发能力很强,单进程就能处理上万个连接。如果需要多核利用,可以启动多个进程,前面用 Nginx 做负载均衡。
实战:异步爬虫
结合 aiohttp 和 asyncio,可以写出高性能的异步爬虫。下面的例子抓取多个页面标题:
import aiohttp
import asyncio
import re
async def fetch_title(session, url):
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
html = await resp.text()
m = re.search(r'<title>(.*?)</title>', html, re.I)
return url, m.group(1) if m else '无标题'
except Exception as e:
return url, f'出错: {e}'
async def main():
urls = [
'https://www.baidu.com',
'https://www.bing.com',
'https://zhihu.com',
'https://www.github.com'
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_title(session, u) for u in urls]
for url, title in await asyncio.gather(*tasks):
print(f"{url} -> {title}")
asyncio.run(main())
相比同步的 requests 库,这个版本能在几秒内完成几十个页面的抓取,而不会因为等待网络响应而卡死。
Warning爬取网站时请遵守 robots.txt 规则,控制并发数量和请求频率,不要给目标服务器造成压力。高频率爬取可能触发封禁甚至法律风险。
在异步代码里调用同步函数
有时候你不得不调用一个同步阻塞的库(比如 requests、数据库驱动)。直接 await 是不行的,需要用 asyncio.to_thread() 把它丢到线程池里执行:
import asyncio
import requests
async def fetch_with_requests(url):
loop = asyncio.get_running_loop()
response = await loop.run_in_executor(None, requests.get, url)
return response.text
# Python 3.9+ 更简洁的写法
async def fetch_simple(url):
response = await asyncio.to_thread(requests.get, url)
return response.text
这样同步阻塞的操作不会卡住事件循环,其他协程可以继续执行。
小结
aiohttp.ClientSession()是异步 HTTP 客户端的入口,支持连接池和并发请求。aiohttp.web能搭建高性能异步服务端,API 设计清晰。- 异步爬虫是 asyncio 的经典应用场景,aiohttp 比同步库快几个数量级。
- 阻塞操作用
asyncio.to_thread()隔离,保护事件循环不被卡住。
来源:参考了 liaoxuefeng「23.3. 使用aiohttp」、runoob「Python asyncio 模块」等,改写后所得。