赞
踩
敏感词检查是一个很常见的需求,在做用户输入功能时,如果不对敏感词进行检查,不仅影响平台内容的质量,可能还因此违反法律法规直接被封。敏感词是动态的,不同时机敏感不同,用户还会千方百计的使用谐音、拼音等避开检查,所以需要实时的更新。及时修改规则。
最好的办法当然是人工审核,效果最好,但是人工审核成本太高了,要是内容少还好,一多根本审核不过来,影响用户发布内容,用户跑完了。
所以一般都是通过机器检查并且不断的根据实际情况完善检测机制。
机器检测也可以通过机器学习的方法不过不在本文的讨论范围内。
先建立一个敏感词库,对用户输入的文本逐个比较检查是否包含敏感词库里的敏感词。
敏感词库建立比较简单,就建立一个表保存敏感词,提供CURD操作即可。敏感词库可以网上找,而且不同的业务需要的敏感词是不一样的,一般网上找一版之后再进行人工筛查后不断的完善即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XhrJzHoF-1668919959859)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3fdd756e521943ccac03312e77ed0d41~tplv-k3u1fbpfcp-watermark.image?)]
这样敏感词检测问题就转化成字符串匹配的问题。假设敏感词库里有一千条敏感词,那么每次都需要进行一千次匹配,比如下面代码。
public static void main(String[] args) { List<String> sensitiveWordList = new ArrayList<>(); sensitiveWordList.add("今天"); sensitiveWordList.add("明天"); sensitiveWordList.add("后天"); String word = "今天去上班,明天也要去上班,后天不用去上班"; for (String s : sensitiveWordList) { if(sensitiveWord(word, s)){ System.out.println("存在敏感词:"+ s); } } } public static boolean sensitiveWord(String word, String sensitiveWord){ if(sensitiveWord.length() == 0){ return true; } if(word.length() < sensitiveWord.length() ){ return false; } for (int i = 0; i < word.length(); i++) { //首字母相等继续匹配下一个字母 if(word.charAt(i) == sensitiveWord.charAt(0)){ if(sensitiveWord(word.substring(i+1), sensitiveWord.substring(1))){ return true; } //否则匹配下一个字母开始 }else if(sensitiveWord(word.substring(i+1), sensitiveWord)){ return true; } } return false; }
输出结果为
存在敏感词:今天
存在敏感词:明天
存在敏感词:后天
整体思路也是比较直观的,和人工去匹配的思维是一样的,代码中先遍历输入句子是否存在今
这个字,如果不存在就比较下一个字符,如果存在就进入子循环检查下一个字符是否是天
,如果是就存在,如果不是再回到原来的地方继续寻找今
这个字符,不断重复此步骤直到末尾。
人工检查也是这样子找的,这样的代码很多循环会显得很笨重。一点都不优雅而且随着敏感词的增多,会越来越慢。如果上万个敏感词,那么每次输入都要进行上万次循环(晕)
有什么办法进行优化呢?
仔细比较会发现有一些重复的比较。比如,如果存在敏感词颜色电影
、颜色小视频
、颜色图片
,去检查句子网站中如果存在颜色电影将会被依法查封
那么颜色
这两个字会被检查三次,重复计算了三次。
存在重复的工作那么就存在优化的空间。如何做让它只进行一次检查后面共用结果呢?
既然是因为三个敏感词都存在颜色
才会重复计算,那么是否可以将它们三个归类,先检查颜色
再检查颜色
后面是否是电影
、小视频
、图片
呢,这样检查颜色后,如果后面存在电影
、小视频
、图片
说明存在敏感词。
进一步想,颜色电影
可以拆成颜色
和电影
那么也可以拆成颜``色``电``影
四个字,这样后面如果新加一个颜如玉
这样的敏感词又可以共用颜
字,减少颜
的匹配次数。美哉。
因为匹配单词是从前面按顺序往后所有字符都匹配才能算成功匹配,所以只有相同前缀的词可以共用前缀复用匹配。
如果说所有敏感词都不存在公共前缀那么就没法通过这个方法优化了。
这就是成了著名前缀树
[208. 实现 Trie (前缀树)](https://leetcode.cn/problems/implement-trie-prefix-tree/)
所以将所有敏感词构建一棵前缀树,之后将用户输入的内容进行检查即可。
public static void main(String[] args) { List<String> sensitiveWordList = new ArrayList<>(); sensitiveWordList.add("今天"); sensitiveWordList.add("明天"); sensitiveWordList.add("后天"); String word = "今天去上班,明天也要去上班,后天不用去上班"; //初始化一棵前缀树 Trie trie = new Trie(); for (String s : sensitiveWordList) { trie.insert(s); } System.out.println(trie.sensitiveWord(word)); } static class Trie { //通过map保存子树 private Map<Character, Trie> children ; private boolean isEnd; public Trie() { children = new HashMap<>(); isEnd = false; } //将敏感词插入到前缀树中 public void insert(String word) { Trie node = this; for (int i = 0; i < word.length(); i++) { char ch = word.charAt(i); Trie childrenNode = node.children.get(ch); if(childrenNode == null){ node.children.put(ch, new Trie()); } node = node.children.get(ch); } node.isEnd = true; } //检查一个单词的开头是否存在铭感词 private boolean prefixSensitiveWord(String word, int start) { Trie node = this; for (int i = start; i < word.length(); i++) { char ch = word.charAt(i); Trie childrenNode = node.children.get(ch); if(childrenNode == null){ //说明后面没有匹配的了 break; }else{ //说明存在一个敏感词匹配了 if(childrenNode.isEnd){ return true; } } node = childrenNode; } return node.isEnd; } //检查一个单词是否存在铭感词 private boolean sensitiveWord(String word) { //不断向前移动检查 for (int i = 0; i < word.length(); i++) { if(prefixSensitiveWord(word, i)){ return true; } } return false; } }
只是通过简单的敏感词匹配判断存在敏感词也存在一些缺陷,比如一台独立的服务器
,是理论上是合规的但是其中含有台又有独所以可能被判定含有敏感词。中文博大精深,可能换个读音表达一个意思,懂的都懂,但是机器不懂。牛点的可以通过人工智能的手段,训练一个专门识别敏感词的模型,提高识别率。但是语言博大精深,像在王者荣耀这种大游戏也照样能不带一点脏话骂人[狗头dog],可见要通过机器准确的进行敏感词检测难度之大,所以敏感词的检测只是一个简单的初步检查,真要花心思发布违规内容很难拦下或者说准确的拦截敏感词的成本是很高的。
一般再通过设计一些用户投诉机制,发布敏感词惩罚机制等进一步提高用户发敏感词的成本,想办法让用户不发,问题解决不了就解决搞出问题的人,嘿嘿。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。