微信群照片归档(Windows 版)
把工作群里刷屏的照片自动归集 → 去重 → 按群名/年月整理 → 存到你指定的文件夹(可选再镜像一份)。
开箱即用:装好、跑一句命令就行,不需要 NAS、不需要第二块硬盘。
照片默认落到 %USERPROFILE%\Pictures\微信照片归档\,
想换地方只改 config.yaml 里的 paths.root 一行。
想多留几份,再往 sync.targets 里加几个路径(本地文件夹 / 移动硬盘 / 网盘同步目录 / NAS 都行),
加几个都行,一个不加也照常工作。
这是 wechat-photo-archive 的 Windows 移植版。原版是给 macOS 写的:脚本里写死了
/Users/.../.workbuddy/binaries/python/...、用 rsync 同步、用 openssl 解密企微回调、
用 launchd 做常驻、用 ~/Library/Containers/com.tencent.xinWeChat/... 找微信缓存。
这些在 Windows 上一个都不能用,所以本版逐项替换,并针对 Windows 微信的实际情况重写了采集层。
典型场景
> 群里天天有人发照片,电脑上微信缓存几十个 G,但一张都不敢删。
- 不敢删:删了怕哪天要找,聊天记录一过期就再也翻不出来
- 不好找:想找三个月前某张现场照,只能靠记忆在聊天记录里往上翻
- 占空间:C 盘长期告急,微信
xwechat_files/WeChat Files一天天变大
用它之后:群里的照片自动落到你指定的文件夹(想的话再镜像一份到别处),按「哪个群 / 哪年哪月」
分好目录、重复图只留一份。本机微信缓存和归档库都留了底,手机和电脑都能放心清理。
适合:项目/施工/巡检群、门店与销售群、家校与班级群、社团活动群,以及任何"照片刷屏但得留档"的群。
相对 macOS 版的替换清单
| 环节 | macOS 原版 | Windows 版 | 为什么必须换 |
|---|---|---|---|
| 解释器 | /Users/…/binaries/python/…/bin/python3 |
%USERPROFILE%\.workbuddy\binaries\python\versions\<版本>\python.exe |
路径完全不同 |
| 归档库位置 | ~/Pictures/微信照片归档 |
同左(Windows 上展开为 %USERPROFILE%\Pictures\微信照片归档),可一行改到任意位置 |
Path.expanduser() 按平台解析 |
| 额外备份 | rsync -a --stats … 到 NAS |
robocopy /E 到任意数量的目标(本地文件夹 / 移动硬盘 / 网盘同步目录 / NAS),可选 |
Windows 无 rsync;robocopy 是系统自带 |
| 企微解密 | 调 openssl enc -aes-256-cbc |
内置纯 Python AES(scripts/aescbc.py) |
Windows 无系统 openssl,Git Bash 那个也不在 PATH |
| 常驻 | launchd plist + nohup |
任务计划程序(schtasks) | Windows 无 nohup / launchd |
| 微信缓存 | ~/Library/Containers/com.tencent.xinWeChat/… |
注册表 + Documents\WeChat Files / xwechat_files + 全盘浅扫 |
路径不固定且可被改盘 |
.dat 解密 |
单字节异或(按首字节反推 key) | 按魔数分 V0/V1/V2 三种格式处理 | 微信 4.x 是 V2(AES 头),原算法会误判成 BMP 假阳性 |
另外顺手修掉了一批 Windows 专属的坑:文件名保留字(CON/PRN/AUX…)、
结尾的点与空格、MAX_PATH 260 字符上限、微信占用文件导致的 WinError 32。
三条通道能拿到什么(重要,先看这个)
| 通道 | 能否识别群名 | 拿到的文件 | 备注 |
|---|---|---|---|
| 企业微信 官方回调 | ✅ 可映射 | 可查看的 jpg/png | 群里 @应用 才推;官方 API,零风险 |
| 个人微信 · 本机缓存(3.x) | ✅ 从目录名取会话标识 | 可查看的 jpg/png | .dat 是单字节异或,完全可还原 |
| 个人微信 · 本机缓存(4.x) | ✅ 从目录名取会话标识 | 视频/封面可看;图片默认不可查看 | 图片是 V2 加密,见下 |
| 手机相册同步目录 | ✅ 目录即群名 | 可查看的 jpg/png | Android/iOS 都可行;4.x 用户推荐走这条 |
⚠️ 微信 4.x 图片的现实(务必先读)
微信 4.x(2025 年起的主流版本,缓存目录是 Documents\xwechat_files\)把聊天图片
加密成了 V2 格式:文件头 1024 字节用 AES-128-ECB 加密,尾部用单字节异或。
- 异或那部分能还原(密钥可以从同目录带
_t的缩略图投票反推,技能已自动做) - AES 那部分不行:那是账号级密钥,不存在磁盘上,只在
Weixin.exe进程内存里
本技能只读、不破解、不扫进程内存(和原版"不碰协议、不破解数据库"的设计前提一致)。
所以对 4.x 的图片,默认行为是:
- 按原样
.dat归档(keep_undecodable_dat: true)—— 保证一张都不丢 - 想要能直接看的图片,二选一:
- 在config.yaml填personal.dat_aes_key(你自己拿到密钥后填,见
references/wechat-dat-format.md)
- 走「手机相册同步目录」通道拿原图(4.x 用户最省事的路,纯官方手段)
4.x 的视频不受影响:msg\video\<年月>\*.mp4 和 *_thumb.jpg 都是明文,照常归档 ✅
目录布局
%USERPROFILE%\Pictures\微信照片归档\ ← 改 config.yaml 的 paths.root 即可换位置
├── _staging\ # 采集缓冲(各通道原始落地 + 同名 .json 元数据)
│ ├── 企业微信\
│ └── 个人微信\
├── 库\ # 正式归档库(这就是「你指定的文件夹」)
│ └── <通道>\<群名>\<YYYY-MM>\
├── _index\manifest.jsonl # 去重索引(sha256 + 来源 + 时间)
└── _logs\ # archive.log / wecom-receiver.log
镜像目标(sync.targets)默认是空的 —— 没有 NAS 的机器什么都不用配。
想加就加,数量不限,可以是本地文件夹、移动硬盘、网盘同步目录或 NAS:
sync:
targets:
- name: 本地备份
path: D:/微信照片归档备份
- name: 移动硬盘
path: E:/照片归档
- name: NAS
path: Z:/Drive/微信照片归档 # 映射的网络驱动器
# - name: NAS-UNC
# path: \\你的NAS\共享名\微信照片归档 # UNC 直连,不用映射盘符
简写也行(只写路径):targets: [D:/微信照片归档备份, E:/照片归档]。
某个目标没插硬盘 / NAS 没开机时,只跳过那一个,其余照常。
快速开始
先定位微信缓存(必做,别手抄路径):
$SK = "$env:USERPROFILE\.workbuddy\skills\wechat-photo-archive-win"
$PY = "$env:USERPROFILE\.workbuddy\binaries\python\versions\3.13.12\python.exe"
& $PY "$SK\scripts\find_wechat_dirs.py" # 输出结论 + 可直接粘贴的 config 片段
& $PY "$SK\scripts\archive.py" doctor # 环境体检
把 find_wechat_dirs.py 给的片段合并进 config.yaml 的 personal.sources,然后:
& $PY "$SK\scripts\archive.py" ingest # 采集 → 归档 → (有配镜像就顺带镜像)
装常驻(企微通道必须常驻,media_id 只活 3 天):
& $PY "$SK\scripts\install_task.py" install --daily 20:00
& $PY "$SK\scripts\install_task.py" status
curl http://127.0.0.1:8099/health # 应返回 ok
命令速查
& $PY "$SK\scripts\archive.py" ingest # 三步串起来(日常就这一条)
& $PY "$SK\scripts\archive.py" scan # 只采集到 _staging
& $PY "$SK\scripts\archive.py" organize # staging → 库(sha256 去重、归类)
& $PY "$SK\scripts\archive.py" sync # 库 → 各个镜像目标
& $PY "$SK\scripts\archive.py" status # 积压 / 库存 / 镜像目标状态
& $PY "$SK\scripts\archive.py" doctor # 环境体检
& $PY "$SK\scripts\archive.py" ingest --since 30d # 只扫最近 30 天(默认 7d)
& $PY "$SK\scripts\archive.py" ingest --dry-run # 只打印计划,不落盘
& $PY "$SK\scripts\archive.py" organize --prune-staging # 归档成功后清空缓存区
首次配置
编辑技能根目录的 config.yaml(改完即时生效):
paths.root—— 归档放哪儿,一行搞定(默认~/Pictures/微信照片归档)。
想把库单独放别处,填paths.library;staging/index/logs留空则自动派生。sync.targets—— 可选的额外备份位置,不配就是纯本机归档。personal.sources—— 用find_wechat_dirs.py的输出替换。
建议保留group_from: auto(自动按会话分目录)和chat_alias(把会话 hash 映射成群名)。wecom_api.*—— 企业微信 corpid / secret / token / encoding_aes_key。
留空也能跑个人微信通道。
企微回调要公网地址,用 Cloudflare Tunnel 加一条 ingress 指向本机 127.0.0.1:8099/wecom/callback,
Windows 上的具体步骤见 references/setup-wecom-windows.md。
归档规则
- 去重:sha256 命中 manifest 则跳过,同一张图重复收发只留一份
- 命名:
YYYY-MM-DD_HHMMSS_<hash前8位><ext>,目录库\<通道>\<群名>\<YYYY-MM>\ - 时间来源:企微用消息 CreateTime;本机微信只能取文件 mtime
- 格式识别:靠 magic bytes 判定真实类型(jpg/png/gif/webp/heic/mp4/mov)
.dat分流:V0 完整还原;V2 有密钥则还原,无密钥按原样归档并在 manifest 标记- 分组:
group_from: auto取"年月目录的前一级"当会话标识 —— 微信 4.x 的
msg\attach\<会话hash>\…和 3.x 的FileStorage\MsgAttach\<会话hash>\…都能识别 - 不删源:staging 默认保留,需
--prune-staging才清;源文件永远只读
脚本一览
| 脚本 | 作用 |
|---|---|
scripts/archive.py |
主入口(scan / organize / sync / ingest / status / doctor) |
scripts/find_wechat_dirs.py |
定位 Windows 微信缓存,输出可粘贴的 config 片段 |
scripts/wx_local_scan.py |
个人微信通道采集(含 group_from 自动分组) |
scripts/wxdat.py |
微信 .dat 解码器(V0/V1/V2)+ 可单独批量补解 |
scripts/wecom_receiver.py |
企业微信回调接收器(常驻,异步下载) |
scripts/install_task.py |
计划任务管理(替代 launchd)+ 每日自动归档 |
scripts/aescbc.py |
纯 Python AES-128/192/256 + CBC/ECB(替代 openssl) |
scripts/mediainfo.py |
类型识别、Windows 文件名清洗、长路径、重试拷贝 |
scripts/wpaths.py |
路径解析:paths.root 派生 + 可选的镜像目标列表 |
scripts/miniyaml.py |
极简 YAML 解析(不依赖 pyyaml) |
坑
- 镜像同步只增不删:只用
robocopy /E,绝不加/MIR或/PURGE
(那会删掉目标端比源端多的文件)。sync结束后会对比两端文件数做核对。 - 某个镜像目标不可达不算错:没插移动硬盘、NAS 没开机、盘符没映射,都只跳过那一个目标。
想让它当硬错误处理,把该目标的skip_if_unmounted设为false。 - 企微 media_id 只活 3 天:接收器挂了就丢图。
archive.py status会提示 staging 积压天数。 - 微信 4.x 图片需要账号级 AES 密钥:见上文「微信 4.x 图片的现实」。
密钥可能随微信版本更新而失效,.dat里带的V2魔数可用于核对格式。 - 微信会占用文件:采集时若报
WinError 32,技能会自动重试;仍在写入的图下次再采即可。 - 长路径:
%USERPROFILE%很深 + 中文群名容易超过 260 字符。技能会自动加\\?\前缀,
但更稳的做法是开启系统的长路径支持,或把库放到D:\WXPhotoArchive这种浅目录。 schtasks被安全策略禁用时:install_task.py会降级导出 XML,
用「任务计划程序 → 操作 → 导入任务」手工导入即可。- 别在微信正在同步时大批量采集:先让微信把缓存写完,跑起来更顺。