那次给一批内容追加评论时,功能本身已经能跑通:系统可以读取最近发布的内容,根据项目相关信息生成评论,也能把评论发到指定位置。看起来,剩下的事情只是把任务数量跑完,或者把发送速度再提快一点。
但在复查某条内容时,我发现了一个很具体的问题。那条内容下面原本已经有一条评论,后来自动化流程又换了一种问法,再次提到了同一个目标账号。两句话并不完全相同,所以原来的判断把它们当成了两条不同评论。站在程序的角度,它没有违反“不能重复发送同一句”的规则;站在读者的角度,这却明显是在同一条内容下重复做同一件事。
评论自动化真正需要避免的,不是重复的句子,而是重复完成同一个动作。
先把场景从“能不能发”改成“该不该发”
一开始,需求更像是增加能力:既要能写评论,也要能回复评论。评论不是固定模板,内容要结合当前项目的介绍和原始链接动态生成。于是实现重点自然落在几个动作上:找到目标内容,组织一段合适的话,填入评论框,确认发送结果。
这些动作都重要,但它们解决的是“怎么发”。真正发生重复以后,问题变成了“这条评论是否还有必要存在”。如果继续只盯着输入框和发送按钮,自动化会越来越熟练,却可能越来越快地把相同意图发送多次。
当时最容易忽略的一点,是评论任务并不只由一段文本组成。它至少包含内容所在的视频、视频作者、要提及的目标账号,以及这次评论想完成的动作。比如,有的评论是介绍一个项目,有的是询问目标账号,有的是回复对方已有的意见。文本只是这个动作最后呈现出来的形式。
如果不把这些上下文保留下来,系统每次看到新句子都会觉得“这是一条新任务”。结果就是同一条内容下出现多条意思接近的评论,既让账号显得像在刷屏,也让后续人工复查变得困难。更麻烦的是,重复一旦发送,就不能只靠下一次生成时改几个字来补救。
完整句子去重为什么会失效
原来的做法并不荒谬。把历史评论和待发送文本做逐字比较,规则简单,容易解释,也不需要先理解评论的含义。如果两条文本完全一样,就跳过;不一样,就继续发送。在测试用例很少、文本又高度固定时,这个方法确实能挡住一部分重复。
问题在于,动态生成评论的前提正好破坏了这个假设。评论内容需要根据项目内容和原始链接变化,系统也可能为了语气自然而调整句式。同一个目标账号,前一次可能是“请介绍一下这个项目”,后一次可能换成“能不能详细说说这个项目的来源”。字符不同,意图却没有变。
这里最容易误判的是,把语言变化当成任务变化。人读两句话时,通常会先看它们在什么视频下、指向谁、想做什么,再判断是不是重复;机器如果只看字符串,就会把“换一种说法”误认成“换了一件事”。
精确匹配只能证明两段文字不同,不能证明两次操作值得同时发生。
我没有继续堆叠更多文本相似度规则,是因为这会把问题带到另一个危险方向:需要不断猜测多少字相同才算重复,或者依赖模型判断两句话是否“足够像”。这种判断在边界上并不稳定,而且仍然可能漏掉语义相同、措辞差异很大的评论。更稳妥的做法,是先定义业务上真正不能重复的单位。
去重单位应该回到视频下的动作
复盘后,我把判断中心从“评论文本”移到了“同一视频下本账号是否已经完成过该类提及”。这句话里有几个必要条件:先锁定视频,再确认评论属于本账号,然后识别目标账号和动作意图。只有这些条件落在同一个范围里,才有理由把待发送任务判定为重复。
视频是第一层边界。相同的话发在不同视频下,可能分别有意义,不能因为文本相同就全部拦截。作者账号是第二层边界,它帮助系统确认内容归属,避免把相似标题或相同项目误当成同一条内容。目标账号是第三层边界,同一视频下提及不同账号,动作对象不同,也不能简单合并。最后是动作意图:一次是首次提及,一次是针对对方回复的跟进,它们可能都需要保留。
可以把一次评论任务理解成一个小的判断键:视频 ID、作者账号、目标账号、动作意图。这个键不一定要直接展示给使用者,但系统在发送前必须能据此查询历史。文本则承担另一种职责:它用来生成内容、检查是否合适,而不是单独决定是否重复。
去重规则的核心不是把所有相似内容都拦住,而是准确识别同一业务动作的再次执行。
这个调整也保留了必要的弹性。如果目标账号不同,即使评论句式近似,也可以继续处理;如果动作意图从首次提及变成了明确回复,也要结合上下文判断,而不是因为同一视频就一律禁止。规则越贴近任务边界,越不需要用一大套模糊的“像不像”来弥补定义不足。
发送前先读历史,命中就留下原因
确定去重单位之后,流程顺序也要跟着改变。不能先生成、先填写、甚至先发送,再回头检查历史。发送前应该先读取目标视频下与本账号相关的评论,按视频、作者、目标账号和动作意图逐项核对。历史中已经存在同类提及,就跳过这次发送,并记录“已存在同类提及”这一原因;没有命中,才进入动态生成和发送。
这一步看起来只是把查询提前了,但它改变了系统的安全姿态。原来的流程把发送当作默认动作,把去重当作一个附加条件;新的流程把“是否允许发送”放在前面,把生成文本放在判断之后。这样即使生成器换了表达方式,也不会绕过业务层的重复保护。
我判断必须在发送前做历史核对,是因为发送后的清理成本更高。已经发出的重复评论可能被看到、被回复,甚至影响账号在内容区的观感。事后删除虽然能修复一部分结果,却不能撤回别人已经看到的内容,也不能保证每次都准确找到需要删除的那一条。
这里还需要处理一个边界:历史列表不一定只有本次任务产生的内容,也可能包含人工留下的评论。因此“系统没发过”不能直接等于“可以发”。只要历史上已经满足同一个视频、目标账号和动作意图,就应该把它视为已经完成,除非任务明确要求继续跟进。
删除重复评论也要做双向确认
发现问题后,不能只把代码改好就结束。那次复盘里,历史任务中有 10 条内容,其中 2 条已经在过去提及过目标账号。它们属于发送前就应该被跳过的任务,却已经留下了重复评论。要修复现状,就需要删除确认过的重复评论,同时保留原先那条有效评论。
删除动作的风险比发送更高,因为发送错了还可能通过补充或删除来修正,删除错了却可能直接丢掉本来应该保留的内容。于是我把清理拆成了几个确认点:先核对目标视频,确认评论确实属于本账号;再区分新产生的重复评论和原有评论;执行删除后,重新读取评论列表,确认目标评论已经消失,而原评论仍然存在。
任何会改变历史数据的清理,都不能以“删除请求成功”作为唯一验收标准。
重新读取列表很关键。页面提示成功,最多说明操作被接受,不等于列表状态已经符合预期。网络延迟、页面刷新或定位偏差,都可能让结果与提示不一致。只有回读后的状态同时满足“重复目标消失”和“保留评论仍在”,这次修复才算完成。
这件事让我重新意识到,去重不是发送流程里的一个小补丁,而是数据生命周期的一部分。发送前要判断,发送后要记录,发现历史问题时要谨慎清理,清理完成还要回读。少掉任何一环,系统都可能在下一次任务里重新制造同样的问题。
下次我会怎么做
下次再做评论自动化,我会先画出任务的业务边界,再设计文本生成。第一步不是写句子,而是确认这条内容的稳定身份:视频、作者、目标账号和动作意图。第二步是读取历史,检查本账号是否已经完成同类提及。第三步才生成符合当前项目内容的评论,并在发送前再做一次最终核对。
如果命中历史,我会跳过发送并留下可追踪的跳过原因,而不是把它悄悄当成成功。这样以后回看任务时,能分清哪些是正常完成,哪些是因为历史已存在而没有发送。对需要回复的场景,我也会把“首次提及”和“针对已有回复的跟进”分开,避免过度去重误伤真正的新动作。
如果发现历史上已经有重复评论,我会先确认目标行和保留行,再执行删除,最后重新读取列表。删除不会因为数量少就省略核对,正因为只涉及 2 条,误删一条反而更容易被忽略。对批量任务,我还会把每条内容的处理结果记录下来,让“跳过、发送、删除、保留”都有明确状态。
表面上我是在优化评论机器人,其实我是在给自动化设定一条边界:它可以替我执行动作,但不能替我模糊动作的含义。一个流程成熟的标志,不是它能把 10 条任务全部发出去,而是它知道其中哪些任务本来就不该再次发送。
这个规则以后也能复用到点赞、私信、收藏和其他批量操作里。先找稳定的业务对象,再识别动作意图,发送前读历史,改变数据后回读结果。语言可以变化,页面可以变化,生成方式也可以变化,但判断链应该被留下来。这样自动化才不是更快地重复过去,而是每次都知道自己为什么要做、为什么要停。