用motor替代pymongo是解决爬虫写入mongodb阻塞问题最有效的方式,但必须配合异步下载流程使用;单独换库不改架构反而更慢,因事件循环被同步操作阻塞。

直接说结论:用 motor 替代 pymongo 是解决爬虫写入 MongoDB 时阻塞问题最有效的方式,但必须配合异步下载流程使用;单独换库不改架构,反而可能因事件循环被阻塞而更慢。
为什么 pymongo 在异步爬虫里一定会阻塞
pymongo 所有操作(insert_one、update_many 等)都是同步阻塞的,哪怕你把它丢进 async def 函数里,它也会让整个 asyncio 事件循环停住,直到网络 IO 和数据库响应完成。这和 aiohttp 的非阻塞本质冲突。
常见错误现象:
- 用了
asyncio发请求,但一调collection.insert_one(...),后续所有请求就卡住几秒 - 日志显示“event loop blocked for X seconds”,CPU 占用低但吞吐量骤降
- 并发数设为 100,实际同时活跃连接不到 10 个
如何正确接入 motor 实现真正异步存储
关键不是“装上 motor 就行”,而是替换整条链路中的同步节点。以下是最小可行配置:
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
- 安装:
pip install motor(不要卸载pymongo,motor依赖它) - 初始化客户端必须用
motor.motor_asyncio.AsyncIOMotorClient,不能复用pymongo.MongoClient - 所有集合操作必须用
await:比如await collection.insert_one(data),不能漏掉await -
motor客户端本身是线程安全的,但**不能跨线程共享**;若混合多线程解析,存储必须在异步线程中统一调度,不能从解析线程直接调await
示例片段:
from motor.motor_asyncio import AsyncIOMotorClient
<p>client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client["crawler_db"]
collection = db["items"]</p><p>async def save_item(item):
await collection.insert_one(item) # ✅ 正确:await + motor 方法
</p>
混合架构下,存储模块该放在哪一层
在“异步下载 + 多线程解析”架构中,motor 存储模块必须作为独立异步协程运行,不能塞进解析线程里。否则解析线程会因等待 await 而退化成同步执行。
- 推荐结构:异步下载 → 队列 → 多线程解析 → 解析结果再入另一个
asyncio.Queue→ 单独一个异步 consumer 协程批量写入motor - 避免反模式:
threading.Thread中直接await motor_collection.insert_one(...)(会报 RuntimeError:no running event loop) - 性能提示:用
insert_many批量写入比单条快 5–8 倍,但要注意文档总大小不超过 16MB 限制
容易被忽略的连接池陷阱
motor 默认连接池行为和 pymongo 类似,但配置项名称不同,且不支持 minPoolSize 这类参数——它只暴露 max_pool_size 和 min_pool_size(注意大小写),且 min_pool_size 在较新版本中已被弃用。
- 必须显式设置:
AsyncIOMotorClient(..., max_pool_size=50),否则默认只有 100,高并发下容易耗尽 - 别信文档里“自动管理”的说法:当并发写入突增时,连接池扩容有延迟,建议预热或略高于预期峰值
- 检查连接状态用:
client.options.max_pool_size,而不是看client.nodes(后者只反映拓扑,不反映实际连接数)
真正卡住的地方往往不是代码逻辑,而是异步/同步混用时那个没加 await 的调用,或者把 motor 客户端传进了线程函数里——这种错误不会立刻报错,但会让整个并发模型失效。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










