微信文件清理与归档

SKILL生活服务
生活服务 未标注权限 许可证不明确
安装这个技能

电脑端微信接收文件的清理与归档:自动定位文件目录 → 只读盘点 → 去重(完全重复/版本重复/跨格式 + 四道保护 + 专用看板)→ 规则化分类 → HTML 决策看板批量拍板 → 回传对账 → 安全执行(备份+MD5+回收站)→ 内容探针反向抽查 + 误判一键回滚。分类规则全部外置在 JSON 里,不含任何个人目录与客户名,换台机器填自己的规则即可复用。触发场景:磁盘空间告警、要清理微信/企微接收的文件、要把微信文件归档进自己的目录、或用户提到"微信文件/重复文件/去重/转移/空间紧张/清理收尾/归档错了要撤回"。删除前强制备份+MD5,删除走回收站并提示清空才真正释放空间。

累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_1b9a0379/wx-file-clean-ty

技能档案

生活服务
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_1b9a0379仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 1791711400
正文体量48.8 KBSKILL.md 原文大小,越大说明说明书写得越细

SKILL.md 全文

48.8 KB
模型实际读到的说明书原文,来自技能仓库

wx-file-clean-ty

电脑端微信接收文件的清理与归档工具链。通用版:脚本里不含任何具体目录、客户名或行业词,
全靠使用者填一份 rules.json 驱动。

两根支柱:

  1. 规则化分类——把几千个杂乱文件判到正确的归档目录(阶段 4,最值钱的部分)
  2. 决策看板——把判定结果交给用户批量拍板,一个一个问是问不完的(阶段 4 末)

去重是可选的减量步骤(阶段 3),属于支线。一次真实的清理里,主力链路是
「盘点 → 共议 → 分类 → 看板 → 执行」,去重只贡献了约 14% 的减量。


首次使用:四步初始化

换一台机器、换一个人,只做这四件事,代码一行不用改:

# 1. 定位微信文件目录(自动找,找不到会提示你手动指定)
python scripts/locate_wechat.py

# 2. 只读盘点,看看面对的是什么量级
python scripts/scan_inventory.py --root "<上一步得到的目录>" --out-dir ./work

# 3. 从你的真实文件归纳规则草案(不是让你照抄模板)
python scripts/suggest_rules.py --inventory ./work/inventory.csv \
    --out ./work/rules.draft.json --min-files 3 --top 25

# 4. 把草案的 A-E 五段确认表交给使用者,逐条确认后再定稿成 rules.json

第 3、4 步是这套工具的立场:规则不该由工具强加,也不该让使用者凭空填模板。
先扫你的真实文件,从文件名里归纳候选词,把「命中多少文件 / 多大 / 举什么例」摆出来,
你增删合并之后才定稿。脚本内置的只有「通用功能词」和「安全保护底线」,
与行业、职业、公司无关——真正的分类词全部来自你自己的数据。

规则写得好不好,看 references/classification-guide.md。


When to use

  • 磁盘空间告警,要清理微信/企微接收的文件
  • 要把微信收到的文件归档进自己的目录体系
  • 用户提到「微信文件 / 重复文件 / 去重 / 转移 / 空间紧张 / 清理收尾」

Hard rules(不可违反)

  1. 先确认目标目录是不是链接。用户常把微信文件目录用 junction / 符号链接挂到别处。
    链接只是指针,它的体积不等于占用空间,绝不能当可释放空间上报,也绝不能删除或断开。
    用 locate_wechat.py 会自动标注 is_link 与指向。
  2. "文件名 + 大小一致" ≠ 内容一致。任何基于此判据的删除,执行前必须双侧 MD5 交叉验证。
  3. 只报告、不删除,除非用户明确批准。删除一律走回收站,禁止 rm -rf / del /F。
  4. 清空回收站必须单独获批,不能与「删除」混为一谈。
  5. 同盘移动不释放空间。只有「删除 + 清空回收站」才释放——开工前必须先讲清(阶段 0)。
  6. 未授权不新建目录。用户在面板里填的路径 = 授权;否则目录不存在就先问。
  7. 用户勾「删除」但命中保护区 → 暂缓,单独列出来再问一次,绝不照单执行。
  8. 任何批量移动都必须可回滚:执行器每次都写 <tag>_移动清单_<时间戳>.csv
    (含 源路径 / 目标路径 / MD5)。没有这张清单就不许执行移动——
    分类一定会判错,判错之后能不能捞回来,是这套工具能不能用的分水岭。
  9. 主题词不能单独当类型判据。「洞察 / 分析 / 研究 / 趋势 / 观察」是主题词,
    内部汇报、客户提案、竞品拆解都可以叫「XX洞察」。判定文档类型要靠
    发布年份期号 / 机构署名 / 法律声明这三类特征,累加计分、凑够才归类,
    凑不够降级为待确认。
    ⚠ 别用"三选二":机构署名覆盖率只有 25%,一旦当必要条件就会漏掉四分之三的真报告。
    正确姿势是"命中即加分,没命中只是少一分",详见 references/classification-guide.md 第七节。

七阶段 SOP 总览

阶段 0  空间账       先讲清"移动不释放空间",避免沟通错位
阶段 1  定位与体检   locate_wechat.py:找目录 + 辨链接 + 量体量
阶段 2  盘点→共议    全程只读:scan_inventory → suggest_rules → 你确认 → rules_lint
阶段 3  去重(可选)  dedup_scan → 去重面板拍板 → apply_batch 删除 → **重跑盘点**
阶段 4  分类→看板    classify_generic → gen_panel 出 HTML → 用户批量拍板
阶段 5  回传与对账   三段式回收 + 数量对账(不对账会漏文件)
阶段 6  安全执行     apply_batch.py:备份+MD5 → 进回收站 → 移动(写移动清单)
阶段 7  收尾         反向抽查(content_probe)→ 误判回滚(rollback)→ 清回收站 + 对账
附录 A  归档后二次去重(仅 A 类对照)
附录 B  脚本工具箱
附录 C  规模参考

阶段 0:空间账(开工前必须讲清)

最容易沟通错位的地方。用户以为「把 10 GB 文件移走 = 腾出 10 GB」,实际同盘移动只是指针搬家。

