敏感詞過濾的簡易實現

介紹

不少時候咱們須要對接受的文本進行過濾,剔除一下不當用詞,好比一些反動的、侮辱性的、淫穢的用語 通常會有一個敏感詞詞庫,基於這個詞庫對輸入的文本進行過濾,分享一種簡易的實現java

示例中爲了和諧,將不會出現上述違反社會主義核心價值觀的詞彙,使用「小明」、「小紅」來舉例    
實際生產中能夠用須要過濾的敏感詞列表替換
複製代碼

如今假設「小明」、「小紅」已經屬於敏感詞了,那麼理想的效果:git

輸入:  小明上課吃零食,老師讓小紅出去。   
輸出:  **上課吃零食,老師讓**出去。
複製代碼

不少時候用戶會簡單的插入一些' '、'_'、'%'相似的簡單字符來躲避過濾,但願算法一樣能過濾敏感詞:github

輸入:  小 明上課吃零食,老師讓'小'紅'出去
輸出:  * *上課吃零食,老師讓'*'*'出去
複製代碼

#準備算法

首先,須要準備一個敏感詞列表

好比:bash

  1. 小明
  2. 小紅

而後,須要一個高效的匹配算法

由於在實際生產中,敏感詞數量會比較多,傳入的文本也會比較長
單純的遍歷敏感詞列表對字符串使用String.replace(key, "**")效果會比較差
這裏使用了一種Aho Corasick自動機結合DoubleArrayTrie極速多模式匹配的算法來進行敏感詞的匹配maven

實現

定義敏感詞列表測試

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>
複製代碼

使用匹配器來匹配敏感詞位置並替換爲'*'ui

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();
}
複製代碼

接下來測試一下,須要先初始化一下匹配器spa

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[]{
    ' ', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=','+',
    '\\', '|', '[', '{', ']', '}', ';', ':', '\'', '"', ',', '<', '.', '>', '/','?',
    
    //中文字符
    '&emsp;', '!', '¥', '…', '(', ')', '、', '「', '」', '【', '】', ';', ':', '「', '」', ',', '。', '《', '》', '?'};
複製代碼

爲了方便展現,這裏僅僅列舉了常見的一部分字符,有須要的話,能夠隨時添加字符進去
優化的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:
小 明上課吃零食,老師讓'小''出去 * *上課吃零食,老師讓'*'*'出去
複製代碼

能夠看到"小 明"已經修改成"* *"了

相關文章
相關標籤/搜索