核心做法是:不要只写“正常流程”,而是把例外拆成可判定的条件、可观察的信号和兜底动作。人工经验里最容易被省略的,恰恰是“什么时候不按常规处理”。如果例外描述得含糊,脚本执行结果往往与预期相反,而且很难判断是需求写错了,还是数据本身变了。
假设你让脚本批量处理页面标题:规则是“标题为空时,用正文首句补全”。人工操作时效果不错,但脚本跑完后,一部分页面标题反而变得很长、重复,甚至和正文首段完全一样。表面看是脚本没有遵守需求,实际上更可能是需求只写了常规路径,没有写例外。
人工操作时会自动判断:正文首句是不是导航文字、是不是免责声明、是不是已经出现在其他页面。脚本不会自动继承这些判断,除非你把它们写成条件。
人工经验里的“看起来不合适”通常包含多个隐含条件。例如“正文首句太短就不要用”“首句包含日期就不要用”“首句和栏目名重复就不要用”。如果需求只写“标题为空时补全”,脚本就会对所有空标题一视同仁。
能区分这种解释的证据是:把脚本实际改动的页面列出来,逐条对照人工判断。如果被改坏的页面都集中在同一类特征上,比如首句少于十个字、首句含“免责声明”、首句与栏目名相同,那说明缺的是例外条件,而不是脚本逻辑本身。
另一种可能是,人工经验形成时样本有限,后来页面结构变了,出现了新的正文开头形式,比如活动说明、作者署名、多语言切换提示。这些内容在旧经验里没有出现过,所以既不在常规规则里,也不在例外清单里。
能区分这种解释的证据是:按时间或页面模板分组,看被改坏的页面是否集中在某次改版之后、某个栏目之下或某种模板之中。如果同一类例外在旧页面上很少出现,在新页面上集中出现,那就不是规则写漏了一条,而是例外类型需要重新归纳。
第一层是触发条件:什么情况下进入例外分支。不要写“内容不合适时”,而要写可判定的特征,例如“正文首句字符数少于某个阈值”“首句包含指定词表中的词”“首句与页面所属栏目名称相同”。阈值和词表要写清楚,不能留给脚本自己猜。
第二层是观察信号:脚本遇到例外时,输出什么证据供人复核。例如跳过原因、命中的条件、原始首句、页面标识。没有这些信号,你只能看到“脚本没改”,却不知道是例外生效,还是脚本根本没跑到这一页。
第三层是兜底动作:例外发生后是跳过、保留原样、进入人工队列,还是用另一条规则替代。不同兜底动作会影响下一步:如果选择跳过,你需要检查跳过量是否异常;如果选择进入人工队列,你需要确认队列有没有被及时处理;如果选择替代规则,你需要定义替代规则的优先级。
假设某站点有 200 个标题为空的页面,脚本规则是“用正文首句补全”。人工经验补充的例外是:首句少于 15 个字、首句含“版权所有”、首句与栏目名相同,这三类跳过并记录原因。
脚本运行后,如果跳过量是 40 条,其中 35 条集中在“首句与栏目名相同”,那就说明这条例外可能写得太宽,或者栏目名本身需要先清理。下一步不是直接删除例外,而是先抽样核对:这些页面的人工判断是否也认为不该补全。如果人工判断认为可以补全,那例外条件需要收窄;如果人工判断也认为不该补全,那问题在栏目名重复,而不是例外本身。
这个例子里的数字只用于说明比较方法,不代表任何真实站点的统计结果。
当你发现结果与直觉相反时,先不要改脚本,而是做一次对照:把脚本实际处理的页面、命中的规则、跳过的原因、人工判断结果放在同一张核对表里。然后按例外类型分组,看哪一组的分歧最大。
完成调整后,下一次比较要尽量保持其他条件一致:同一批页面、同一时间段、同一采集方式。如果必须跨时间段比较,要意识到搜索需求、季节和页面改版都可能影响结果,不能把前后差异全部归因于例外规则。
更稳妥的写法是:每条正常规则后面紧跟它的例外和兜底。例如正常规则是“标题为空时补全”,例外是“首句少于 15 个字时跳过并记录”,兜底是“跳过页面进入人工复核队列”。这样脚本执行后,你能从记录里直接看到例外是否被触发,而不是靠猜。
如果例外条件超过三条,建议先按优先级排序,并写明哪条优先判断。否则同一个页面可能同时命中多条例外,不同脚本实现会得到不同结果。优先级本身就是需求的一部分,不能留给执行环节临时决定。
最后,把例外写成脚本需求时,判断标准不是“写得多详细”,而是“出现反常结果时,能不能用记录区分是条件写错了、数据变了,还是兜底动作选错了”。能区分,下一步才有明确动作;不能区分,改脚本也只是换一种方式重复同一个问题。