PHP 敏感词过滤扩展

2018-06-12 09:33:35 +08:00
 gaozihang

关键词过滤扩展,用于检查一段文本中是否出现敏感词,基于 Double-Array Trie 树实现。

依赖环境

安装

因为本项目依赖于 libdatrie, 所以需要先安装 libdatrie, 再安装本扩展。

$ wget https://github.com/tlwg/libdatrie/releases/download/v0.2.11/libdatrie-0.2.11.tar.xz
$ tar zxvf libdatrie-0.2.11.tar.xz
$ cd libdatrie-0.2.11
$ ./configure && make && make install

PS: libdatrie 依赖于 libiconv, 如果编译的时候报 undefined reference to libiconv, 你需要先安装 libiconv。如果安装完还有错误可以执行这个命令 ./configure LDFLAGS=-L/usr/local/lib LIBS=-liconv

$ git clone https://github.com/cdoco/xfilter.git && cd xfilter
$ phpize && ./configure && make && make install

你可以在 php.ini 中设置字典文件的路径, 如果你不想在 php.ini 中设置你也可以在 setFileName 方法中设置。

[xfilter]
extension=xfilter.so

// 你可以在这里设置加载的字典文件路径
xfilter.filename=/path/to/xfilter/blackword.dic

示例

<?php

use Cdoco\Filter;

// 这个方法里可以传入词典的路径或者在 php.ini 中设置
Filter::setFileName(__DIR__ . '/blackword.dic');

// 保存敏感词到字典文件中
Filter::save(['敏感词', '高子航', 'xfilter']);

// 搜索字符串中的敏感词 会返回敏感词的起始位置和长度
$rs = Filter::search('这是一个敏感词测试语句, 由高子航创造, xfilter, By ZiHang Gao。');

// Array
// (
//     [0] => Array
//         (
//             [0] => 12
//             [1] => 9
//         )
//
//     [1] => Array
//         (
//             [0] => 38
//             [1] => 9
//         )
//
//     [2] => Array
//         (
//             [0] => 55
//             [1] => 7
//         )
//
// )
print_r($rs);

方法

setFileName

该方法可以传入一个敏感词的字典文件路径。

Filter::setFileName(string $filename);

// 示例
Filter::setFileName(__DIR__ . '/blackword.dic');

save

保存一个敏感词数组到字典文件中, 如果在 setFileName 方法中设置了路径, 会优先使用 setFileName 方法中的路径, 如果没有会使用 php.ini 中设置的 xfilter.filename

boolean Filter::save(array $blackword [, boolean $append = false]);

// 示例
Filter::save(['敏感词', '高子航', 'xfilter'], true);

save 方法有两个参数, 第一个参数 $blackword 是一个敏感词的数组, 第二个参数 $append 用来表示是否是追加写入。

<?php

use Cdoco\Filter;

Filter::setFileName(__DIR__ . '/blackword.dic');

Filter::save(['xfilter']);
$rs = Filter::search('xfilter, By ZiHang Gao。');

// Array
// (
//     [0] => Array
//         (
//             [0] => 0
//             [1] => 7
//         )
//
// )
print_r($rs);

// 不设置 $append 参数 save 方法会重新建立一个文件
Filter::save(['cdoco']);
$rs = Filter::search('xfilter, By ZiHang Gao。');

// Array
// (
// )
print_r($rs);

// 如果设置了 $append 为 true, save 方法会在之前字典的基础上追加敏感词
Filter::save(['xfilter'], true);
$rs = Filter::search('cdoco, By ZiHang Gao。');

// Array
// (
//     [0] => Array
//         (
//             [0] => 0
//             [1] => 5
//         )
//
// )
print_r($rs);

search

搜索一个字符串中是否包含敏感词。

array Filter::search(string $text);

// 示例
Filter::search('这是一个敏感词测试语句, 由高子航创造, xfilter, By ZiHang Gao。');

search 方法会返回一个二维数组, 包含敏感词出现的位置和长度, 你可以用 substr 方法截取出敏感词。

