👋 这是一套围绕 MT Photos 数据恢复的实战脚本与流程说明,目标是:
- 从 PostgreSQL 导出的备份(.sql)与
file表导出的 JSON 清单中,批量在多块磁盘上定位原始文件; - 按数据库中原有目录结构进行复制与还原;
- 二次 MD5 校验确保数据无误;
- 最终批量更新 SQL 备份中的路径(可在必要时连带更新 MD5)。
脚本均使用 Python 标准库,无第三方依赖,适用于 macOS/Linux/Windows(示例命令以 macOS 为例)。
- 环境与准备
- 恢复总流程
- 步骤一:建立磁盘索引 build_index.py
- 步骤二:根据索引批量恢复 recover_files.py
- 步骤三:分析未恢复项 check_unfound_files.py
- 步骤四:二次 MD5 校验 verify_recovery.py
- 步骤五:批量更新 SQL 路径 update_sql_backup.py
- JSON 清单示例与字段说明
- 重要说明与常见问题
- 致谢与许可证
- Python ≥ 3.8
- 备份文件:
- PostgreSQL 导出的 SQL 备份(例如
mtphotos_backup-20250301.sql) file表导出的 JSON(例如file-nas.json)。本工具以该 JSON 作为“权威清单”。
- PostgreSQL 导出的 SQL 备份(例如
- 若干待搜索的磁盘/目录(NAS、外接盘等)。
- 目标恢复目录(建议单独磁盘/分区)。
强烈建议先对 JSON 和 SQL 文件做额外备份。脚本在关键步骤会自动生成
.bak文件,但多一层手工备份更稳妥。
- 建索引:对需要搜索的每个磁盘运行
build_index.py生成索引 JSON。 - 找文件:使用
recover_files.py结合清单与索引,按清单中的原始目录结构复制到指定恢复根目录,并把新路径回写到清单的new_path。 - 分析缺漏:运行
check_unfound_files.py查看还未找到的条目。若存在,给其他磁盘建立索引,再次执行第 2 步,可多轮迭代直至尽可能找到全部文件。 - 重新校验 MD5:运行
verify_recovery.py对已恢复文件进行 MD5 校验,补充new_md5并记录md5_check结果。 - 更新 SQL:使用
update_sql_backup.py读取“权威清单”更新 SQL 备份中的文件/文件夹路径;在必要时同步更新文件 MD5。
递归扫描一个搜索目录,建立“文件名 → 所有可能路径列表”的索引。
用法:
python3 build_index.py \
"/Volumes/NAS/photos" \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas-index.json"输出结构示例(简化):
{
"summary": {
"scan_directory": "/Volumes/NAS/photos",
"scan_date": "2025-10-11T12:34:56",
"total_paths_found": 123456,
"unique_filenames": 98765
},
"index": {
"IMG_20180414_192153.jpg": [
"/Volumes/NAS/photos/2018/04/IMG_20180414_192153.jpg",
"/Volumes/Backup/phone/IMG_20180414_192153.jpg"
]
}
}提示:
- 大目录扫描需要时间与内存,建议逐块磁盘/目录分别生成索引文件。
- 后续找文件时可多次使用不同索引文件迭代恢复。
读取“权威清单”(file-nas.json 等)与索引 JSON,将命中的文件复制到“恢复根目录”下,目录结构尽量保持与清单 path 一致;把实际复制到的新路径写回清单 new_path。
用法:
# 默认启用 MD5 校验(推荐)
python3 recover_files.py \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas.json" \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas-index.json" \
"/Volumes/RECOVER/mtphotos_recovered"
# 若不执行精细定位,可临时关闭 MD5 校验。注意!这可能导致恢复的文件并非原文件。
python3 recover_files.py \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas.json" \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas-index.json" \
"/Volumes/RECOVER/mtphotos_recovered" \
--no-md5行为要点:
- 默认 MD5 校验:当清单中含有
md5时,会对候选路径计算实际 MD5,确保内容一致再复制;若同名多处出现,仅择一并记录重复提示。 - 关闭 MD5(
--no-md5):仅按文件名命中后取第一个路径复制,速度快但有误判风险。脚本会在记录中添加recovery_note,并建议后续务必跑第 4 步校验。如果您很确信文件名可以定位到唯一的文件,可以关闭MD5校验(这个使用场景,常用于文件在MT Photos服务端被修改导致MD5不一致,无法通过MD5校验一致时进行兜底)。 - 目录重建:按清单
path去掉 Windows 盘符后重建层级,最终落在你指定的恢复根目录内。 - 日志:生成
recovery_log_YYYYmmdd_HHMMSS.txt,包含成功、重复、MD5 不一致、复制失败等分类明细。 - 清单回写:成功复制后把
new_path写回原清单(同时自动创建一次.bak备份)。
可多轮迭代:
- 如果一次索引无法覆盖所有来源(多块磁盘),先用 A 盘索引恢复一轮,再用 B 盘索引继续恢复,清单会被逐步补齐
new_path。
统计清单中仍缺少 new_path 的记录,便于针对性补建索引再找文件。
用法:
python3 check_unfound_files.py \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas.json"输出会列出未恢复文件名与原始路径,并给出合计数量。
对已恢复文件执行 MD5 校验,确保文件与数据库原记录一致;若原清单无 MD5,则生成 new_md5 供后续 SQL 更新使用。
用法:
python3 verify_recovery.py \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas.json"行为要点:
- 会先对清单生成
*.bak_verify_YYYYmmddHHMMSS备份。 - 对每条含
new_path的记录:- 若原始
md5存在:计算new_md5并与之比对,在md5_check标记 True/False;异常记录为error_*。 - 若原始
md5缺失:跳过比对,标记md5_check = "skipped_no_original_md5",同时写入new_md5(供后续 SQL 更新)。
- 若原始
- 结果写回原清单。
用“权威清单”(含 new_path、new_md5、md5_check)批量更新 PostgreSQL 备份 SQL 中的路径;必要时同步更新 MD5。
支持以下表与场景:
public.file:更新文件路径;当md5_check为 False 且存在new_md5时,同步更新 MD5。public.folder:既包含文件也包含文件夹路径:- 通过
name是否包含.粗分“文件/文件夹”场景; - 文件路径按文件映射更新;
- 文件夹路径使用“最长前缀匹配”的目录映射批量更新子层级。
- 通过
public.album_link:当type='folder'时,更新 JSONvalue.label中的文件夹路径。
用法:
python3 update_sql_backup.py \
"/Users/ke/Downloads/fromJsonLocateFile/file-nas.json" \
"/Users/ke/Downloads/fromJsonLocateFile/mtphotos_backup-20250301.sql" \
"/Users/ke/Downloads/fromJsonLocateFile/mtphotos_backup-20250301.updated.sql"处理说明:
- 同时支持
INSERT INTO ... VALUES (...)与COPY ... FROM stdin;两种导出形式。 - 对
public.file:- 若
name一致且旧 MD5(或备选new_md5)与记录匹配,且md5_check为 True,则仅更新路径; - 若
md5_check为 False 且new_md5存在,则同时更新路径与 MD5。
- 若
- 对
public.folder与public.album_link:采用目录映射与最长前缀规则,尽量保持原有子目录层级不变,仅替换根前缀。 - 结果输出到新的
.updated.sql,原 SQL 不会被覆盖。
典型的 file 表导出(简化示例):
[
{
"id": 123,
"name": "IMG_20180414_192153.jpg",
"path": "/Volumes/old/phone/2018/IMG_20180414_192153.jpg",
"md5": "17f6e7...",
"new_path": "/Volumes/RECOVER/mtphotos_recovered/phone/2018/IMG_20180414_192153.jpg",
"new_md5": "17f6e7...",
"md5_check": true
}
]- 必需字段:
name、path;若存在md5则可提升匹配准确度。 - 恢复写回字段:
new_path:recover_files.py成功复制后写入。new_md5、md5_check:verify_recovery.py写入。
- 建议逐盘建索引:索引结构为“文件名 → 路径列表”,体量大时内存占用较高。拆分为多份索引文件,分轮恢复更稳妥。
- 关于 MD5:
- 恢复阶段若关闭 MD5,只能保证“文件名命中”,存在误匹配风险。请务必执行第 4 步的 MD5 校验。
- SQL 更新时,只有在
md5_check=False且清单提供new_md5时才会替换 MD5;否则保留原值。
- 路径规范化:
- 脚本会去掉
C:这类 Windows 盘符并统一使用/,以便跨平台处理; - 恢复时会尽量复刻清单
path的相对目录层级。
- 脚本会去掉
- SQL 解析边界:
- 假设
INSERT INTO ... VALUES (...)为单行(可包含多条(...)); - COPY 行按制表符切分并遵循 PostgreSQL 文本 COPY 的转义规则;
- 若你的导出格式非常定制(多行 VALUES、特殊分隔),建议先在样本文件上试跑并审阅
.updated.sql。
- 假设
- 日志与备份:
recover_files.py会生成按类目分组的恢复日志;verify_recovery.py与recover_files.py会对清单做备份;update_sql_backup.py只写入新的输出 SQL,不会覆盖原始备份。
- 多轮恢复策略:
- 用 A 盘索引跑一轮恢复;
- 用
check_unfound_files.py看还差哪些; - 为 B 盘建索引继续恢复;
- 反复迭代直至尽可能全部补齐;
- 跑 MD5 校验与 SQL 更新。
- 代码以 MIT License 开源,欢迎提交 PR 改进更多表与字段的适配。
- 感谢MT Photos官方文档提供的灵感与最佳实践。