需通过gridfs.gridfs实例操作,显式指定数据库;用fs.find({})遍历所有文件,清洗文件名、分块读取大文件,保存元数据并校验本地文件完整性以实现安全导出。

用 pymongo 连接 GridFS 并列出所有文件
GridFS 不是普通集合,不能直接 find(),必须通过 gridfs.GridFS 实例操作。连接时注意:如果 MongoDB 启用了认证或非默认端口,connection_string 必须包含用户名、密码、数据库名和端口;否则会连上但查不到文件(因为 GridFS 默认使用 fs 前缀的两个集合:fs.files 和 fs.chunks,而这些集合归属指定数据库)。
常见错误现象:fs.list() 返回空列表,但确认库里有文件——大概率是连错了数据库,或者初始化 GridFS 时没传对 database 对象。
实操建议:
- 先用
mongoshell 连上,运行use your_db_name; db.fs.files.countDocuments({})确认文件存在 - Python 中显式传入
database,不要依赖客户端默认库:from pymongo import MongoClient import gridfs client = MongoClient("mongodb://user:pass@localhost:27017/") db = client["your_db_name"] fs = gridfs.GridFS(db) -
fs.list()返回生成器,转list()才能遍历或取长度
逐个下载文件并保存到本地路径
GridFS 文件名可能含斜杠、空格、Unicode 字符,直接用作本地文件名会出错(如 OSError: [Errno 22] Invalid argument)。同时,fs.get_last_version() 只取最新版,而 fs.find({}) 才能拿到全部版本(含历史上传记录)。
实操建议:
- 用
fs.find({})遍历所有文件文档,避免漏掉同名多版本 - 对每个
file_obj,用file_obj.filename获取原始名,但需清洗:import re safe_name = re.sub(r'[:"/\|?*]', "_", file_obj.filename)
- 用
fs.get(file_obj._id)获取文件内容流,再写入二进制文件(别用文本模式):with open(f"./export/{safe_name}", "wb") as f: f.write(file_obj.read()) - 大文件(>10MB)建议分块读取,避免内存爆满:
grid_out = fs.get(file_obj._id) with open(..., "wb") as f: for chunk in iter(lambda: grid_out.read(8192), b""): f.write(chunk)
处理文件元数据与自定义字段
GridFS 支持在 fs.files 文档中存任意字段(如 uploadDate、contentType、metadata),但默认导出脚本不保留这些信息。如果需要按时间筛选、按类型分类或还原原始路径,就得主动读取并参与命名或目录结构构建。
常见错误现象:导出后一堆同名文件覆盖,或无法区分 PDF 和图片。
实操建议:
- 检查
file_obj.metadata是否为dict,再取值,避免AttributeError - 用
file_obj.uploadDate生成子目录,例如:f"./export/{file_obj.uploadDate.strftime('%Y-%m')}/{safe_name}" - 若
file_obj.contentType存在且是image/jpeg,可强制加.jpg后缀(前提是原名无后缀) - 导出前先建好目标目录:
os.makedirs(os.path.dirname(filepath), exist_ok=True)
避免重复导出和中断恢复
全量导出耗时长,网络抖动或程序崩溃会导致部分文件写一半就停。没有断点续传机制,重跑脚本会重复下载已存在的文件,浪费带宽和时间,还可能因同名覆盖导致旧文件丢失。
实操建议:
- 导出前检查本地文件是否存在且大小匹配:
os.path.exists(filepath) and os.path.getsize(filepath) == file_obj.length - 把已成功导出的
_id记到一个临时文件(如exported_ids.txt),下次启动时跳过 - 写入文件后,再追加写入
_id到记录文件(确保原子性),否则崩溃时记录滞后于实际写入 - 不推荐用文件名去重——GridFS 允许不同
_id有相同filename,仅靠名字无法唯一识别
真正麻烦的不是“怎么读出来”,而是“怎么安全地落地”:文件系统限制、MongoDB 权限粒度、元数据语义缺失、以及中途失败时的状态一致性。导出脚本跑半小时后崩了,你得能立刻知道卡在哪、哪些已经完成、哪些要重来——这些细节比调通第一行 fs.get() 更关键。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











