优先用 Pillow 的 _getexif() 读取 EXIF 中 ID 为 36867 的 DateTimeOriginal 字段获取拍摄时间;若缺失则 fallback 到文件修改时间并告警;日期解析须用 strptime("%Y:%m:%d %H:%M:%S");路径操作用 pathlib.Path,移动用 shutil.move();I/O 密集型任务选用线程池而非进程池。

用 PIL.Image 和 exifread 读取拍摄时间,但优先选 Pillow 自带的 _getexif()
大部分 JPG/HEIC 图片的拍摄时间藏在 EXIF 的 DateTimeOriginal 字段里。别急着装 exifread——Pillow(即 PIL)自带解析能力,更轻量、兼容性更好,且能处理部分被裁剪或重保存后丢失 exifread 所需标签的图片。
注意:不是所有图片都有 DateTimeOriginal;有些只有 DateTime(修改时间),不能代替拍摄时间;HEIC 文件需额外安装 Pillow 的 HEIF 支持(如 pip install pillow-heif)。
-
_getexif()返回的是字典,键是数字 ID,得查映射表:36867对应DateTimeOriginal - 若返回
None,说明 EXIF 为空,跳过或 fallback 到文件修改时间(os.path.getmtime())——但务必加日志警告,避免误分类 - 读取失败时别直接抛异常,用
try/except OSError捕获损坏文件或权限问题
日期字符串转 datetime 时,用 strptime 而非 fromisoformat
EXIF 中的 DateTimeOriginal 格式固定为 "YYYY:MM:DD HH:MM:SS"(注意是英文冒号),不是 ISO 标准格式(ISO 用短横和 T 分隔)。用 datetime.fromisoformat() 会直接报 ValueError。
正确做法是用 datetime.strptime(exif_time, "%Y:%m:%d %H:%M:%S")。如果遇到个别设备写入不规范(比如秒数缺位、空格变制表符),加一层清洗:
立即学习“Python免费学习笔记(深入)”;
- 先
.strip().replace("\t", " ").replace(" ", " ") - 再切分空格,确保前半部分是
YYYY:MM:DD,后半部分长度 ≥ 7(至少含HH:MM:S) - 年份小于
1990或大于2050的时间戳大概率是错的,建议跳过并记录
按年/月建目录时,用 pathlib.Path 构造路径,避免 os.path.join 拼接出错
手动拼路径容易在 Windows 下漏掉反斜杠、Linux 下多加斜杠,或者忽略路径存在性检查。用 pathlib 更安全直观:
from pathlib import Path
target_dir = Path("sorted") / f"{dt.year}" / f"{dt.month:02d}"
target_dir.mkdir(parents=True, exist_ok=True)
shutil.move(str(src), str(target_dir / src.name))
注意:shutil.move() 的参数必须是字符串,Path 对象要显式转 str();exist_ok=True 防止并发或多进程时重复建目录报错;parents=True 确保年目录不存在时也自动创建。
- 别用
os.rename()——跨文件系统会失败,shutil.move()自动降级处理 - 移动前建议先
target_dir / src.name是否已存在同名文件,避免覆盖(可加if not (target_dir / src.name).exists():) - 大量文件时,移动比复制快,但务必确认源盘空间充足、无只读挂载
处理海量文件时,用 concurrent.futures.ThreadPoolExecutor,别用 multiprocessing
图片元数据读取是 I/O 密集型操作,不是 CPU 密集型。开多进程反而因进程启动开销和 IPC 拷贝拖慢整体速度;线程池更合适,且 Pillow 的 _getexif() 在 GIL 下仍可并发读磁盘。
实测:10 万张 JPG,在 4 线程下耗时约 3–5 分钟(SSD);开 32 进程可能卡死或触发系统句柄限制。
- 线程数设为
min(32, os.cpu_count() + 4)是较稳妥的起点 - 每个任务函数内必须独立打开图片(别复用
Image.open()对象),否则线程间可能冲突 - 加
timeout=30到executor.submit(),防止单张损坏图阻塞整个池
KeyError: 36867 或 OSError: cannot identify image file——这些不是代码逻辑问题,而是数据脏。先写个最小验证脚本,只读 10 张图、打印每张的 EXIF key 和时间,比直接上并发更省时间。


















