JS 敏感詞識別-模式匹配算法 DFA


DFA(Deterministic Finite Automaton,即確定有窮自動機。其原理為:有一個有限狀態集合和一些從一個狀態通向另一個狀態的邊,每條邊上標記有一個符號,其中一個狀態是初態,某些狀態是終態。但不同於不確定的有限自動機,DFA中不會有從同一狀態出發的兩條邊標志有相同的符號。

舉例有限狀態機字典如下

@startuml
(王) -> (八)
(八) -> (蛋)
(八) -> (羔)
(羔) -> (子)
@enduml
{
  '王': {
    '八': {
      '蛋': {
        empty: true
      }
    }
    '羔': {
      '子': {
        empty: true
      }
    }
  }
}

輸入為王八端時,逐字對比有限狀態機的字典

  1. 王字命中
  2. 八字命中
  3. 端沒有命中,則返回八字繼續開始匹配
  4. 八字沒有命中有限狀態機,over

輸入為王八蛋時

  1. 王字命中
  2. 八字命中
  3. 蛋命中
  4. 下一個 empty 為 true 命中,匹配成功。

實現

找出敏感詞代碼

'use strict';

// 特殊符號過濾邏輯
const ignoreChars = " \t\r\n~!@#$%^&*()_+-=【】、{}|;':\",。、《》?αβγδεζηθικλμνξοπρστυφχψωΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩①②③④⑤⑥⑦⑧⑨⑩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇≈≡≠=≤≥<>≮≯∷±+-×÷/∫∮∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙≌∽√§№☆★○●◎◇◆□℃‰€■△▲※→←↑↓〓¤°#&@\︿_ ̄―♂♀┌┍┎┐┑┒┓─┄┈├┝┞┟┠┡┢┣│┆┊┬┭┮┯┰┱┲┳┼┽┾┿╀╁╂╃└┕┖┗┘┙┚┛━┅┉┤┥┦┧┨┩┪┫┃┇┋┴┵┶┷┸┹┺┻╋╊╉╈╇╆╅╄";
const ignoreObj = {};
for (let i = 0, j = ignoreChars.length; i < j; i++) {
  ignoreObj[ignoreChars.charCodeAt(i)] = true;
}

// 構建有限狀態機
// 生成的數據結構形制如下
/**
 * {
 *   '王': {
 *     '八': {
 *       '蛋': {
 *         empty: true
 *       }
 *       '羔': {
 *         '子': {
 *           empty: true
 *         }
 *       }
 *     }
 *   }
 * }
 */
function buildMap(wordList) {
  const result = {};

  for (let i = 0, len = wordList.length; i < len; ++i) {
    let map = result;
    const word = wordList[i];
    for (let j = 0; j < word.length; ++j) {
      const ch = word.charAt(j).toLowerCase();
      if (map[ch]) {
        map = map[ch];
        if (map.empty) {
          break;
        }
      } else {
        if (map.empty) {
          delete map.empty;
        }
        map[ch] = {
          empty: true,
        };
        map = map[ch];
      }
    }
  }
  return result;
}

// 獲取敏感詞列表
function getSensitiveWords() {
  /*
  let words = [];
  if (words.length === 0) {
    const data = fs.readFileSync(path.join(__dirname, './words'), 'utf8');
    words = data.split('\n');
  }
  return words.filter(item => !!item);
  */
  return [
    '王八蛋',
    '王八羔子'
  ]
}

// 獲取敏感詞庫對象
const sensitiveWords = getSensitiveWords();
const map = buildMap(sensitiveWords);

// 具體檢測代碼。
function check(map, content) {
  const result = [];
  let stack = [];
  let point = map;
  for (let i = 0, len = content.length; i < len; ++i) {
    const code = content.charCodeAt(i);
    if (ignoreObj[code]) {
      continue;
    }
    const ch = content.charAt(i);
    const item = point[ch.toLowerCase()];
    if (!item) {
      i = i - stack.length;
      stack = [];
      point = map;
    } else if (item.empty) {
      stack.push(ch);
      result.push(stack.join(''));
      stack = [];
      point = map;
    } else {
      stack.push(ch);
      point = item;
    }
  }
  return result;
}

function checkSensitive(content) {
  const words = check(map, content);
  return words;
}

module.exports = checkSensitive;

測試

checkSensitive('老板黃鶴*王&八&(&蛋,吃喝嫖賭,欠下了3.5個億,帶着他的小姨子跑了')

// ['王八蛋']

缺陷

若用火星文、異體字、同音字來替代,這類算法沒什么好的辦法能識別

checkSensitive('王八羔仔')

// []

若敏感詞短,則誤傷很大,若前文設置的是 王八 作為敏感詞。

checkSensitive('虎軀一震,散發一陣王八之氣');

// ['王八']

自然還有大名鼎鼎的 江陰毛紡織廠 怕也會很容易被誤傷。所以,有更智能的辦法是先做分詞,參照分詞庫, 比如說 Node 庫的 nodejieba,Python 的 jieba

可以將 江陰毛紡織廠,拆解為 江陰毛紡織廠 的詞后,在進行 DFA 模式匹配

但截止日前筆者還不敢直接在線上庫當中直接使用分詞庫,市面上大多數的敏感詞過濾也不會上分詞庫的,或許是擔心性能問題,也存在寧殺錯無放過的心態吧。如果有人有更好的辦法,請不吝賜教啊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM