4.1 敏感内容:文本与图片审核

场景:一条违规帖差点让小程序被下线

日活三万的时候,有天晚上九点运营在群里炸了:有人在同城版块发了一条涉嫌违规的图文,被用户截图举报到平台。从发出来到我们人工删除,中间隔了四十分钟。平台给了警告,再有一次就要暂停服务。那天晚上我们连夜开会,结论是人工巡检绝不能当第一道防线,必须上机器审核:所有文本和图片在发帖时先过审核,判定通过才允许进信息流。

当晚的复盘记录里还有一组让我们后怕的数字:那条帖子发出后的四十分钟里被三十七个用户看到,其中六个点了赞。如果举报晚来半小时,平台处置大概率升级。内容治理的窗口期就是分钟级的,晚一分钟就多几十个曝光。

选择自建还是用云能力也是一笔账。自建敏感词库维护成本高、词库更新跟不上变形速度,最终我们采用了分层方案:第一层是本地敏感词库做快速兜底,命中直接拒绝;第二层是云的内容审核接口做主力,覆盖涉政、暴恐、色情等大类。选云审核还有一个现实考量:它的模型跟着平台政策迭代,等于我们用很低的成本雇了一支随政策更新的专家团队,这比自建的可能落后一个季度的词库强太多,尤其在小团队没有专职安全人员的情况下。第三层保留人工复核通道,审核结果模棱两可的进人工队列。

分层方案定下来之前我们其实先试过纯人工:招两位实习生三班倒巡检。两周之后两人都提出了离职,理由是看多了违规内容心理压力太大。这件事让我们意识到,内容治理不能靠人肉堆,既不可持续也不人道,机器审核必须顶上去扛九成以上的量。

另外还有一条暗中起作用的因素:审核的及时性直接影响违规内容在平台上的存活时长,而存活时长又直接关系到平台对这类问题的容忍度判断。我们后来把违规内容平均存活时长也做成指标,目标是从两小时压到五分钟以内,这个数字比任何审核通过率都更能说明问题。

方案:三道关卡与人审兜底

发帖流程里审核卡在落库之前,帖子状态先置为待审,审核结果通过回调异步回写,通过则进信息流,不通过则通知用户修改。审核动作全程异步,用户体验上发帖即时返回,审核秒级出结果,平均耗时在两秒以内。上线后的第一周我们跟踪了一组数据,贴在这里给各位做参照,其中拦截率包含本地词库命中和云审核拒绝:

指标 数值 说明
日均送审帖量 约四千二百 日活三万时
文本审核平均耗时 一秒二 含重试
图片审核平均耗时 一秒八 单张
拦截率 百分之二点一 违规加误伤
误伤占比 约五分之一 拦截中
关卡 手段 命中处理 耗时
第一层 本地敏感词库 直接拒绝 毫秒级
第二层 云文本审核 状态隐藏加通知 一秒
第二层 云图片审核 图片拒绝 两秒
第三层 人工复核队列 人工裁决 分钟级
回写 异步状态更新 进信息流 秒级

各内容类型的审核策略略有差异,单独拉了张表,运营和审核同学都能照着对齐预期。注意评论和帖子用了不同严格度,评论量大但短,严格一点对体验影响有限:

内容类型 主要手段 严格度 兜底
帖子标题正文 词库加云文本 高 人审
评论 云文本为主 中 疑似隐藏
昵称头像 云图片加文本 高 直接拒绝
搜索词 词库实时 高 拒绝

搜索词这个地方容易被忽略,我们补上搜索词审核是因为发现有人在搜索框里互相留违规暗语,虽然内容没有落库,但搜索联想会被看到,同样需要拦。

配置与代码

本地敏感词库做第一层,用 DFA 或者简单的掩码扫描,命中率虽然低但速度极快,挡掉了大量明显的违规尝试。词库来源有三部分:运营手工维护、平台通报同步、历史违规内容提取,每天凌晨合并一次写入数据库,服务启动时加载到内存。词库表只有 id、词、来源、生效时间四个字段,越简单越好维护,复杂的标签体系我们试过一次就放弃了,没人填:

const BAD_WORDS = loadFromDB(); // 词库每日同步
function localHit(text) {
  for (const w of BAD_WORDS) if (text.includes(w)) return w;
  return null;
}
async function auditText(text) {
  const hit = localHit(text);
  if (hit) return { pass: false, reason: 'local:' + hit };
  const res = await cloud.audit('text', { content: text });
  return { pass: res.suggestion === 'pass', reason: res.label };
}

图片审核由 COS 的内容审核能力承担,直传完成后触发,结果异步写回帖子状态。这里刻意没有阻塞用户发帖流程,审核未出结果前图片在详情页显示占位,体验上只多了约两秒的过渡:

async function auditImage(key) {
  const res = await cosImageAuditing({
    Bucket, Key: key,
    DetectType: 'porn,ads,illegal'
  });
  const ok = res.Result === 0;
  if (!ok) await cosDelete(key);
  return ok;
}

异步回写的状态机也值得交代,帖子状态只有三个值,任何状态流转都写一条 CLS 日志方便事后追溯。这套状态机上线后再没出现过帖子卡在中间态的用户投诉:

// 审核完成回写
async function onAuditDone(post_id, pass) {
  const next = pass ? 'normal' : 'rejected';
  await postModel.updateStatus(post_id, next);
  await cls.log('audit_flow', { post_id, next });
  if (pass) await warmupFeed(post_id); // 进信息流预热
}

踩坑

第一个坑是词库维护。早期把词库写死在代码里,每次加词都要发版,半夜出问题只能干等,改成数据库表每日同步后才自由。第二个坑是误伤,云审核对某些正常词汇给出疑似判定,帖子被隐藏用户投诉,我们把判定策略从疑似即拦截改成疑似进人工队列,投诉率降了一半而违规漏出只多了百分之三。第三个坑是图片审核的异步时序,图片先传后审,审核结果还没回来帖子就已经进了信息流短暂可见,改为先审后显,帖子状态默认待审直到全部图片出结果。第四个坑是变体绕过,用户在文字里用拼音和表情替代敏感词,单纯词库扫不出来,靠云模型的语义识别补位,同时对频繁修改昵称和头像的账号加审核加权。第五个坑是成本,图片审核按次计费,有人一次发九张图反复重试,每月费用涨到三位数,加了客户端预检:尺寸不符或重复上传直接拦截,调用量降了四成。第六个坑是审核接口超时没有降级策略,有次云审核抖动,发帖接口跟着超时,用户提交了七八次都失败,后来给审核调用加了两秒超时和三次重试,仍失败则先放行进待审队列,不再阻塞发帖动作本身。第七个坑是用户重发同一违规内容,被拒后立刻换个标点再发,把账号风控和内容审核联动起来,同账号半小时内被拒三次直接禁言一小时。第八个坑是审核结果缓存,同一张图被多次复用时反复送审白花钱,把审核结论按图片哈希缓存七天,命中率达到百分之三十,每月省下一笔固定开销。第九个坑是夜间审核人力真空,凌晨人工队列没人看,某次云审核服务波动导致大量内容误进人审队列,用户等到早上九点才看到自己的帖子,之后夜班也安排了一名值班同学专门清队列。第十个坑是审核日志没打全,有一次运营想统计某类内容的违规趋势,发现历史日志里只有结论没有原文,无法回溯分析,之后审核日志统一带原文快照和云接口返回的完整字段,为治理决策提供数据基础。第十一个坑是昵称审核的定时漏洞,昵称改动后老昵称的违规内容已展示在各处历史评论里,补了一个异步任务把改名用户的历史展示内容重扫一遍。

小结

内容审核是小内容的生命线,不是可选项。三道关卡加异步审核的组合,既保住了发布体验又把违规内容的暴露压到分钟级。词库和策略都需要持续迭代,治理没有一劳永逸。给同样在起步阶段的团队一句忠告:审核系统上线第一天就要把误伤申诉通道做好,用户被误拦后的情绪远比违规内容本身更难安抚。我们的申诉入口从第一周就开着,平均四小时人工回完,这条口碑后来反而成了产品的加分项。再就是做事顺序:先把自动审核跑通降住大头,再谈精细化策略,顺序反了一定会乱。审核能力的更多玩法,可以看 AI 工具实战 的实践合集,语义对抗的纵深还能对照 爬虫与反爬实战 的攻防视角。

延伸阅读