週末の空き時間にセンシティブワードフィルタリング機能を開発しましたので、その実装過程を以下に記録します。
センシティブな言葉は、一方では我が国のインターネット監視によって制限されており、他方では、私たち自身も個人攻撃や広告情報などをフィルタリングする必要があるかもしれません。特定の語彙については、Google で検索できます。たくさんあります。 。
センシティブな単語をフィルタリングし、単純なループを使用するのは非常に非効率的です。さらに、語彙が増えると、単純な置換では完全に一致しない単語を解決できません。このとき、最初に辞書ツリー (トライ) を構築する必要があります。単純な辞書ツリーは多くのスペースを必要とします。Double-Array Trie または Ternary Search Tree を使用すると、パフォーマンスを確保しながらスペースを節約できますが、基本的にはそれほど多くはありません。機密性の高い単語については、基本的に数千、さらには数万の単語に対する圧力はありません。そのため、実装では、最初に辞書ツリーを構築してから、それを単語ごとに照合することを選択します。
コードが少ないので、ここに投稿します。
<?phpclass SensitiveWordFilter{ private $dict; private $dictPath; public function __construct($dictPath) { $this->dict = array(); $this->dictPath = $dictPath; $this->initDict(); } private function initDict() { $handle = fopen($this->dictPath, 'r'); if (!$handle) { throw new RuntimeException('open dictionary file error.'); } while (!feof($handle)) { $word = trim(fgets($handle, 128)); if (empty($word)) { continue; } $uWord = $this->unicodeSplit($word); $pdict = &$this->dict; $count = count($uWord); for ($i = 0; $i < $count; $i++) { if (!isset($pdict[$uWord[$i]])) { $pdict[$uWord[$i]] = array(); } $pdict = &$pdict[$uWord[$i]]; } $pdict['end'] = true; } fclose($handle); } public function filter($str, $maxDistance = 5) { if ($maxDistance < 1) { $maxDistance = 1; } $uStr = $this->unicodeSplit($str); $count = count($uStr); for ($i = 0; $i < $count; $i++) { if (isset($this->dict[$uStr[$i]])) { $pdict = &$this->dict[$uStr[$i]]; $matchIndexes = array(); for ($j = $i + 1, $d = 0; $d < $maxDistance && $j < $count; $j++, $d++) { if (isset($pdict[$uStr[$j]])) { $matchIndexes[] = $j; $pdict = &$pdict[$uStr[$j]]; $d = -1; } } if (isset($pdict['end'])) { $uStr[$i] = '*'; foreach ($matchIndexes as $k) { if ($k - $i == 1) { $i = $k; } $uStr[$k] = '*'; } } } } return implode($uStr); } public function unicodeSplit($str) { $str = strtolower($str); $ret = array(); $len = strlen($str); for ($i = 0; $i < $len; $i++) { $c = ord($str[$i]); if ($c & 0x80) { if (($c & 0xf8) == 0xf0 && $len - $i >= 4) { if ((ord($str[$i + 1]) & 0xc0) == 0x80 && (ord($str[$i + 2]) & 0xc0) == 0x80 && (ord($str[$i + 3]) & 0xc0) == 0x80) { $uc = substr($str, $i, 4); $ret[] = $uc; $i += 3; } } else if (($c & 0xf0) == 0xe0 && $len - $i >= 3) { if ((ord($str[$i + 1]) & 0xc0) == 0x80 && (ord($str[$i + 2]) & 0xc0) == 0x80) { $uc = substr($str, $i, 3); $ret[] = $uc; $i += 2; } } else if (($c & 0xe0) == 0xc0 && $len - $i >= 2) { if ((ord($str[$i + 1]) & 0xc0) == 0x80) { $uc = substr($str, $i, 2); $ret[] = $uc; $i += 1; } } } else { $ret[] = $str[$i]; } } return $ret; }}
使い方
<?phprequire 'SensitiveWordFilter.php';/*初始化传入词库文件路径,词库文件每个词一个换行符。如:敏感1敏感2目前只支持UTF-8编码*/$filter = new SensitiveWordFilter(__DIR__ . '/sensitive_words.txt');/*第一个参数传入要过滤的字符串,第二个是匹配的字间距,比如'枪支'是一个敏感词,想过滤'枪||||支'的时候,就需要指定一个两个字的间距,可以根据情况设定,超过指定间距就不会过滤。所有匹配的敏感词会被替换为'*'。*/$filter->filter('这是一个敏感词', 10);
パフォーマンスは詳細にテストされていませんが、主に CPU を使用する一般的なシナリオでは十分です。辞書は生成された辞書を JSON にエンコードして保存できます。 Redis または Memcached を取り出して、次回直接復元します。
PHP が WEB を記述する場合、デーモンとは異なり、構築されたデータ構造はメモリ上に永続的に存在できません。それに比べて、厳密なパフォーマンス要件がある場合は、C、C++、Java などの方が適している可能性があります。他の言語で書いてください。もちろん、PHP で利用できる Swoole もありますが、私はそれについてあまり楽観的ではありません。