必须先清洗nan/none等非法值并建唯一索引,再用upsert插入;对非结构化字段建稀疏索引;生产环境需配合schema validator控制字段语义漂移。

插入前必须先处理 NaN 和 None 值
爬虫数据里混着 NaN、None、空字符串甚至嵌套的 float('nan'),直接丢给 collection.insert_many() 会报 InvalidDocument: Cannot encode object。MongoDB 的 BSON 标准不支持这些值,PyMongo 也不会自动转换。
实操建议:
- 用
pandas.isna()或math.isnan()检测浮点型 NaN,统一转为None(BSON 允许null) - 对字典字段做递归清洗:遇到
None或NaN就删掉该键,或替换成占位符如"MISSING"(视业务而定) - 避免用
df.fillna("N/A")一刀切——字符串型字段填"N/A"可能干扰后续 NLP 分析 - 插入前加一层校验:
if not isinstance(v, (str, int, float, bool, dict, list, type(None))):过滤掉datetime、Decimal等非原生类型(需手动转)
用 upsert=True 避免重复插入和脏数据
爬虫常重跑或分片抓取,同一 URL 或 ID 可能多次入库。不用 upsert,靠应用层去重既慢又不可靠;硬删再插又破坏写入原子性。
实操建议:
- 定义唯一索引:
collection.create_index("url", unique=True)或复合索引collection.create_index([("source", 1), ("item_id", 1)], unique=True) - 用
collection.update_one({"url": item["url"]}, {"$set": item}, upsert=True)替代insert_one - 注意:如果
item含_id字段且是 ObjectId 类型,upsert会按_id匹配;否则只按查询条件匹配 - 别在循环里反复调
create_index()—— 放到脚本开头或部署时执行一次即可
对嵌套字段建稀疏索引提升查询效率
非结构化数据字段名不固定,比如有的商品有 "specifications",有的只有 "attrs",还有的字段值是空列表或缺失。普通索引会为缺失字段存 null,浪费空间且拖慢查询。
实操建议:
- 对高频查询但可能缺失的字段建稀疏索引:
collection.create_index("price", sparse=True) - 对嵌套字段用点号语法:
collection.create_index("product.specifications.weight", sparse=True) - 避免对整个子文档建索引(如
"product"),容易触发“索引爆炸”——每个子键都单独索引 - 用
explain("executionStats")验证查询是否真走了索引,尤其注意nReturned和totalDocsExamined是否接近
批量写入时控制 bypass_document_validation=True 的风险
开发阶段关掉 schema 校验能加快插入速度,但上线后若没配好 validator,脏数据会直接污染集合,后期清洗成本极高。
实操建议:
- 仅在本地调试或导入历史快照时临时启用:
collection.insert_many(docs, bypass_document_validation=True) - 生产环境务必配合
collMod设置 validator,例如限制price必须为正数:{"$jsonSchema": {"properties": {"price": {"type": "number", "minimum": 0}}}} -
bypass_document_validation不跳过 _id 冲突检查,也不绕过唯一索引约束 - 如果用了 PyMongo 4.0+,validator 修改后需重启连接或显式调
client.admin.command("collMod", ...)生效
非结构化数据入库最麻烦的不是插入动作本身,而是字段语义漂移——今天 "score" 是整数,明天爬虫返回字符串,后天变成嵌套对象。得靠预处理规则 + 索引策略 + validator 三层卡口,少一层都容易在某次重跑后发现数据查不出来。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











