在当前的网络环境下,敏感词过滤已经是各大网站的“标准配置”,如果不想被大量的垃圾信息充斥,除了使用机器人识别、验证码等验证工具,还需要阻止含有敏感词内容的发布,否则可能面临关站等风险,可谓是国内互联网的红线。
布隆过滤器
布隆过滤器(英语:Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
敏感词的过滤,在编程中最常见的方法是敏感词库数组遍历匹配,如果敏感词在文本中出现,则视为违规。本文将介绍基于分词技术和布隆过滤器实现的敏感词检测。相比于其它的数据结构,布隆过滤器在空间和时间方面都有巨大的优势。布隆过滤器存储空间和插入/查询时间都是常数(O(k))。另外,散列函数相互之间没有关系,方便由硬件并行实现。布隆过滤器不需要存储元素本身,在某些对保密要求非常严格的场合有优势。除了敏感词过滤,布隆过滤器还有以下应用场景:
- 字处理软件中,需要检查一个英语单词是否拼写正确
- 在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上
- 在网络爬虫里,一个网址是否被访问过
- yahoo, gmail等邮箱垃圾邮件过滤功能
布隆过滤器的原理
布隆过滤器(Bloom Filter)的核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k
以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
基于Java的编程实现
Google提供的guava核心库中,提供了布隆过滤器的实现。其中的敏感词库文件,可以参考:https://github.com/tenstone/textfilter
@Service
public class BloomFilterService {
private BloomFilter<String> configuredFilter;
private final BloomFilter<String> filter = BloomFilter.create(new Funnel<String>() {
private static final long serialVersionUID = 1L;
@Override
public void funnel(String arg0, PrimitiveSink arg1) {
arg1.putString(arg0, Charsets.UTF_8);
}
}, 1024*1024*32);
/**
* 读取带敏感词的布隆过滤器
*
* @return
* @throws IOException
*/
public BloomFilter<String> getSensitiveWordsFilter() throws IOException {
InputStreamReader read = null;
BufferedReader bufferedReader = null;
read = new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("SensitiveWords.txt"), StandardCharsets.UTF_8);
bufferedReader = new BufferedReader(read);
for (String txt = null; (txt = bufferedReader.readLine()) != null; ) {
filter.put(txt);
}
this.configuredFilter = filter;
return filter;
}
/**
* 判断一段文字中,是否包含敏感词
*
* @param segments
* @return
*/
public Boolean segmentSensitiveFilterPassed(String[] segments) {
if(configuredFilter == null){
try {
getSensitiveWordsFilter();
}catch (IOException e){
e.printStackTrace();
}
}
Segment shortestSegment = new DijkstraSegment().enableCustomDictionary(false).enablePlaceRecognize(true).enableOrganizationRecognize(true);
for(String segment :segments){
List<Term> termList = shortestSegment.seg(segment);
for (Term term :termList){
// 如果布隆过滤器中找到了对应的词,则认为敏感检测不通过
if(configuredFilter.mightContain(term.word)){
log.info("检测到敏感词:"+term.word);
throw new RuntimeException("检测到敏感词");
}
}
}
return true;
}