现场是这样的。清洗脚本用 re.sub 去页脚,模式里带了 .* 又没有关掉 DOTALL,结果把两条样本连同中间的换行一起吃掉,8 万条变成 7.1 万条。

中间走过弯路,把「替换没有限制次数,反复匹配到文末」当成主因,改完现象没变。

排查方向其实有限,先把可能性排一排。正则默认不跨行,但脚本显式开了 DOTALL;模式里的贪婪匹配没有加边界;清洗后没有做条数与长度分布校验。

先统计清洗前后的条数与长度分布

python -c "print(sum(1 for _ in open('before.txt')), sum(1 for _ in open('after.txt')))"

然后把贪婪匹配换成非贪婪并加行锚点

python -c "import re;print(re.sub(r'^页脚.*$','', '页脚 x', flags=re.M))"

随后关掉 DOTALL 后重跑并比对条数

python clean.py --in before.txt --out after_v2.txt --no-dotall

等前面都确认过,再按长度分桶确认没有超长合并样本

python len_stat.py --file after_v2.txt --buckets 100,500,2000

别只看报错消失,验证要多做一步。清洗前后条数只差 3%,长度分布没有出现异常长尾,抽查的 30 条样本边界完整。

这里还有个坑。跨行匹配最容易在清洗里制造隐形数据丢失,条数和长度分布是最便宜的哨兵,每次跑完都该看一眼。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。

作者 9823189