首页 / Python3 入门教程 / aiohttp 与异步实战

Python3 入门教程

aiohttp 与异步实战

本教程共 70 篇 · 第 64 篇 · 更新于 2026-07-22 · 约 4 分钟阅读

PythonPython3 入门教程aiohttp异步爬虫asyncio

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_postadd_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。

Note

aiohttp 服务端的并发能力很强,单进程就能处理上万个连接。如果需要多核利用,可以启动多个进程,前面用 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 模块」等,改写后所得。