V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gaozihang
V2EX  ›  程序员

PHP 敏感词过滤扩展

  •  
  •   gaozihang · 2018-06-12 09:33:35 +08:00 · 5339 次点击
    这是一个创建于 2116 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Build Status libdatrie PHP xfilter License

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

    依赖环境

    • PHP 7 +
    • libdatrie (Version >= 0.2.4)

    安装

    因为本项目依赖于 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.

    16 条回复    2018-06-13 10:22:05 +08:00
    tanszhe
        1
    tanszhe  
       2018-06-12 09:45:49 +08:00
    感觉意义不大,作为学习练习还是可以。
    如果加上检查一个词语在不在字符串中就 strpos 就好了。

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

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

    TCP 开销其实蛮大的。
    nullen
        16
    nullen  
       2018-06-13 10:22:05 +08:00
    @lowe :)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3246 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 14:01 · PVG 22:01 · LAX 07:01 · JFK 10:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.