动作 是否释放空间 说明
移到同盘的归档目录 ❌ 一个字节都不释放 价值在归档,在找得到,不在瘦身
删除到回收站 ❌ 未真正释放 回收站也在同一个盘
清空回收站 ✅ 真正释放 必须单独批准

开场白模板:「这次要做的两件事得分开看——归档(把文件归位,不腾空间)和瘦身(删除+清空回收站,才腾空间)。你想先要哪个?」

> 边界:本技能只管微信接收文件目录。全盘找空间大头(浏览器缓存、旧工作区、虚拟机镜像)属于另一件事,别在这里展开。


阶段 1:定位与体检

1.1 自动定位(不要硬编码路径)

python scripts/locate_wechat.py              # 列出全部候选
python scripts/locate_wechat.py --only-file  # 只打印接收文件目录,供管道消费

查找顺序:环境变量 WXFILES_ROOT → 命令行 --root → 系统里记录的微信文件管理路径
(Windows 注册表 HKCU\Software\Tencent\WeChat)→ 默认候选(Windows ~/Documents/WeChat Files、
~/Documents/xwechat_files、各盘根目录;macOS ~/Library/Containers/com.tencent.xinWeChat/...)
→ 在这些根下找特征子目录。

两类目录要分清:

特征路径 含义 处理
msg/file、FileStorage/File 接收的文件 本技能的清理目标
msg/attach 图片与缓存 体量常常比 file 大数倍,清理前必须单独确认,别按"文件"的方式归档

1.2 校验链接(硬规则 1)

locate_wechat.py 会输出 is_link 与 target。看到链接时:

  • 它不是一份占空间的副本,只是指针
  • 删除 / 断开它 = 直接破坏微信的文件目录
  • 判断真实占用来看 target 指向的那个盘

若脚本没认出来,Windows 上手工确认:

(Get-Item -LiteralPath '<路径>' -Force).LinkType

1.3 盘空间与体量

python scripts/scan_inventory.py --root "<file 目录>" --out-dir ./work

输出 inventory.csv(全量清单)+ 分布画像:扩展名 TOP15、体积分层、月份分布、最大 20 个文件、
同名同大小的可疑重复组数。

低于 5 GB 可用空间才展开全量清理;否则给一行状态即可。


阶段 2:盘点 → 共议(只读,最值钱的部分)

铁律:分类阶段只读,绝不改盘。 产出是一张映射 CSV,用户拍板后才动。

2.1 先盘点,别急着定规则

python scripts/scan_inventory.py --root "<file 目录>" --out-dir ./work

拿到 inventory.csv(全量清单)+ 画像:扩展名 TOP15、体积分层、月份分布、最大 20 个文件。

2.2 让它归纳草案,规则由你定(本阶段的核心)

不要照抄模板,也不要凭空想关键词。先让脚本从你的真实文件里归纳:

python scripts/suggest_rules.py --inventory ./work/inventory.csv \
    --out ./work/rules.draft.json --min-files 3 --top 25

它会输出一张五段确认表,这五段要原样交给使用者逐条确认:

段 内容 使用者要做的决定
A. 建议建组 候选词 + 命中文件数 + 体积 + 示例文件名 留哪些、删哪些、合并哪些、目录叫什么
B. 短词待定 1-2 字的高频词 是品牌名还是通用词?后者挪进 need_context
C. 扩展名画像 各类扩展名数量与体积 要不要按扩展名建组(安装包 / 素材)
D. 保护底线 合同、录音、证件、简历 这是安全相关的默认值,可增删
E. 体积阈值 <1MB 碎片的占比 skip.min_mb 定多少

脚本已经做了两件容易出错的事,但仍需你复核:

  1. priority 按「命中文件数越少 = 越具体」自动排序——实测不这么排的话,
    宽泛的「客户」(6 个) 会把具体的「品牌方案」(4 个) 全部吞掉。子集关系会在 A 段标
    「是「XX」的子集,已排在其前面」。
  2. 已被保护区覆盖的词不再建组(如「合同」),建了也不生效。

草案里每个组都带 _evidence(命中多少文件 / 多大 / 举什么例),
没有证据的词不要凭感觉加。default_action 一律为「看」——删不删是人的决定。

定稿三处必改:① archive_root ② 删掉不认可的组 ③ 调 priority。
改完把 _evidence 和 _comment 删掉即可(分类器会忽略它们)。

2.2b 分类之前,先给规则做个体检(必做一步)

规则定稿后、跑分类器之前,先过一遍静态体检:

python scripts/rules_lint.py ./work/rules.json            # 有 E 级就退出码 1
python scripts/rules_lint.py ./work/rules.json --out lint.md --strict

它不检查"你的词表对不对"(那是 2.2 里人工确认的事),只检查结构性风险——
那些"看起来能跑、跑起来必出事"的模式。最该拦的是 E1:

> 一条规则只有 any 一个维度,词表里却是「洞察/分析/研究/趋势」这类主题词。
> 主题词只说明文件在讲什么,说明不了文件是什么,单独定罪必误判。

体检报告每条都写了为什么和怎么改,照着改即可。E 级清零再往下走;
W 级看情况;I 级是提示,多数可以忽略。

改完校验器本身也跑一次自检,确认没改坏:

python tests/selftest_rules_lint.py

2.3 规则文件字段说明

字段 作用
archive_root 归档根,分组的 dir 里用 {{archive_root}}/xxx 引用
skip 不纳入决策:小于 N MB 的碎片、明确噪音词、黑名单扩展名
protected 保护区:命中即进「待人工」组,永不给删除建议
groups[].priority 数字越小越先匹配——顺序错了会出事故
groups[].any / all / not 命中任一 / 必须全部命中 / 命中即排除
groups[].ext 扩展名白名单,空 = 不限
groups[].need_context 短词与通用词专用:命中后不直接归类,转「待确认」。它本身就是判据,不必同时写进 any
groups[].default_action 移 / 删 / 留 / 看(别轻易写「删」)

跑分类:

python scripts/classify_generic.py \
  --inventory ./work/inventory.csv --rules ./rules.json \
  --out ./work/batch1_ID映射.csv --prefix B

