Python如何通过asyncio构建高性能爬虫_优化并发连接与响应解析
绝大多数时候该用 asyncio.create_task(),它强制要求在运行中的事件循环内调用,语义明确、出错更早;ensure_future() 隐式行为易掩盖调度错误。
asyncio.create_task() 和 asyncio.ensure_future() 用哪个?
绝大多数时候该用 asyncio.create_task(),它是 Python 3.7+ 推荐的启动协程方式。直接调用 asyncio.ensure_future() 容易误传普通函数、或在非事件循环上下文中触发 RuntimeError: no running event loop。
-
create_task()强制要求在运行中的事件循环内调用,语义明确,出错更早、更可读 - 如果传入的是协程对象(
coro),ensure_future()会自动包装成 Task;但若传入的是Future或Task,它就只是原样返回——这种隐式行为容易掩盖调度逻辑错误 -
爬虫中批量发起请求时,应统一用
create_task()包裹每个fetch(url)协程,避免混用导致任务未被调度
aiohttp.ClientSession 的连接池怎么不生效?
常见现象是并发量上不去、响应延迟陡增,甚至报 ClientConnectorError: Cannot connect to host —— 很可能没复用 ClientSession 实例,或者配置了过小的连接限制。
- 必须在整个爬虫生命周期内**复用同一个
ClientSession实例**,不能每次请求都新建:新建 session 会重建 TCP 连接池和 cookie jar,彻底失去复用价值 - 默认连接池上限是 100,但实际受
limit和limit_per_host双重约束;高并发场景建议显式设置:aiohttp.TCPConnector(limit=300, limit_per_host=50) - 记得在爬虫结束时调用
session.close(),否则连接不会释放,下次运行可能遇到OSError: [Errno 24] Too many open files
解析 HTML 用 aiohttp + BeautifulSoup 还是 httpx + selectolax?
纯异步解析不是“越快越好”,而是看阻塞点在哪。BeautifulSoup 在解析阶段会同步阻塞事件循环,而 selectolax 是 C 扩展、无 GIL 锁,且 API 原生支持 bytes 输入,更适合 aiohttp 流式响应。
Stable Diffusion 2.1 Demo
最新体验版 Stable Diffusion 2.1
下载
- 如果用
response.text()获取字符串再喂给BeautifulSoup,等于把异步 IO 的收益在解析阶段全吃掉了;改用response.read()拿bytes,再交给selectolax.Parser,全程不阻塞 - 注意
selectolax不支持 CSS 选择器里的某些伪类(如:nth-child),复杂页面仍需退回到lxml+asyncio.to_thread()做隔离 - 不要在
async for chunk in response.content.iter_chunked(1024)中边流式读取边解析——HTML 解析需要完整文档结构,流式解析仅适用于已知格式的简单文本提取
如何控制并发请求数又不卡死事件循环?
用 asyncio.Semaphore 是最直接的方式,但它只限流不保序;若需按优先级或 URL 模式动态调整,并发控制就得结合队列与工作协程模型。
立即学习“Python免费学习笔记(深入)”;
- 初始化一个
sem = asyncio.Semaphore(20),所有 fetch 请求前加async with sem:,就能硬性限制同时最多 20 个请求发出 - 别用
time.sleep()或asyncio.sleep()做请求间隔——这会让整个协程挂起,浪费并发能力;应该让每个 task 自己 sleep,或用asyncio.wait_for()控制单个请求超时 - 真实爬虫常遇到反爬限速,此时需对特定域名单独建
Semaphore,比如sems["example.com"] = asyncio.Semaphore(3),否则全局限速会拖慢其他正常站点
真正难的不是并发数字调多大,而是连接复用、解析阻塞、限速策略这三者怎么咬合。漏掉任意一环,asyncio 写出来的代码跑起来可能比 requests + threading 还慢。