长篇小说场景定位
从单文件全本 txt 中,把"某一类场景"定位到章节级。核心不是写正则,是切分先自检、判据后自检。
铁律
- 不许先断言后验证。 判据写完,必须在"已知答案的样本"上跑一遍再开口报数。
- 只报分数不算自检。要抽查被判中的段落原文、也要抽查被判掉的原文。
- 不要顺手扩大范围:只做用户要的那一类场景。
步骤
1. 编码与结构探查
- 先试
utf-8 / gbk / utf-16三种解码(网文 txt 多为 GBK)。 - 用
re扫行首匹配第N章|第N幕|第N卷(N 含中文数字与阿拉伯数字),看标题格式与卷划分。 - 中文章节编号要写
cn2num(十/百/千进位,注意"两"=2)。
2. 切分 + 切分自检(必做)
标题行判据(实测有效组合):
行首匹配 ^第N(章|幕|卷)
且 前一行为空行 ← 正文段落多以全角空格开头,对话行不是,所以这条是主力
且 长度 ≤ 30
且 不以句号结尾 ← 排除"第十八幕,S'taz为精灵语。"这类注释
注意:不要把"不以标点结尾"扩大到 ?!,会误删真标题(如"第四十三幕 不是高地骑士吗?")。
也不要要求"后一行必须是空行",真标题后常直接跟对话行。
切完做卷内序号连续性自检:遍历标题,期望序号从 1 起逐章 +1,遇到卷标题重置。
报告所有断裂处。断裂为 0 → 切分可信;断裂多 → 说明判据漏行或混了噪声,回去调。
另可辅助验证:统计相邻标题间的字数间隔,正常应 > 800 字;间隔极小的成对标题多半是噪声
(唯一合法的小间隔是"卷标题 → 该卷第一幕")。
3. 场景打分(双层词表 + 滑动窗口)
- CORE 词:只有该类场景真的发生时才出现的词。战斗场景 = 挥剑/劈开/格挡/闪避/贯穿/喷血/冲锋…
- AUX 词:场景语境词但会滥用的(伤口/交战/魔力/敌人…)。
- 滑动窗口 300 字 / 步长 150。
- 窗口判定:
命中的不同 CORE 词 ≥ 2,或≥1 且 (CORE+AUX) ≥ 5。
用"不同词的个数"而不是总次数——否则历史回顾里反复出现的"杀死/战死"会刷分。 - 允许中断 1 个窗口后接续(场景里常夹一两句对话/心理);连续 ≥2 窗口算一段。
- 输出指标:战斗(场景)段落字数 / 全章字数 = 占比;最长段落字数;核心词密度(每千字)。
4. 判据自检(必做)
- 随机抽 12 章,打印每章 首/中/尾 各 ~300 字,人工读一遍给出 有/无,再和判据对比,报出 正确数/12。
- 同时抽查高分榜 TOP 若干与中低分档若干,打印被判中的段落原文确认真是该类场景。
- 常见误判及修法(战斗场景实测):
| 误判 | 修法 |
|---|---|
| 环境/氛围描写(白骨、灰烬、废墟、尖啸) | 这类词从词表剔除 |
| 历史回顾反复"杀死/战死" | 改用不同词个数;'杀死'降为 AUX |
| 宏大场面(旗帜燃烧、金色光柱) | 光柱/火焰类降为 AUX |
| 军事部署叙述、战后清点 | 无法完全消除,归到最低档并注明"疑似" | - 试过无效的特征:平均句长(战斗窗口 40 字 vs 叙述窗口 32 字,方向与直觉相反)。
5. 输出
- 分档:S(≥50%) / A(30–50%) / B(15–30%) / C(<15%) / D(无)。
- 除单章清单外,按系列聚合很有用:标题去掉尾部
(一)(上)后同名合并,算系列平均占比
(能一眼看出"安魂曲""决战""破晓"这类战役系列整段都是战斗)。 - 交付:markdown 报告(含判据与自检说明)+ CSV 全量(卷/章号/标题/分档/占比/字数/起始行)。
参考实现
本项目脚本:split_chapters.py(切分)、battle_dict.py(词表)、
battle_score.py(打分)、report.py(出报告)、sample.py / peek_rank.py(抽查自检)。