⚡ asyncio 异步编程
什么是异步?为什么需要它?
假设你要同时请求 100 个网页。同步方式是一个一个来——等第一个网页返回,再请求第二个。如果每个请求要 1 秒,总共要 100 秒。
异步方式是发完一个请求不等回复,立刻发下一个——100 个请求几乎同时发出,等所有回复一起回来,总共只要 1-2 秒。
💡 提示: 💡 类比:同步 = 你在餐厅点了一道菜,站在厨房门口等做好才点下一道。异步 = 你把 100 道菜全点了,然后坐下来等厨房一起做好端上来。
asyncio 基础
python
import asyncio
# 定义一个异步函数(协程)
async def fetch_data(url, delay):
print(f"开始请求 {url}")
await asyncio.sleep(delay) # 模拟网络请求(非阻塞等待)
print(f"完成请求 {url}")
return f"{url} 的数据"
# 运行协程
async def main():
# ❌ 同步方式:一个一个来,总耗时 = 所有 delay 之和
# result1 = await fetch_data("api/users", 2)
# result2 = await fetch_data("api/orders", 3)
# 总耗时:5 秒
# ✅ 异步方式:并发执行,总耗时 = 最长的那个
results = await asyncio.gather(
fetch_data("api/users", 2),
fetch_data("api/orders", 3),
fetch_data("api/products", 1),
)
# 总耗时:3 秒(最长的那个)
for r in results:
print(r)
asyncio.run(main())bash
开始请求 api/users
开始请求 api/orders
开始请求 api/products
完成请求 api/products ← 1秒后
完成请求 api/users ← 2秒后
完成请求 api/orders ← 3秒后(不是 2+3=5秒!)aiohttp 并发爬虫
真实场景:并发抓取 100 个网页。用 aiohttp 替代 requests:
python
import asyncio
import aiohttp
async def fetch(session, url):
"""抓取单个网页"""
try:
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
text = await resp.text()
return {"url": url, "status": resp.status, "length": len(text)}
except Exception as e:
return {"url": url, "error": str(e)}
async def main():
urls = [
"https://httpbin.org/get",
"https://httpbin.org/delay/1",
"https://httpbin.org/status/404",
"https://httpbin.org/html",
] * 25 # 模拟 100 个 URL
# 限制并发数(避免被封 IP)
semaphore = asyncio.Semaphore(20)
async def limited_fetch(session, url):
async with semaphore:
return await fetch(session, url)
async with aiohttp.ClientSession() as session:
# 并发抓取所有 URL(最多 20 个同时进行)
tasks = [limited_fetch(session, url) for url in urls]
results = await asyncio.gather(*tasks)
# 统计结果
success = sum(1 for r in results if r.get("status") == 200)
failed = len(results) - success
print(f"✅ 成功: {success}, ❌ 失败: {failed}")
asyncio.run(main())httpx——现代异步 HTTP 客户端
python
import asyncio
import httpx
async def main():
# httpx 同时支持同步和异步
async with httpx.AsyncClient() as client:
# 并发请求
tasks = [
client.get("https://api.github.com/users/torvalds"),
client.get("https://api.github.com/users/gvanrossum"),
client.get("https://api.github.com/users/rosalindfranklin"),
]
responses = await asyncio.gather(*tasks)
for resp in responses:
data = resp.json()
print(f"{data['login']}: {data['public_repos']} repos")
asyncio.run(main())💡 提示: 💡 httpx vs aiohttp:httpx API 更像 requests(学习成本低),同时支持 HTTP/2。aiohttp 更轻量、性能略高。新项目推荐 httpx,已有 aiohttp 代码不需要换。
异步任务管理
python
import asyncio
async def slow_task():
await asyncio.sleep(10)
return "slow done"
async def fast_task():
await asyncio.sleep(1)
return "fast done"
async def main():
# 1. gather:等待所有任务完成
results = await asyncio.gather(slow_task(), fast_task())
# 2. wait_for:设置超时(超时抛 TimeoutError)
try:
result = await asyncio.wait_for(slow_task(), timeout=3)
except asyncio.TimeoutError:
print("任务超时了!")
# 3. as_completed:按完成顺序处理
tasks = [fast_task(), slow_task(), fast_task()]
for coro in asyncio.as_completed(tasks):
result = await coro
print(f"完成: {result}")
# 4. create_task:创建后台任务
task = asyncio.create_task(slow_task())
# 可以做其他事情...
result = await task # 需要结果时再 await
# 5. Semaphore:限制并发数
sem = asyncio.Semaphore(5) # 最多 5 个并发
async def limited():
async with sem:
return await fast_task()
asyncio.run(main())异步生成器和上下文管理器
python
import asyncio
# 异步生成器(用 async for 遍历)
async def async_range(n):
for i in range(n):
await asyncio.sleep(0.1) # 模拟异步操作
yield i
async def main():
async for num in async_range(5):
print(num) # 0, 1, 2, 3, 4
# 异步上下文管理器(用 async with)
class AsyncDB:
async def __aenter__(self):
print("连接数据库...")
await asyncio.sleep(0.5)
return self
async def __aexit__(self, *args):
print("关闭数据库连接...")
await asyncio.sleep(0.1)
async def query(self, sql):
await asyncio.sleep(0.2)
return [{"id": 1, "name": "test"}]
async with AsyncDB() as db:
result = await db.query("SELECT * FROM users")
print(result)
asyncio.run(main())