aiohttp 因异步非阻塞、连接复用高效而适合高并发爬虫,但需手动管理会话、连接池、DNS 缓存及错误重试,否则易触发文件描述符超限或被封 IP。

为什么用 aiohttp 而不是 requests 做高并发爬虫
requests 是同步阻塞的,每个请求发出去后必须等响应回来才能继续,100 个链接串行跑可能要几十秒;aiohttp 基于 asyncio,单线程就能并发发起几十甚至上百个 HTTP 请求,CPU 不卡死、连接复用也更高效。
但别误以为“用了 aiohttp 就自动快十倍”——实际吞吐受目标服务器限速、DNS 解析、SSL 握手、本地文件描述符限制共同影响。没做连接池控制或错误重试时,反而容易触发 ClientOSError: [Errno 24] Too many open files 或被对方封 IP。
- 默认不复用 TCP 连接,得手动配
aiohttp.TCPConnector(limit=100) - 不处理 DNS 缓存,高频请求下
getaddrinfo可能成瓶颈,建议加limit_per_host=30防止单域名压垮 -
requests的 session 复用是隐式的,aiohttp的ClientSession必须显式创建+关闭,漏掉await session.close()会泄漏连接
如何正确启动一个 asyncio 爬虫任务
别在 Jupyter 或普通 Python 脚本里直接写 await fetch(...),会报 RuntimeError: no running event loop。必须用 asyncio.run() 启动,或者在已有 event loop 中用 create_task()。
常见错误是把同步逻辑混进协程:比如在 fetch 函数里调 time.sleep(1),这会阻塞整个 event loop;该用 await asyncio.sleep(1)。
- 入口函数必须是
async def main(),然后用asyncio.run(main())启动 - 批量请求推荐用
asyncio.gather(*tasks),不是for await逐个等——后者退化成串行 - 如果要控制并发数(比如最多同时 20 个请求),用
asyncio.Semaphore(20)包住session.get()调用
aiohttp 抓到 403/429 却没报错?怎么判断请求是否真成功
aiohttp 默认不会因为 HTTP 状态码非 2xx 抛异常,resp.status 是 403 你也照常拿到响应体,容易误判为“成功获取”。尤其遇到反爬时,返回的可能是伪装成 200 的登录页或验证码 HTML。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
必须主动检查状态码和内容特征:
- 加
if resp.status >= 400:判断,并记录resp.reason - 对关键页面,检查
resp.content_type == 'text/html'和是否含预期文本(比如'),避免把 403 页面当正常数据解析首页 ' - 注意重定向:默认
allow_redirects=True,但跳转后状态码是最终页的,原始 URL 的状态已丢失,需设max_redirects=0手动控制
为什么本地跑得飞快,一上服务器就报 ConnectionResetError 或超时
多数情况不是代码问题,而是服务端对异步客户端更敏感:短连接频次高、User-Agent 单一、缺少必要 headers(如 Accept、Accept-Language),被 WAF 当成扫描器拦截。
Linux 服务器还有两个隐藏限制:
- 文件描述符上限低(
ulimit -n默认常为 1024),aiohttp并发数超这个值就会报OSError: [Errno 24],需改/etc/security/limits.conf - 临时端口耗尽:
net.ipv4.ip_local_port_range默认是 32768–65535,高并发短连接下很快用完,可扩到1024 65535 - 别忽略 DNS:服务器常没配好 DNS 缓存,
aiohttp每次都走系统getaddrinfo,延迟飙升,建议加resolver=aiohttp.AsyncResolver()
真正难调的从来不是并发数设多少,而是让每个请求看起来像真人点开的浏览器——Header、间隔、连接复用、退出前清理,缺一不可。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










