aiohttp 异步请求完整使用详解

一、基础概念

  1. aiohttp:基于 Python asyncio 的异步 HTTP 客户端 / 服务端库,并发性能远超 requests(同步阻塞),适合大量爬虫、高并发接口调用。
  2. 核心两个组件:
    • aiohttp.ClientSession:会话对象,必须复用,内部维护连接池,不要每次请求新建。
    • async/await:所有 IO 操作必须加 await,函数标记 async
  3. 环境安装
pip install aiohttp
 

二、最简入门示例(GET 请求)

核心规则

  • 异步函数不能直接调用,必须通过 asyncio.run() 运行(Python3.7+)
  • 请求、关闭会话都需要 await
import aiohttp
import asyncio

async def simple_get():
    # 1. 创建会话(建议全局复用)
    async with aiohttp.ClientSession() as session:
        # 2. 发送GET请求
        async with session.get("https://httpbin.org/get") as resp:
            # 状态码
            print(resp.status)
            # 文本响应
            text = await resp.text()
            print(text)
            # json响应
            json_data = await resp.json()
            print(json_data["url"])

# 启动异步事件循环
if __name__ == "__main__":
    asyncio.run(simple_get())
 

响应读取三种方式(均为协程,必须 await)

 
方法 用途
resp.text(encoding="utf-8") 获取网页文本,可指定编码
resp.json() 自动解析 JSON,接口专用
resp.read() 原始二进制(图片、文件、视频)

三、POST 请求(表单、JSON、二进制)

1. 传递 JSON 参数(最常用接口)

async def post_json():
    payload = {"username": "test", "pwd": "123456"}
    async with aiohttp.ClientSession() as session:
        async with session.post(
            url="https://httpbin.org/post",
            json=payload  # 自动设置 Content-Type: application/json
        ) as resp:
            data = await resp.json()
            print(data["json"])
 

2. 表单表单提交(x-www-form-urlencoded)

使用 data= 传参:
async def post_form():
    form_data = {"user": "admin", "age": 20}
    async with aiohttp.ClientSession() as session:
        async with session.post(
            url="https://httpbin.org/post",
            data=form_data
        ) as resp:
            print(await resp.text())
 

3. 上传文件 multipart/form-data

async def upload_file():
    data = aiohttp.FormData()
    # 添加普通字段
    data.add_field("name", "demo")
    # 上传本地文件
    data.add_field("file", open("test.jpg", "rb"), filename="test.jpg")

    async with aiohttp.ClientSession() as session:
        async with session.post("https://httpbin.org/post", data=data) as resp:
            print(await resp.json())
 

四、请求通用参数详解

1. 请求头 headers

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "Referer": "https://www.baidu.com"
}
session.get(url, headers=headers)
 

2. URL 查询参数 params

自动拼接 ?key=val,无需手动拼接:
params = {"page": 1, "size": 10}
session.get("https://httpbin.org/get", params=params)
# 实际访问:https://httpbin.org/get?page=1&size=10
 

3. 超时 timeout(防止卡死)

全局超时 10s:
# 方式1:统一超时
session.get(url, timeout=aiohttp.ClientTimeout(total=10))

# 方式2:细分超时(连接/读取)
timeout = aiohttp.ClientTimeout(
    total=15,        # 总超时
    connect=5,       # 建立连接超时
    sock_read=10     # 读取响应超时
)
 

4. Cookie 携带与自定义

  1. 会话自动保存 cookie(同一个 session 共享)
  2. 手动传入 cookie:
 
cookies = {"token": "abc123xyz"}
session.get(url, cookies=cookies)
 

5. 代理 proxy

# http代理
session.get(url, proxy="http://127.0.0.1:7890")
# socks代理需额外安装 aiohttp-socks

 

五、高并发批量请求(核心优势)

使用 asyncio.gather() 批量并发执行,一次性发起大量请求。

示例:并发 10 个请求

import aiohttp
import asyncio

async def fetch(session, url):
    """单个请求封装函数"""
    async with session.get(url) as resp:
        return await resp.json()

async def batch_request():
    # 待请求url列表
    urls = ["https://httpbin.org/get"] * 10
    async with aiohttp.ClientSession() as session:
        # 构造协程任务列表
        tasks = [fetch(session, url) for url in urls]
        # 并发执行所有任务
        results = await asyncio.gather(*tasks)
    # 遍历结果
    for res in results:
        print(res["url"])

if __name__ == "__main__":
    asyncio.run(batch_request())
 

控制并发数量(防封 IP,信号量 Semaphore)

一次性请求上千个会瞬间打满目标服务器,用信号量限制并发数:
import aiohttp
import asyncio

# 最大同时5个并发
SEMAPHORE = asyncio.Semaphore(5)

async def fetch_limit(session, url):
    async with SEMAPHORE:  # 限制并发
        async with session.get(url) as resp:
            return await resp.status

async def run_limit():
    urls = ["https://httpbin.org/get"] * 20
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_limit(session, u) for u in urls]
        res = await asyncio.gather(*tasks)
        print(res)

asyncio.run(run_limit())
 

六、异常捕获(生产必备)

异步请求常见异常:连接超时、连接失败、服务器 500、DNS 错误等
from aiohttp import ClientError, ClientResponseError, ClientTimeout

async def safe_request(session, url):
    try:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=5)) as resp:
            # 主动抛出4xx/5xx错误
            resp.raise_for_status()
            return await resp.json()
    except ClientResponseError as e:
        print(f"状态码异常:{e.status}, url:{url}")
    except ClientError as e:
        print(f"连接异常:{str(e)}")
    except asyncio.TimeoutError:
        print(f"请求超时:{url}")
    except Exception as e:
        print(f"未知错误:{e}")
    return None
 
异常类说明:
  • ClientResponseError:404、500、403 等 HTTP 错误码
  • TimeoutError:超时
  • ClientError:所有网络基础错误(断网、DNS 失败、拒绝连接)

七、高级功能

1. 持久会话 Cookie(会话维持登录)

同一个 ClientSession 会自动保存服务器返回的 Set-Cookie,模拟登录态:
async def login_demo():
    async with aiohttp.ClientSession() as session:
        # 1. 登录,获取cookie
        await session.post("https://httpbin.org/post", data={"user": "test"})
        # 2. 后续请求自动带上登录cookie
        async with session.get("https://httpbin.org/cookies") as resp:
            print(await resp.json())
 

2. 自定义连接池(限制最大连接数)

默认连接池无上限,爬虫建议限制:
connector = aiohttp.TCPConnector(limit=100)  # 全局最大100连接
async with aiohttp.ClientSession(connector=connector) as session:
    ...
 

3. 下载二进制文件(图片 / 视频保存本地)

async def download_img(session, url, save_path):
    async with session.get(url) as resp:
        with open(save_path, "wb") as f:
            while chunk := await resp.content.read(1024*1024):
                f.write(chunk)
 

4. HTTPS 关闭 SSL 校验(内网 / 自签证书)

# 关闭ssl验证
connector = aiohttp.TCPConnector(verify_ssl=False)
async with aiohttp.ClientSession(connector=connector) as session:
    session.get("https://self-signed.badssl.com")
 

八、常见坑 & 最佳实践

  1. 不要每次请求新建 ClientSession
     
    错误写法:循环里 async with ClientSession(),频繁创建销毁连接池,性能暴跌。
     
    正确:全局创建一个 Session,所有请求复用。
  2. 所有 IO 操作必须加 await
     
    session.get()resp.text()resp.json() 都是协程,不加 await 只会返回对象,拿不到数据。
  3. 必须使用 async with 自动关闭资源
     
    不使用 async with 需要手动 await resp.close()await session.close(),容易漏关造成连接泄漏。
  4. 并发不加信号量极易被封 IP
     
    爬取大量页面一定要配合 asyncio.Semaphore 限制并发。
  5. Windows 旧版本 asyncio 事件循环报错
     
    在程序开头添加兼容代码:
 
import asyncio
import sys
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
 
  1. gather 一个任务报错全部中断
     
    想要部分失败不影响其他任务,使用 return_exceptions=True
 
results = await asyncio.gather(*tasks, return_exceptions=True)
 

九、完整爬虫模板(可直接投产)

融合并发限制、异常捕获、会话复用、超时、UA
import aiohttp
import asyncio
import sys

# windows兼容
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

# 全局配置
MAX_CONCURRENT = 8  # 最大并发
SEMA = asyncio.Semaphore(MAX_CONCURRENT)
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
TIMEOUT = aiohttp.ClientTimeout(total=10)

async def fetch(url, session):
    async with SEMA:
        try:
            async with session.get(url, headers=HEADERS, timeout=TIMEOUT) as resp:
                resp.raise_for_status()
                return await resp.text()
        except Exception as e:
            print(f"失败 {url}: {str(e)}")
            return None

async def main():
    urls = [f"https://httpbin.org/get?id={i}" for i in range(20)]
    connector = aiohttp.TCPConnector(limit=50)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = [fetch(u, session) for u in urls]
        all_data = await asyncio.gather(*tasks)
    print("完成,有效数据数量:", len([d for d in all_data if d]))

if __name__ == "__main__":
    asyncio.run(main())

 

 

 

 

posted @ 2026-07-06 20:18  chenlight  阅读(4)  评论(0)    收藏  举报