介绍
很多时候我们需要对接受的文本进行过滤,剔除一下不当用词,比如一些反动的、侮辱性的、淫秽的用语
一般会有一个敏感词词库,基于这个词库对输入的文本进行过滤,分享一种简易的实现
示例中为了和谐,将不会出现上述违反社会主义核心价值观的词汇,使用“小明”、“小红”来举例
实际生产中可以用需要过滤的敏感词列表替换
现在假设“小明”、“小红”已经属于敏感词了,那么理想的效果:
输入: 小明上课吃零食,老师让小红出去。
输出: **上课吃零食,老师让**出去。
很多时候用户会简单的插入一些' '、'_'、'%'类似的简单字符来躲避过滤,希望算法同样能过滤敏感词:
输入: 小 明上课吃零食,老师让'小'红'出去
输出: * *上课吃零食,老师让'*'*'出去
准备
首先,需要准备一个敏感词列表
比如:
- 小明
- 小红
然后,需要一个高效的匹配算法
因为在实际生产中,敏感词数量会比较多,传入的文本也会比较长
单纯的遍历敏感词列表对字符串使用String.replace(key, "**")效果会比较差
这里使用了一种Aho Corasick自动机结合DoubleArrayTrie极速多模式匹配的算法来进行敏感词的匹配
实现
定义敏感词列表
private static final String[] SENSITIVE_KEYS = new String[]{
"小明",
"小红"
};
使用maven将算法库引用进来
<dependencies>
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>aho-corasick-double-array-trie</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
使用匹配器来匹配敏感词位置并替换为'*'
public static String shadowSensitive(String text) {
StringBuffer sb = new StringBuffer(text);
// filter sensitive words
List<AhoCorasickDoubleArrayTrie.Hit<String>> hits = acdat.parseText(sb);
// shadow sensitive words
for (AhoCorasickDoubleArrayTrie.Hit<String> hit : hits) {
for (int i = hit.begin; i < hit.end; i++ ){
sb.deleteCharAt(i);
sb.insert(i, "*");
}
}
return sb.toString();
}
接下来测试一下,需要先初始化一下匹配器
public static void main(String[] args) {
TreeMap<String, String> keys = new TreeMap<String, String>();
for (String key : SENSITIVE_KEYS) {
keys.put(key, key);
}
acdat = new AhoCorasickDoubleArrayTrie<String>();
acdat.build(keys);
String text1 = "小明上课吃零食,老师让小红出去";
String text2 = "小 明上课吃零食,老师让'小'红'出去";
System.out.println("text1:");
System.out.println(text1);
System.out.println(shadowSensitive(text1));
System.out.println("text2:");
System.out.println(text2);
System.out.println(shadowSensitive(text2));
}
执行程序后,控制台输出
text1:
小明上课吃零食,老师让小红出去
**上课吃零食,老师让**出去
text2:
小 明上课吃零食,老师让'小'红'出去
小 明上课吃零食,老师让'小'红'出去
可以看到text1中“小明”和“小红”已经被替换成了“**”
但是,在词语中简单的加入一些字符就可以绕开过滤器,这还需要优化一下
优化
匹配器只能匹配到“小明”,而无法匹配到“小 明”或者“小_明”
优化的思路如下:
- 输入的文本
小 明上课吃零食,老师让'小'红'出去
- 将一些常见的字符取出来,只留下文字内容
小明上课吃零食,老师让小红出去
- 进行敏感词的匹配,将敏感词改为*
**上课吃零食,老师让**出去
- 将取出的字符再重新插回去
* *上课吃零食,老师让'*'*'出去
参照这个思路,改写了上面的shadowSensitive方法
改线前需要定义一些常见的字符
private static final char[] SPECIAL_CHARS = new char[]{
' ', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=','+',
'\\', '|', '[', '{', ']', '}', ';', ':', '\'', '"', ',', '<', '.', '>', '/','?',
//中文字符
' ', '!', '¥', '…', '(', ')', '、', '「', '」', '【', '】', ';', ':', '“', '”', ',', '。', '《', '》', '?'};
为了方便展示,这里仅仅列举了常见的一部分字符,有需要的话,可以随时添加字符进去
优化的shadowSensitive方法:
public static String shadowSensitive(String text) {
// detect special chars
List<int[]> descriptors = new ArrayList<int[]>();
for (int i = 0; i < text.length(); i++) {
for (int j = 0; j < SPECIAL_CHARS.length; j++) {
if (text.charAt(i) == SPECIAL_CHARS[j]) {
int[] descriptor = new int[2];
descriptor[0] = i;
descriptor[1] = j;
descriptors.add(descriptor);
}
}
}
// remove special chars
StringBuffer sb = new StringBuffer(text);
for (int i = descriptors.size() - 1; i >= 0; i--) {
sb.deleteCharAt(descriptors.get(i)[0]);
}
// filter sensitive words
List<AhoCorasickDoubleArrayTrie.Hit<String>> hits = acdat.parseText(sb);
// shadow sensitive words
for (AhoCorasickDoubleArrayTrie.Hit<String> hit : hits) {
for (int i = hit.begin; i < hit.end; i++ ){
sb.deleteCharAt(i);
sb.insert(i, "*");
}
}
// refill special chars
for (int[] descriptor : descriptors) {
sb.insert(descriptor[0], SPECIAL_CHARS[descriptor[1]]);
}
return sb.toString();
}
接下来测试一下
public static void main(String[] args) {
TreeMap<String, String> keys = new TreeMap<String, String>();
for (String key : SENSITIVE_KEYS) {
keys.put(key, key);
}
acdat = new AhoCorasickDoubleArrayTrie<String>();
acdat.build(keys);
String text1 = "小明上课吃零食,老师让小红出去";
String text2 = "小 明上课吃零食,老师让'小'红'出去";
System.out.println("text1:");
System.out.println(text1);
System.out.println(shadowSensitive(text1));
System.out.println("text2:");
System.out.println(text2);
System.out.println(shadowSensitive(text2));
}
执行程序后,控制台输出:
text1:
小明上课吃零食,老师让小红出去
**上课吃零食,老师让**出去
text2:
小 明上课吃零食,老师让'小'红'出去
* *上课吃零食,老师让'*'*'出去
可以看到"小 明"已经修改为" "了
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。