输出映射 CSV,列:ID, 文件名, 大小MB, 月份, 分组, 默认动作, 默认目录, 源路径, 判断依据。
同时打印分组分布与跳过原因 TOP5,以及一行自检提示(映射行数必须 == 面板 DATA 条数)。

2.4 分类方法论(五条,与行业无关)

完整版在 references/classification-guide.md,这里只列标题,写规则时必须过一遍:

  1. 漏斗原则:先具体后宽泛——三种典型顺序事故(合同被客户吞、分享稿被客户吞、服务商被当客户)
  2. 排除规则必须分三级力度(HARD / STRONG / SOFT)——「解决方案」这类词放错级别会误伤一批白皮书
  3. 误命中防呆:品牌名=通用词、项目名≠主体名、短词要有上下文、扩展名要隔离
  4. 阈值:小于 1 MB 的碎片数量占一半、体积只占百分之一二,不值得逐个决策
  5. 主题词 ≠ 类型词——见下面 2.6,这是「同事发来的工作文件被当成行业报告」的根因

2.5 保护区(命中即人工,绝不批量删)

类别 理由
录音、会议转录 不可再生
合同、报价、授权书、发票 版本差异是实质性的(主体、金额不同)
带编号的图片/音视频(图(5)) 编号是素材序号不是版本号
盖章/签署版 PDF 盖章版常是 PDF,docx 原件反而是未盖章草稿
证件、体检、病历、家庭材料 个人资料
简历 涉及他人个人信息
用户勾「删除」但同批有同类文件被勾「保留」 高度疑似误勾,暂缓再问

2.6 文件名会骗人:主题词 ≠ 类型词

真实事故:两份内部数据分析 PPT,文件名是「XX分群复购产品消费者洞察.pptx」,
被规则里的「洞察」关键词判成行业报告,搬进了报告库。
打开第一页就能看到 XX指数=(特征在目标群体中的占比%/特征在总体中的占比%)*100
——这是同事做的数据分析,不是第三方出版物。

根因:洞察 / 分析 / 研究 / 趋势 / 观察 / 白皮书 / 报告 是主题词,不是文档类型词。
四类完全不同的文档都可以叫「XX洞察」:行业报告、内部汇报、客户提案、竞品拆解。

真正的类型特征是另外三样,累加计分、凑够才归类,凑不够降级为待确认:

特征 例子 实测覆盖率
发布年份 / 期号 2026、第57期、Vol.3 70%
机构署名 XX研究院、XX咨询、XX证券、公司全称 25%,命中即采信
法律声明 免责声明、版权归…所有、未经授权不得转载 命中即采信

⚠ 不要写成"三选二"。 在 1249 份真实报告上量过:机构署名只有 25% 能检出,
一旦把它当必要条件(要求"必须有机构"),会漏掉四分之三的真报告——
要求"机构 且 年份"同时成立时,只能认出 20.4%。
正确用法是命中即加分,没命中只是少一分,不是反证。

四道防线,按发生顺序排,越靠前越便宜。建议全部做:

# 时机 防线 做法 成本
① 分类之前 规则体检 rules_lint.py 静态扫描规则,把"主题词单独定罪""缺类型证据"等模式拦在执行之前 秒级
② 写规则时 规则层 主题词写进 need_context 而不是 any,单独命中不归类 改一次 JSON
③ 归档之后 内容层 content_probe.py 读首屏文字反向抽查 几分钟
④ 出事之后 可回滚层 执行器必写移动清单,判错了 rollback.py 原路搬回 零

防线 ① 是后来补的。起因是复盘那次事故时发现:规则设计原本写的是
「报告特征词 + 年份」两个条件,但执行时用的是另一个脚本,那里只留了单条件
「关键词=洞察」——年份条件在从设计落到执行的过程中丢了。
而那份文件名里
恰好没有年份。规则本身没错,是没人检查"写下来的规则"和"跑起来的规则"
是不是同一条。rules_lint.py 就是用来堵这个口子的。

# ① 规则体检:分类之前必做一步,有 E 级就必须改完再跑
python scripts/rules_lint.py rules.json
python scripts/rules_lint.py rules.json --out lint.md --strict   # 出报告 + W 级也卡

# ③ 内容探针:先看信号词有没有噪声,再正式扫,最后出复核清单
python scripts/content_probe.py --dir &quot;&lt;归档目录&gt;&quot; --signals signals.json --audit-words
python scripts/content_probe.py --dir &quot;&lt;归档目录&gt;&quot; --signals signals.json --out probe.csv --cache probe.cache.jsonl
python scripts/probe_report.py probe.csv --out 复核清单.html

# ④ 判错了就捞回来
python scripts/rollback.py ./work/W类_移动清单_0831.csv --filter 洞察          # dry-run
python scripts/rollback.py ./work/W类_移动清单_0831.csv --filter 洞察 --go

rules_lint.py 查什么(有 E 级就退出码 1,可以直接卡在流程里):

级别 检查项
E1 主题词单独定罪:规则只有 any 一个维度,且词表里含洞察/分析/研究/趋势等主题词
E2 出版物类分组缺类型证据:组名像"报告/白皮书",判据里却既无年份也无机构词
E3 兜底层默认动作是"删":没被任何规则认领的文件会被直接建议删除
W1 宽泛词排在具体词前面:词 A 是词 B 的子串且 A 先匹配,B 永远拿不到文件
W2 排除词力度过重:解决方案 这类双面词放靠前的层排除,会误伤一批白皮书
W3 "删"类规则没有体积下限:碎片文件也被建议删除,收益极小、误删面扩大
I 单维判据无辅助 / 没有保护区 / 两字词直接当判据(可忽略)

体检只覆盖结构性风险。词表本身贴不贴合你的文件,仍要靠 suggest_rules.py
归纳后人工确认——这两件事不能互相替代。

content_probe.py 的关键设计(都是实测踩出来的):

  • 信号词分 strong / weak 两级权重。只按「命中任一词」判定,一份厂商公开方案
    会因为正文里出现一次大厂名就被判成内部产出。强词(行业黑话、口径术语、版本标记)
    命中 1 个即成立;弱词(大厂名、渠道名、通用商务词)要攒够 3 个不同词。
    实测同一批文件:加权前 31 个疑似内部,加权后 8 个,误报降了 3/4。
  • 必做一次 --audit-words,揪出噪声词。实测里「微信」在 1249 份真报告中命中 54.6%,
    放进 internal 直接把探针搞失效。
  • --cache 缓存正文。解 1000+ 个 PDF 要几分钟,调信号词要反复迭代,
    有缓存后第二次 2 秒出结果。
  • 机构署名单列一组 orgs(可选,老词表没有这个键也能跑)。「谁发的」比「讲什么」
    硬得多。之所以不直接并进 published.strong:同一个机构会被切成好几个词
    (XX大学国家发展研究院 里同时含 XX大学 和 研究院),混在一起就重复计分,
    看着像命中一堆证据,其实只有一家机构。单列后判定只看「有没有机构」,不看「命中几个词」。
    具名机构命中 1 个即采信;半具名(XX学院/XX研究中心)并进 published 弱词池一起攒。
    实测:加入 40 个具名机构后,「无信号」341 → 333(8 份靠机构定案),
    疑似内部一个没变 —— 纯增量、零误伤。
  • 机构是加分项不是门槛:命中就采信,没命中不等于不是报告。实测 1247 份里机构只覆盖
    10.7%(具名)/ 24.7%(含泛化后缀),大量报告首屏根本不写机构。
  • 探针有盲区:图片型 / 扫描件 PDF 抽不出文字。实测 267 个「无信号」PDF 里
    147 个属于这类,「无信号」不等于「没问题」。

阶段 2.7:混合态文件 —— 人工决策看板(按需,非必做,位于阶段 2 共议内)

探针会扫出一类「两边都像」的文件:同时命中 internal 和 published 信号
(实测 1247 份里有 52 份,约 4%)。

⚠ 先算账,再决定要不要开这个环节(实测教训)

2026-09-06 实测:1247 份 → 58 份进入人工决策 → 最终只挪动 2 份(0.16%)。
人工决策环节的假阳性率 97%。让人盯 58 份找出 2 份,ROI 极低。

所以开这个环节前先自问两件事:

  1. 默认保留的代价是多少? 如果错放率已经 <1%,
    直接一律保留,把省下的时间用到「新文件入库拦截」上。
  2. 这些混合态文件是不是已经清过几轮了? 存量目录清过 2 轮以上,
    剩下的基本都是灰色地带的泛资料,再抠也抠不出东西。

判断标准(满足任一就不开): 错放率 <1% · 已治理 2 轮以上 · 混合态 >50 份。

# ① 生成可勾选的 HTML(--targets 预置常用归档目录,逗号分隔;不填则只能手填)
python scripts/build_decision_board.py --csv probe.csv --out 决策看板.html \
  --targets &quot;D:/归档/客户资料,D:/归档/分享资料&quot;

# ② 使用者在页面上勾选:保留 / 移出 / 删除,导出 CSV

# ③ 预演(默认 dry-run,不动任何文件)
python scripts/exec_decisions.py 决策清单.csv

# ④ 确认计划无误后真执行
python scripts/exec_decisions.py 决策清单.csv --apply

页面能力:三选一 + 目标目录下拉/手填 + 筛选(未决策/保留/移出/删除)+ 按可疑度排序
+ 自动存 localStorage(刷新不丢)+ 导出 CSV/JSON + 一键「未决策的都保留」。

exec_decisions.py 的安全设计(没有这些就别交付):

措施 说明
默认 dry-run 不加 --apply 只打印计划
执行前预检 源文件不存在 / 移出未填目标 / 目标目录不存在,全部拦下并退出码 1
删除走回收站 调 recycle_delete.py(SHFileOperation + FOF_ALLOWUNDO),可还原
移动校验 MD5 搬完比对,不一致就记录「移动后MD5不一致」
写执行日志 exec_log_&lt;时间戳&gt;.csv,含源路径、目标、结果、MD5,可回滚

预标注的三个坑(都踩过):

  1. 别把公司名/产品名当内部信号。使用者所在公司的官方白皮书是公开行业资料,
    不是内部文件;讲协同办公的报告必然提到该司的会议产品名。这类词和「微信/小程序」
    一样是噪声,放进强信号会误伤。分两级:强信号(内部资料/保密/立项/验收,+3 分)、
    中等信号(公司名/产品名/作者,+1 分)。
  2. 噪声词要在页面上显式划掉。把 微信 / 小程序 / 版本号 / 口径 / 复盘 / 转化率
    这类渲染成灰色删除线,使用者一眼能看出「这个证据不成立」,比藏起来更可信。
  3. 预标注只排序不拍板。给「保留 / 看一眼 / 移出」三档建议 + 一句判据理由,
    最终决定权在人。
  4. 🔴 先验区分力,再调参数(本条最重要,写在这里是因为我栽过)。
    2026-09-06 实测:预标注建议移出 5 个,使用者采纳 0 个,假阳性率 100%。
    根因不是权重调得不好 —— 是「正文内部词」这个特征本身区分力为零
    (58 份待决策文件 58 份全命中,包括保留的 56 份)。
    零区分力的特征,怎么调权重都是噪声。

动手做预测前,先跑一张「特征 × 决策」对照表,例如:

特征 keep move delete
正文有内部词 56 1 1
文件名含客户名 0 1 0

一个把所有样本都判成正例的特征 → 信息量为零 → 直接弃用,别再调它。
样本极度不平衡时(正例只有个位数),任何统计规律都不可靠,直接承认机器判不了。


阶段 3:去重(可选,跑在阶段 2 盘点之后、阶段 4 分类之前)

> 先去重能让分类阶段少搬一堆垃圾;反过来先分类后去重,归档基线会一直在变,对不上账。

3.1 三步走

# 1) 扫描:只读,算出重复组并套四道保护
python scripts/dedup_scan.py --root &quot;&lt;微信 file 目录&gt;&quot; \
    --inventory ./work/inventory.csv --out-dir ./work
#   --inventory 可选:裁剪到盘点范围,并复用里面的 MD5 列省一大笔时间
#   产物:去重分组.csv(看板输入)/ 去重_ID映射.csv(执行输入)/ 去重统计.json

