insert_many()配ordered=False是刚需,因默认ordered=True时单条失败(如字段错误、唯一冲突)会导致后续全部跳过,卡死爬虫或日志流程;设为False后失败不中断,成功ID仍返回,但需主动捕获BulkWriteError并解析e.details["writeErrors"]定位问题。

为什么insert_many()配ordered=False是刚需
爬虫或日志导入场景下,单条失败就中断整批写入会直接卡死流程。默认ordered=True时,只要第 3 条文档因字段类型错误或唯一索引冲突失败,后面 997 条全被跳过——你得自己拆包重试,还容易漏数据。
设为ordered=False后,失败只报错不中断,insert_many()仍返回成功插入的_id列表,失败项可通过result.upserted_ids和异常里的details定位。但注意:它不保证执行顺序,也不回滚已成功部分。
- 必须配合
try/except捕获BulkWriteError,不能只靠if result.acknowledged - 失败详情在
e.details["writeErrors"]里,含具体文档位置和错误码(如11000是重复键) - 别在循环里反复调用
insert_many()而不检查返回值——静默丢数据比报错更危险
batch size设成1000不是玄学,是网络与内存的平衡点
实测中,batch size 超过 5000 容易触发 MongoDB 的 maxMessageSizeBytes 限制(默认 48MB),尤其当文档含 base64 图片或长文本时;低于 100 又让网络往返次数飙升,吞吐掉一半。
1000 是多数场景下的甜点值:既压低了 TCP 包数量,又不会让单次请求吃光 Python 进程内存。若你处理的是平均 2KB/条的日志,1000 条 ≈ 2MB,安全冗余充足。
立即学习“Python免费学习笔记(深入)”;
- 用
len(json.dumps(chunk))粗估序列化后大小,超过 16MB 就强制拆分 - 别依赖
estimated_document_count()判断 batch size——它不反映实际字节长度 - 如果用
bulk_write()替代insert_many(),同样要控制每个requests列表长度,逻辑一致
MongoClient实例必须全局复用,否则连接池会雪崩
每新建一个MongoClient实例,默认开一个最多 100 连接的池子。爬虫开 10 个协程,每个都初始化 client → 瞬间 1000 连接打到 MongoDB,立刻触发Connection refused或ServerSelectionTimeoutError——这时 MongoDB 没挂,是客户端把连接数堆爆了。
正确姿势是模块级单例,且显式配置连接行为:
-
maxPoolSize=50防资源耗尽,minPoolSize=5保冷启动响应速度 -
serverSelectionTimeoutMS=3000避免卡在副本集主节点选举上 -
connectTimeoutMS=2000和socketTimeoutMS=5000防止某台机器网络抖动拖垮整个流程 - 务必在应用退出前调用
client.close(),否则连接泄漏会随时间累积
去重更新别用find()+insert_one(),upsert才是并发安全解
两个爬虫进程同时查url="https://example.com",都得到空结果,然后都执行insert_one() → 唯一索引冲突或重复数据。应用层判断永远赢不了竞态。
用update_one({"url": url}, {"$setOnInsert": doc}, upsert=True),MongoDB 内核保证原子性:查无则插,有则忽略。但注意两点:
-
$setOnInsert只在真正插入时生效,避免覆盖已有字段;若需更新存量字段,改用$set并确保upsert=True - 必须提前在
url字段建唯一索引,否则upsert无法识别“已存在”,会不断插入新文档 - 别在插入过程中建索引——
db.collection.create_index("url", unique=True)要离线做,否则写入吞吐暴跌 70% 以上


















