sensitive-word 入门:Java 敏感词过滤的落地方案
UGC 评论区、昵称、投稿内容,只要允许用户输入,就绕不开敏感词过滤。这篇讲 Java 里最省心的方案。
为什么需要敏感词过滤
一个面向公众的网站,评论区就是它的门面。涉政、辱骂、广告引流的内容如果放任不管,轻则体验变差,重则合规出事。所以输入内容在入库前先过一遍过滤器,命中就替换或拦截,是 UGC 系统的标准动作。
自己从零写过滤逻辑,最直觉的办法是维护一个词库 List,然后对每条输入遍历 contains 判断——词库几千条、评论一多,这种朴素做法的性能直接崩掉。sensitive-word 这个库(houbb 出品)就是为解决这个问题来的:基于 DFA(确定有限状态自动机)算法,把词库组织成树状结构,一次遍历输入文本就能完成所有词的匹配,性能与词库大小基本无关。
快速上手
引依赖:
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-word</artifactId>
<version>0.20.0</version>
</dependency>
三个最常用的方法:
import com.github.houbb.sensitive.word.core.SensitiveWordHelper;
String text = "这是一段包含垃圾广告和敏感内容的评论";
// 1. 判断是否包含敏感词
boolean contains = SensitiveWordHelper.contains(text);
// 2. 找出所有命中的敏感词
List<String> words = SensitiveWordHelper.findAll(text);
// 3. 把敏感词替换成 *(默认等长替换)
String safe = SensitiveWordHelper.replace(text);
// => "这是一段包含****广告和****内容的评论"
评论系统的标准接法是第 3 种:替换而不是拒绝。用户打了一行正常回复里夹了个敏感词,直接拒绝体验很差,打成星号既守住了底线又不打断交流。
自定义词库
默认词库开箱即用,但每个产品的敏感词标准不一样, inevitably 要加自己的词。敏感词文件放在 resources 下,一行一个词:
resources/sensitive_words.txt
resources/white_words.txt # 白名单:命中但应放行的词
// 自定义:敏感词 + 白名单 + 忽略大小写等配置
SensitiveWordBs wordBs = SensitiveWordBs.newInstance()
.ignoreCase(true)
.ignoreWidth(true) // 全角半角视为相同
.wordAllow(WordAllows.defaults())
.wordDeny(WordDenys.custom()
.add(WordDens.file("sensitive_words.txt"))
.add(WordDens.file("illegal_words.txt")))
.init();
String safe = wordBs.replace(text);
SensitiveWordBs 是重对象,初始化一次全局复用,不要在请求里反复 new。词库更新后调 init() 重新加载即可,本站就是这么做的:后台可配置词库,改完热刷新,不用重启服务。
实战里真正要处理的三个坑
第一是变体对抗。「敏感词」会被用户写成「敏 感 词」「敏-感-词」「㩁感词」。库里的 ignoreRepeat、ignoreWidth 能处理插入空格和全角半角,但拼音、同音字、拆字这类变体,还是要靠持续运营词库——过滤是场持久战,没有一劳永逸。
第二是误杀。词库里放了「免费」,结果用户讨论「免费开源软件」也被打了星号。这就是白名单存在的意义:把合理场景加进 white_words.txt,命中但放行。
第三是过滤器只是第一道防线。敏感词过滤是确定性规则,挡得住词库里的词,挡不住新梗、谐音、图片。认真做内容安全还要叠加上报审核、用户举报、必要时接第三方内容安全 API,敏感词库负责把其中 80% 的低级问题在入库前拦掉。
小结
sensitive-word 把「DFA 算法 + 词库管理 + 各类归一化」打包成了两三行代码的事。接入成本低、性能好、支持自定义词库和热更新,是 Java 项目做敏感词过滤的默认答案。记住它的定位:入库前的第一道闸,守住确定性的底线,剩下交给运营和审核流程。