# 2) 出决策面板
python scripts/gen_dedup_panel.py --groups ./work/去重分组.csv \
    --out ./work/去重面板.html --key dup1
node scripts/check_html_js.js ./work/去重面板.html          # 四项全绿才交付

# 3) 面板拍板 -&gt; 复制结果文本 -&gt; 执行(复用阶段 6 的执行器,安全网零改动继承)
python scripts/apply_batch.py 决策结果.txt --map ./work/去重_ID映射.csv --tag dup1
python scripts/apply_batch.py 决策结果.txt --map ./work/去重_ID映射.csv --tag dup1 --go

# 4) 去重后必须重跑盘点,再往下做分类
python scripts/scan_inventory.py --root &quot;&lt;file 目录&gt;&quot; --out-dir ./work

3.2 三类组

类型 判据 默认建议 风险
exact 完全重复 MD5 字节级一致 保留最新,删其余 零风险,删副本没有任何信息损失
version 版本重复 同一扩展名内、规范化名相同、内容不同 保留最新,删其余 中,必须过四道保护
format 跨格式 可编辑原件 + pdf/图片导出件并存 全部保留,不预勾任何删除 高,只提示不自动判

一个文件只进一个组,优先级 exact &gt; version &gt; format,天然无 C/D 叠加冲突
(这是老版 dedup_analyze.py 的坑:C 判删、D 判留,同批撞车 10 个)。

norm() 规范化名:去扩展名 → 去 (1)(2) → 去尾部 v1/v1.2 → 去 8 位以上日期 → 去空白 → 转小写。

3.3 四道保护(version / format 生效,命中整组不预勾删除)

# 保护 为什么
1 图片/音视频不参与版本去重 插画 (5)~(13).png 是十个素材,编号是素材序号不是版本号
2 保留项体积 < 被删项 × 50%(pdf 用 70%) 方案(1).docx 只有 0.03 MB 却要删 25 MB 带图版,那是精简版不是新版
3 合同/协议/报价/发票/确认函/证件/简历整组不删 省不了多少空间,丢了是事故
4 format 组里 pdf 体积 > 原件 3 倍 导出件比原件大这么多,很可能含独立内容

exact 组不走保护:字节级完全一致,留一份删副本没有信息损失。
保护规则针对的是"版本差异可能有实质内容"(主体、金额、盖章不同),不适用于副本。

实测参考:C90 组 / D36 组原始可删 156 个 0.81 GB,四道保护拦下 71 个(约 45%),最终安全删 105 个。

3.4 决策面板能做什么

粒度 操作
整组 保留最新删其余 / 保留最大删其余 / 全部保留 / 恢复默认
单文件 每个文件单独点「保留」或「删除」
保险 每组至少保留一份——手动把整组全标删除时会被拦下并提示
其他 按类型筛选、只看被保护组、搜索文件名、展开折叠、预览、打开所在文件夹、localStorage 按 --key 存

被保护或被判 format 的组,卡头会写明原因(红底/黄底),默认一个都不删。

3.5 对账

  • 映射 CSV 行数 == DATA 条数(gen_dedup_panel.py 会打印,必须一致)
  • dry-run 第一行「删除 N / 保留 M」必须满足 N + M == 面板总条数
  • 执行后确认每一组都还剩至少一份(删完跑一次 dedup_scan.py,组数应显著下降)

阶段 4:分类 → 决策看板

4.1 生成

python scripts/gen_panel.py \
  --map ./work/batch1_ID映射.csv --out ./work/决策面板.html \
  --title &quot;微信文件清理 · 决策面板&quot; \
  --subtitle &quot;待决策文件清点&quot; \
  --key wxclean_batch1 \
  --result-title &quot;【微信文件清理 · 决策结果】&quot; \
  --dirs-root &quot;&lt;你的归档根&gt;&quot;
  • --dirs-root:目录自动补全的候选根(填你自己的归档根目录)
  • --tips:可选 JSON {分组名: [默认动作, 说明, 色值]},覆盖自动推导的组说明与配色
  • 映射 CSV 必需列:ID, 文件名, 大小MB, 月份, 分组, 默认动作, 默认目录, 源路径

> 生成器会打印「目录树 N 条」。N=0 说明你的归档根下还没建子目录,
> 面板里就没有补全候选,只能手敲全路径——先按 references/taxonomy.example.md 把目录树建好再来。

4.2 固化能力清单

能力 说明
懒渲染 组里 >60 条自动折叠,几千条也不会卡死
组级一键动作 全部移动/删除/保留
组级跳过已选行 已做过选择的文件保持不变,只改没选的,toast 报告保留数
填目录自动勾移动 行级/组级填了目录 → 自动勾「移动」(填了目录就是要移动)
文件名就地编辑 可改名 + 批量清脏后缀((1) ~$ 等)
目录自动补全 datalist 内置归档根的两级目录树
跨组搜索 按文件名搜索
👁 预览 / 📂 打开文件夹 新标签页打开
ID 区间压缩输出 B0001-B0036 而不是 36 行,防结果文本被截断
localStorage 批次隔离 按 --key 存,每批次必须不同,否则串批

目录选择只能手填 + 自动补全,别再试别的方案:原生「选择文件夹」按钮
(showDirectoryPicker 只给 handle 不给路径)、&lt;input webkitdirectory&gt;(只有相对路径)、
拖文件夹(交互不稳定)——三条路都验证过不可行或体验差。

4.3 交付前双绿校验(缺一不可)

node scripts/check_html_js.js ./work/决策面板.html

检查:JS 语法 / 事件里调用的函数是否都有定义 / DATA 条数 + ID 唯一 / 标签配对。

外加一条:映射 CSV 行数 == DATA 条数。曾经漏写 writerows 让映射表只剩表头,
是靠执行阶段报错才暴露的。


阶段 5:回传与双对账

5.1 回传三段式

  1. 结果文本(面板「生成结果文本」复制出来的 TSV):
    ID区间 \t 动作 \t 目标目录 [\t 新文件名] B0001-B0036 move &lt;归档根&gt;/学习资料 B0041 move &lt;归档根&gt;/素材 新名.pdf
    未选 = 不动,不输出。delete 可以只有两列。
  2. 映射 CSV(阶段 4 产出,执行器靠它反查源路径)
  3. 执行 + 反查:apply_batch.py 用 ID 反查源路径,校验后执行