Array
(
    [0] => Array
        (
            [0] => 0 //敏感词出现的位置
            [1] => 5 //敏感词的长度
        )

)

<?php

use Cdoco\Filter;

// 截取字符串
$content = '这是一个敏感词测试语句, 由高子航创造, xfilter, By ZiHang Gao。';
$rs = Filter::search($content);

foreach ($rs as $v) {
    echo substr($content, $v[0], $v[1]) . "\n";
}

// 敏感词
// 高子航
// xfilter

delete

删除字典文件中的敏感词。

boolean Filter::delete(string $keyword);

// 示例
Filter::delete('高子航');

感谢

@wulijun 的 trie-filter 扩展已不维护更新, 目前使用起来有点繁琐。这个项目是根据自己的想法, 在 trie-filter 的基础上修改而来, 感谢 @wulijun。

License

PHP License. See the LICENSE file.

5367 次点击
所在节点    程序员
16 条回复
tanszhe
2018-06-12 09:45:49 +08:00
感觉意义不大,作为学习练习还是可以。
如果加上检查一个词语在不在字符串中就 strpos 就好了。

一个词语过滤器 应当具有词语相识度识别的功能。换句话说就是这个词语没有在你的词库中 你也应该能识别出来。在实际场景中用户如果发现一个词语被限制了他会换一个词语 相近的词语。如果只是靠枚举 肯定是不全面的 而且新词语产生的非常快。词库的维护需要耗费很多精力。所有过滤器应当有自我进化的功能。
hubqin
2018-06-12 09:48:25 +08:00
我们一般的做法是建一个敏感词表或 php 文件(return 一个数组的形式),将敏感词读取出来(数组),循环判断文本中是否含有
surfire91
2018-06-12 10:59:08 +08:00
@tanszhe 你说的这个挺屌的,现在有哪家已经实现的吗?
phperstar
2018-06-12 11:12:25 +08:00
@hubqin 你这个效率得多低呀....
yankebupt
2018-06-12 11:43:31 +08:00
不知道 double array trie 哪方面性能好一点...
是大文本量好一点还是大量关键词好一点...
看了一眼数据结构介绍,发现没见过.
tanszhe
2018-06-12 13:52:44 +08:00
@surfire91 没有见过,以前做过类似的,觉得这是最基本的功能。 这个功能如果都没有还不如直接用 @hubqin 的方法了,效率也不低。
R18
2018-06-12 13:56:59 +08:00
直接用的接口,有问题也好甩锅
changwei
2018-06-12 15:04:16 +08:00
提个小建议哈,如果是强调性能的项目,最好带上 benchmark 测试结果的!
Z1076
2018-06-12 17:21:46 +08:00
我是把敏感词直接扔数据库里面, 一个 where like 完事. 决定什么是敏感词都是运营那边的大佬负责添加.
Z1076
2018-06-12 17:23:41 +08:00
@Z1076 噢 搞错了...
nullen
2018-06-12 18:07:03 +08:00
实现的蛮好。
我们之前也是用 PHP 实现的 Trie,但是敏感词越来越多,词库越来越大,效率降低;
而且 PHP 请求是运行完一次就全部销毁,逐渐成为一个瓶颈;
后来我们把敏感词单独用 Go 实现成一个服务,感觉良好。
owenliang
2018-06-12 18:28:30 +08:00
double array trie 吧?
GoPHP
2018-06-12 18:33:26 +08:00
@nullen 难道不能放缓存里面?这个敏感词应该也不会经常变吧,单独写成一个服务也是屌
lowe
2018-06-12 19:05:40 +08:00
@nullen 很巧,我之前有个项目就是从 github 上找的 golang 版本的 Trie 树敏感词过滤,也是做成服务
nullen
2018-06-13 10:20:21 +08:00
@GoPHP 你说的放缓存是指类似 Redis 缓存吗?这种效率还是不高。

敏感词库缓存 -> TCP -> PHP。
敏感词库文件 -> PHP。

TCP 开销其实蛮大的。
nullen
2018-06-13 10:22:05 +08:00
@lowe :)

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/462342

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX