游戏和网站,都需要敏感词(屏蔽词)过滤,你懂的。而且敏感词库不断的增长,从十几年前的3000多词,已经增长到14000以上(各渠道获取的词库略有不同)。

这1万多词的扫描处理,开销的性能就有点客观了。所以今天把过去的算法,以及性能指标,重新整理了一下,给大家提供一些思路。

额外说说

从根上说,有两类办法去解决屏蔽词这个事儿。

1,接入第三方API。现在有很多处理敏感词的接口,各大平台都有,比如鹅厂、猪场、阿里、小度等等。目前最便宜的是小度,1块钱50W次。

好处:A,你不用管词库的更新问题。B,不浪费你的处理器资源

坏处:毕竟是第三方,即便我们认为它稳定性是100%,但是一旦网络出了问题(慢甚至断),那么我们自己的服务就跟着挂了。

2,自己写算法处理。好处坏处跟接入第三方正好相反。本文主要是写如何自己处理。

---- 词库处理 ----

词库是可以优化的。例如以下算法:

1,词库去重

如果是多个词库的,那么要去重复。如果是渠道给了最新的单文本文件,也可以略过去重步骤。

2,词库去包含

去包含的意思是:词库里经常有这样的词:【你好】【你好哇】,实际上过滤掉[你好],就破坏了[你好哇]的词义了。理论上讲,大多数的词,可以通估计去包含化,做精简处理。

注意,有些无法去包含化,因为语义的关系。您可以自行优化去包含化的算法,以达到语义层面的精简化。

算法也比较简单,贴一下python版的。

# 生成敏感词库, list格式
def generate_sensitive_list():
    # 把不同版本的敏感词库合并, 去包含化
    # ------ 利用字典key唯一性, 进行关键字[去重] ------
    words_dic = {}
    # 词库1
    for word in civilise.cluckivilise:
        words_dic[word] = {}
    # 词库2
    for word in sensitive_political.political_arr:
        words_dic[word] = {}
    # 词库3
    for word in sensitive_porn.porn_arr:
        words_dic[word] = {}
    words_list = list(words_dic.keys())
    print("去重:", len(words_list))
    # ------ 关键字[去包含](效果不是很好) ------
    # 比如: "luck"中包含了"luck", 那么,只保留fuck即可]
    for A in words_list:
        tmp_list = list(words_dic.keys())
        for B in tmp_list:
            # 在A比B短的情况下
            if len(A) < len(B):
                # A包含在B中, 那么删除B
                if A in B:
                    del words_dic[B]
    # 去包含化后的词库大小
    words_list = list(words_dic.keys())
    print("去包含化:", len(words_list))
    # ---------- [生成敏感词文件] ----------
    the_f = open("d:/sensitive_list.py", mode='w', encoding="utf8")
    # 写文件头
    the_f.write("#! /usr/bin/env python\n")
    the_f.write("#coding=utf-8\n\n")
    the_f.write("sensitive_list = [\n")
    prefix = "    "    # 四空格
    # 逐一写入敏感词
    for word in words_list:
        line_str = prefix + '"' + word + '",\n'
        the_f.write(line_str)
    the_f.write("]\n")
    # 关闭文件
    the_f.close()

---- 敏感词处理算法 ----

1,遍历,查找替换

最原始、最简单的算法。经过词库优化(去重,去包含)之后,新词库数量为2967个。运行一下查找替换算法,耗时0.0004秒。如图

魔兽世界如何屏蔽LUA报错_ci

 

2,利用正则

实际性能提高不了多少。所以干脆省略。

3,树状查找

把词重新进行数据结构上的分类,产生树状结构。然后,利用首字算法,进行查验。比如:【世】开头的敏感词有9个。那么,只要【世】不在文本里,那这9个敏感词,都不用查了。更不用说再往下的细分了。

以字典的数据结构作为示例:因为是敏感词,原谅我打码:

魔兽世界如何屏蔽LUA报错_数据结构_02

 然后,我们来看一下算法示例(原理就是上面说的,很简单,算法可以您自行写,然后优化):

# 利用tree的算法
def text_legalization_2(raw_str):
    from sensitive.sensitive_tree import sensitive_tree
    x_list = list(sensitive_tree.keys())
    print("树状1级节点:", len(x_list))
    st_tm = time.time()
    j = 0
    new_str = raw_str
    # 首字符列表
    for x_1 in x_list:
        # 首字符在输入的文本里
        if x_1 in raw_str:
            sort_list = list(sensitive_tree[x_1].keys())
            # 优先处理文字更长的
            sort_list.sort()
            sort_list.reverse()
            # 挨个对比
            for word in sort_list:
                new_str = new_str.replace(word, "**")
                # print(word, new_str)
                j += 1
        j += 1
    ed_tm = time.time()
    print("处理结果:", new_str)
    print("扫描次数:", j, "耗时:", ed_tm-st_tm)

最后看一下树状过滤算法的性能:

魔兽世界如何屏蔽LUA报错_数据结构_03

 

最终性能对比是:

原始字库:0.00081秒

原始字库:0.00044秒

原始字库:0.000077秒  

结论

处理字库+利用树状扫描算法,整体性能,可以提高10倍以上。

这样,1核CPU,1秒也可以处理1.2万次的敏感词过滤。性能基本可以接受了。