5.2 三数对账(硬要求,不做会漏文件)

解析出的决策数 + 未选数 == 面板总条数

实战教训:有一次回传 937 项、面板 1080 条、未选 143,937+143=1080 ✓,看起来对上了。
但对账之前,解析器有个 bug 把「delete 且无目标目录」的两列行静默跳过了——
838 个删除只解析出 119 个。是拿总数对账才发现的。

> 所以:dry-run 第一行输出「移动 N / 删除 N / 保留 N」,必须拿去和面板数字对账,
> 对不上先查解析器再执行。


阶段 6:安全执行

python scripts/apply_batch.py 决策结果.txt --map ./work/batch1_ID映射.csv --tag batch1        # dry-run
python scripts/apply_batch.py 决策结果.txt --map ./work/batch1_ID映射.csv --tag batch1 --go    # 执行

执行顺序与安全规则:

  1. 删除:先完整备份 + MD5 到 _delete_backup_&lt;tag&gt;_&lt;MMDD&gt;,再走回收站(分批、失败即停)
  2. 移动:os.replace + 移动前后 MD5 比对,不一致即报失败
  3. 目标同名 → 自动加 (副本),绝不覆盖
  4. 同批内同名(两个文件移到同一目录且同名)→ 视为不同版本,自动加 (来自&lt;源子目录&gt;) 后缀共存
  5. 改扩展名一律拦截(防手滑把 .pdf 写成 .pdf.txt)
  6. 区间两端 ID 前缀/位数不一致、ID 不存在、源路径消失 → 全部报错中止

常见残留:Office 文件被 Word/WPS 锁住会移动/删除失败。不要强杀进程——
那可能正是用户正开着的文件。报告路径,等用户关闭后补处理。


阶段 7:收尾

  1. 先复查,别急着收尾:对这次搬进归档目录的文件跑一次 content_probe.py
    + probe_report.py。文件已经离位了,但捞回来只要一条命令(见 2.6 ③)。
    这一步在真实清理里抓出过「内部数据分析被当成行业报告」这类误判。
  2. 清空回收站(单独获批):先跑 scripts/inspect_recycle_bin.py 解析 $I 元数据,
    还原每条已删文件的原路径/大小/时间,与删除清单比对,确认回收站里没有用户的其他残留,才清空。
  3. 对账:映射源路径仍存在数 vs 盘上实际文件数,差异项列出来(通常是期间新收到的文件)。
  4. 规模记录:记下这一批的移动/删除量级、踩到的新坑、待办项(被锁文件、未闭环的决策)。

附录 A:归档后的二次去重(A 类对照)

> 主力的去重在阶段 3(dedup_scan.py,微信目录内部找重复,配决策面板)。
> 这一节只讲阶段 3 覆盖不到的那一层:微信目录与你的归档目录之间的对照。
> 时机是阶段 6/7 归档搬完之后——那时归档侧才有东西可对照。

A.1 对照层(微信侧 ↔ 归档侧)

层 判据 风险 处理
A 与归档目录内文件同名 + 同大小 零(归档侧已有) MD5 交叉校验后可删微信侧副本,无需转移

exact / version / format 三类已并入阶段 3,见上文 3.2。四道保护同样在 3.3,这里不重复。

A.2 为什么 version / format 叠加在新版不会再撞车

老版 dedup_analyze.py 里 version 和 format 分别扫描同一批文件,
会出现「同一文件被 version 判删、被 format 判留」——首次运行抓出 10 个冲突
(三个版本的 pptx 同时被 version 判删、被 format 判留)。

新版 dedup_scan.py 用一个文件只进一个组(优先级 exact > version > format)从结构上消除了冲突,
不需要再做「删除集 ∩ 保留集 = ∅」的断言。老脚本保留在 scripts/ 里仅作历史参考。

A.3 删除前 MD5 交叉校验

A 类逐一对微信侧与归档侧算 MD5,只有一致才进最终清单;不一致的写进日志并排除。
同名 + 同大小 ≠ 内容一致,这条硬规则(见 Hard rules 2)在这里最容易翻车。


附录 B:脚本工具箱

skills/wx-file-clean-ty/
├── SKILL.md                      本文件
├── references/
│   ├── rules.example.json        ★ 规则模板(复制成 rules.json 改)
│   ├── classification-guide.md   ★ 分类方法论四条
│   ├── signals.example.json      ★ 探针信号词模板(strong/weak 两级 + orgs 机构组)
│   └── taxonomy.example.md       ★ 归档树设计指南 + 三套骨架
├── scripts/
│   ├── locate_wechat.py          ★ 自动定位微信文件目录(跨平台,辨链接)
│   ├── scan_inventory.py         ★ 只读盘点 + 画像 + inventory.csv
│   ├── suggest_rules.py          ★ 从真实文件归纳规则草案 + 五段确认表
│   ├── rules_lint.py             ★ 规则体检:分类之前拦住结构性风险(E/W/I 三级)
│   ├── classify_generic.py       ★ 规则驱动分类器(读 rules.json → 映射 CSV)
    ├── gen_panel.py              ★ 决策面板生成器
    ├── panel_template.py         ★ 面板固定主体(gen_panel 依赖,别手改)
    ├── apply_batch.py            ★ 回传结果批量执行器(产出移动清单 = 回滚凭据)
    ├── check_html_js.js          ★ 面板交付校验(JS语法/事件函数/DATA/标签)
    ├── content_probe.py          ★ 内容探针:读首屏文字判「内部产出/正式出版物」
    ├── probe_report.py           ★ 探针结果 → 可点击复核清单 HTML
    ├── rollback.py               ★ 按移动清单原路回滚(误判归档的补救手段)
    ├── build_decision_board.py   ★ 混合态文件 → 可勾选决策 HTML(含预标注)
    ├── exec_decisions.py         ★ 执行决策 CSV(默认 dry-run,删除走回收站)
    ├── recycle_delete.py         ★ 删到回收站(exec_decisions 依赖,可独立用)
    ├── merge_dirs.py             ★ 重复目录合并(同名冲突检测 + MD5 + 可回滚)
    └── analyze_decisions.py      ★ 特征区分力检验(做预测前必跑,见阶段 2.7)
    ├── dedup_scan.py             ★ 去重扫描:exact/version/format 三类 + 四道保护
    ├── gen_dedup_panel.py        ★ 去重决策面板(整组一键 + 单文件拍板)
    ├── dedup_analyze.py          老版四层去重(仅历史参考,见附录 A.2)
    ├── exec_recycle_delete.ps1   回收站删除(纯 ASCII 无 BOM)
    └── inspect_recycle_bin.py    回收站 $I 解析盘点
