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 工具实战 的实践合集,语义对抗的纵深还能对照 爬虫与反爬实战 的攻防视角。
延伸阅读
- AI 工具实战(智能审核能力延伸)
- 爬虫与反爬实战(语义识别与绕过对抗)
- PHP 接口实战:设计与防护(异步回调设计)