└── tests/
    ├── selftest_rules_lint.py            ★ rules_lint 自检(改完校验器跑一遍)
    ├── selftest_content_probe_orgs.py    ★ 探针机构组自检(含向后兼容反例)
    ├── selftest_dedup.py                 ★ 去重自检(norm + 四道保护反例 + 端到端冒烟)
    └── selftest_panel_behavior.js        ★ 面板交互行为自检(node,改完 panel_template.py 跑)

脚本通用化约定:任何绝对路径都不写死在代码里,一律走命令行参数或 rules.json。
改规则不改代码——这是这套工具能分享给别人的前提。


附录 C:规模参考

这一节的作用:让第一次做这件事的人有个量级感,知道大概要花多久、能期待什么结果。
数字来自一次真实清理,只保留规模信息。

一次典型清理:微信文件目录 3516 个文件 / 24.4 GB,分十个批次处理完,
累计移动约 1500 个 / 12.6 GB、删除约 1700 个 / 8.7 GB。

值得记住的三件事:

  • 分类 + 决策面板贡献了主要减量(约 86%),去重只贡献约 14%。
    别一上来就埋头找重复文件。
  • 保护规则拦下的比例很高:去重阶段原始判定可删 156 个,四道保护拦下 71 个(约 45%)。
    自动判定必须配保护规则,不然就是在赌。
  • 对账救回过 719 个文件:解析器静默跳过了一批删除项,是靠「决策数 + 未选数 == 总条数」
    这个等式发现的。这一步不能省。

Pitfalls(按阶段)

阶段 1 定位

  • 链接目录不是副本,它的体积不等于占用空间,删除/断开会直接破坏微信文件目录。
  • msg/attach 体量可能比 msg/file 大数倍,两者性质不同,别混为一谈。
  • 微信的文件管理路径可以在设置里改,注册表或默认路径都可能不准 —— 找不到就问用户。

阶段 4 分类

  • priority 顺序错了会出事故:合同必须排在客户名之前,分享稿必须排在客户名之前,
    服务商必须排在客户名之前。
  • 泛词不能当独立判据。把 AI、Agent、解决方案 这类词单独当判据,会把第三方的
    报告全捞进来,数量虚高数倍。
  • 主体名单里混进项目名/活动名 → 凭空生成假目录。只放品牌或公司名。
  • 短词直接归类必错 → 走 need_context 转人工确认。
  • n-gram 会切出边界怪词(「周会」+「会议记录」→「会会议记录」)。
    这类怪词比正确切法更长,会被「长词优先」的冗余过滤错误地保留。
    suggest_rules.py 用「相邻重复字」信号剔除它们,自己写脚本时要记得这层过滤。
  • priority 别按体积排 → 按「命中文件数越少越具体」排,否则宽泛词吞掉具体词。
  • 分类脚本被中断会写截断 → 恢复后先 wc -l + tail 检查完整性再运行。
  • 主题词当类型词用必出事故 → 「洞察」把同事发的数据分析搬进了行业报告库。
    主题词只能进 need_context,判定类型靠「年份期号 / 机构署名 / 法律声明」累加计分。
  • 把低覆盖率特征当必要条件 → 机构署名在真报告里只有 25% 能检出,
    拿它当门槛会漏掉四分之三。低召回特征的正确用法是"命中即加分"。
  • 规则设计写了多条件,执行时退化成单条件 → 那次事故里 L3 层设计写的是
    「报告特征词 + 年份」,执行脚本里却只剩「关键词=洞察」,而文件名恰好没有年份,
    两个条件的保险丝被抽掉了一根。规则定稿后必跑 rules_lint.py,
    它专门查这类"设计有、执行没了"的口子。
  • 校验器自己也会写错 → 判断"规则里有没有年份证据"时曾写成 (19|20)\d{2},
    要求 20 后面跟两个字面数字,结果 re:20\d{2}(\d 是正则元字符)识别不出来,
    E2 检查长期形同虚设而测试一直是绿的。
    教训:判据识别逻辑要有含反例的对照测试(tests/selftest_rules_lint.py),
    尤其要包括"不该被识别"的写法,比如版本号 re:v\d+。
  • 同一文件在多版清单里被判成两类时,必须做冲突断言,取后出的、证据更强的那版。
    实测:同一份 PPT 在 transfer_all.csv 里是 report/洞察,
    在 transfer_all2.csv 里是 work_maybe/biz(后者才对),
    但最后执行用的是前者 —— 中间产物不收敛,正确判定会被先出的错误判定盖掉。
  • 探针的「无信号」不是免检证 → 图片型 PDF 抽不出文字(实测 267 个无信号 PDF 里
    147 个属于此类,共 1.5 GB),探针看不见,只能靠文件名或人工。

阶段 3 去重

  • exact 组不要套合同/报价保护。字节级完全一致的文件,留一份删副本没有任何信息损失;
    保护规则针对的是"版本差异可能有实质内容"(主体、金额、盖章不同)。
    实测把「报价单模板」当保护词时,一组纯副本被白白拦下。
  • --min-mb 会吃掉"精简版"。保护 2 的经典场景是 方案(1).docx 只有 0.03 MB 却要删 25 MB 带图版,
    但 0.03 MB < 默认阈值 0.1 MB,这个文件根本进不了扫描,组都成不了,保护也就无从谈起。
    想抓这类,把 --min-mb 调到 0.02 再跑一遍。
  • 去重后必须重跑盘点。删完文件数变了,inventory.csv 就是脏的,拿去分类会对不上账。
  • gen_dedup_panel.py 的 onclick 里不要写内联表达式。check_html_js.js 会把
    onclick=&quot;openFile(&#x27;...&#x27;.replace(/&#x27;/g,&#x27;&#x27;))&quot; 里的 replace 当成未定义的函数调用报错。
    正确做法是 onclick 只传 ID(openFile(&#x27;D0001&#x27;)),路径从 GINDEX 里取。
  • 老版 dedup_analyze.py 的 C/D 会撞车(同一文件既判删又判留,首次跑抓出 10 个冲突)。
    新版用一个文件只进一个组规避,老脚本别再用了。

阶段 4 看板

  • --key 每批次必须不同,localStorage 按 key 存选择,重名会串批。
  • 生成器漏写 writerows(mp_rows) → 映射 CSV 只有表头。交付前校验行数 == DATA 条数。
  • 模板内部用三双引号分隔 CSS/JS,外层必须用三单引号包裹,别改。
  • 改 panel_template.py 的交互逻辑,语法校验不够,必须跑行为测试:
    node tests/selftest_panel_behavior.js &lt;生成的面板.html&gt;。
    实例:docstring 声称「填目录自动勾移动 / 组级动作跳过已选行」,实际两者都没实现——
    setDir() 只存目录不勾移动,setGroup() 无条件覆盖全组。2026-09-06 修复后补的行为测试
    含 8 项断言(含「组级动作不覆盖单独拍板行」这类回归点)。
  • 组级动作只作用于未选行(已单独拍板的一律不动);行内填/改目录自动把该行勾成「移动」
    (清空目录不动已有决策);组级填目录同样是「目录应用到全组 + 未选行自动勾移动」。

阶段 5 回传

  • 「delete 无目标目录」是两列行,解析器若要求 ≥3 列会静默跳过(838 → 119 的血案)。
  • dry-run 数量必须和面板对账:决策数 + 未选数 == 总条数。

阶段 6 执行

  • PowerShell 输出是 GBK,subprocess 用 text=True 会 UnicodeDecodeError →
    不带 text=True,手动 decode(&#x27;gbk&#x27;)。
  • .ps1 必须纯 ASCII 无 BOM,否则中文路径乱码。
  • Office 文件被 Word/WPS 锁住会失败,不要强杀进程,报告路径等用户关闭。
  • 同盘移动不释放空间,别在报告里算成「已释放」。
  • 改过的脚本必须端到端跑一遍,语法检查抓不到运行时错误。
    实例:把 a.go 写成 go,ast.parse 完全通过、dry-run 前几行也正常输出,
    直到 --go 那一刻才 NameError。只对解析器做单元测试是不够的,
    必须造一批测试文件把「定位 → 盘点 → 分类 → 面板 → 执行」整条链路跑通。

阶段 7 收尾

  • 对账时路径要先 os.path.normpath——walk 出的路径混着 / 和 \,
    集合比对会全部失配,假报「映射外新增」。
  • 清空回收站前必须解析 $I 元数据,确认里面没有用户此前删的其他东西。

Verification

  • [ ] 已用 locate_wechat.py 定位,并确认目标是 file 类而非 attach 类
  • [ ] 链接(junction/symlink)已识别,未把它当可释放空间上报
  • [ ] 规则来自使用者自己的文件(suggest_rules 归纳),不是照抄的模板
  • [ ] 五段确认表(A-E)已交给使用者逐条确认并收到反馈
  • [ ] 草案里每个组都带 _evidence 证据,没有凭感觉加词
  • [ ] rules_lint.py 体检已跑,E 级清零(E1 主题词定罪 / E2 缺类型证据 / E3 兜底层删)
  • [ ] 分类阶段只读,没有改过任何文件
  • [ ] 规则里没有把主题词(洞察/分析/研究/趋势)单独当类型判据
  • [ ] 多版分类清单已做冲突断言(同一文件被判两类时取哪一版有明确结论)
  • [ ] 映射 CSV 行数 == 面板 DATA 条数
  • [ ] check_html_js.js 四项全绿
  • [ ] 去重(若做了)跑在分类之前,且执行后已重跑 scan_inventory.py
  • [ ] 去重执行后每一组都还剩至少一份(删完再跑一次 dedup_scan.py 复查组数)
  • [ ] --key 与历史批次不重名
  • [ ] dry-run 三数对账通过(决策数 + 未选数 == 总条数)
  • [ ] 删除前备份 + MD5 清单已生成
  • [ ] 移动清单已落盘(含源路径/目标路径),确认可用 rollback.py 回滚
  • [ ] 执行后残留项已列出并说明原因(多为文件被占用)
  • [ ] 归档目录已用 content_probe.py 反向抽查过,疑似内部产出的已复核
  • [ ] 已提醒「清空回收站才真正释放空间」且未擅自清空

开源与分享

这套工具设计成可以直接分享给别人:脚本不含个人信息,使用者只需要填一份 rules.json。

分享前自查:

  1. rules.json 是使用者私有的,不要随技能一起发布(模板用 rules.example.json 即可)
  2. inventory.csv、映射 CSV、决策面板 HTML 都含真实文件路径,属于产物,不入库
  3. 备份目录 _delete_backup_* 是文件副本,更不入库

给别人用时的最短路径:

python scripts/locate_wechat.py
python scripts/scan_inventory.py --root &quot;&lt;目录&gt;&quot; --out-dir ./work
python scripts/suggest_rules.py --inventory ./work/inventory.csv --out ./work/rules.draft.json
#   ↑ 把输出的 A-E 五段交给使用者确认,反馈后定稿成 rules.json
python scripts/classify_generic.py --inventory ./work/inventory.csv --rules ./rules.json --out ./work/batch1.csv
python scripts/gen_panel.py --map ./work/batch1.csv --out ./work/决策面板.html \
    --title &quot;微信文件清理&quot; --subtitle &quot;待决策文件&quot; --key wxclean_batch1 \
    --result-title &quot;【微信文件清理 · 决策结果】&quot; --dirs-root &quot;&lt;归档根&gt;&quot;
node scripts/check_html_js.js ./work/决策面板.html

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。