hEn8lf

1.前言

最近给公司开发商品评论系统,需求要对评论进行脱敏处理,因为之前没深入接触过,有些想当然的怎么简单怎么来,刚开始想用foreach循环匹配,但是深入想一想,想不到这里的水真深。先说说思想,我们一般人能够是通过数据库去匹配文本内容、如果敏感词库数据量大,就会出现典型的数据多匹配数据少,这样即增加开销、浪费资源,又可能增加了页面的响应时间;而符合生产环境的做法是将文本内容分词然后匹配数据库敏感词,这样少量的循环就能实现我们需要的效果,如下图所示。

FFCTH9

可以看到我们传统思路是由数据库匹配文本,典型的多匹配少;所以思路逆转,少匹配多,采用文本分词手段,将需要的词匹配数据库,总的来说,当前方案一共讨论出四种。

方案1:PHP函数

优点:开发快
缺点:数据大效率低

方案2:正则匹配

优点:开发快,匹配度高
缺点:数据大效率低

方案3:全文搜索OR分词

优点:效率高
缺点:开发较复杂

方案4:API

优点:无需开发,直接调用
缺点:可能需要付费、无法自定义敏感词库

本章代码分享:https://github.com/mtdgclub/sensitiveWord

2.方案实战

2.1创建敏感词存储数据库

1
2
3
4
5
6
7
8
CREATE TABLE `t_sensitive_word` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`word` varchar(20) NOT NULL COMMENT '敏感字',
`reason` varchar(20) DEFAULT NULL COMMENT '过滤原因',
`is_del` tinyint(1) DEFAULT '0' COMMENT '是否删除 0不删除 1删除',
`create_time` int(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='敏感词库';

2.2方案1:PHP函数

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
/**
* PHP函数,处理敏感词接口
* 作用:将敏感词变成*号,并返回处理后的文本
*/
public function dealByFuncAction()
{
$word = $this->$_POST['appraise_msg'];
if (!empty($word)) {

//将敏感库初始静态化
if (empty(self::$save_words_array)) {
//从数据库获得敏感词库
$m_sensitive_word = Helper::load('sensitive_word');
$sensitiveWordArray = $m_sensitive_word->Field('word')->Select();
//处理数组,组装
$dealArray = [];
foreach ($sensitiveWordArray as $k => $v) {
$dealArray[] = $v['word'];
}
self::$save_words_array = $dealArray;
}

//给敏感词打码
$badWords = array_combine(self::$save_words_array, array_fill(0, count(self::$save_words_array), '*'));
$res = strtr($word, $badWords);
$data = ['code' => 1, 'msg' => '处理成功', 'data' => $res];
} else {
$data = ['code' => 0, 'msg' => '参数丢失'];
}
Helper::response($data);
}

2.3方案2:正则匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
/**
* 正则匹配,处理敏感词接口
* 作用:检测提示敏感词
*/
public function checkByRegularAction()
{
$word = $this->$_POST['appraise_msg'];
if (!empty($word)) {
//去掉文本多余字符
$flag_arr = array('•', '?', '!', '¥', '(', ')', ':', '‘', '’', '“', '”', '《', '》', ',', '…', '。', '、', 'nbsp', '】', '【', '~');
$content_filter = preg_replace('/\s/', '', preg_replace("/[[:punct:]]/", '', strip_tags(html_entity_decode(str_replace($flag_arr, '', $word), ENT_QUOTES, 'UTF-8'))));

//将敏感库初始静态化
if (empty(self::$save_words_array)) {
//从数据库获得敏感词库
$m_sensitive_word = Helper::load('sensitive_word');
$sensitiveWordArray = $m_sensitive_word->Field('word')->Select();
//处理数组,组装
$dealArray = [];
foreach ($sensitiveWordArray as $k => $v) {
$dealArray[] = $v['word'];
}
self::$save_words_array = $dealArray;
}

//正则匹配
$blacklist = "/" . implode("|", self::$save_words_array) . "/i";
self::$save_words_string = $blacklist;

//判断
if (preg_match(self::$save_words_string, $content_filter, $matches)) {
$data = ['code' => 0, 'msg' => '有敏感词', 'data' => $matches[0]];
} else {
$data = ['code' => 1, 'msg' => '没有敏感词'];
}
} else {
$data = ['code' => 0, 'msg' => '参数丢失'];
}
Helper::response($data);
}

另外还有正则匹配将敏感词变成*号并返回处理文本的函数 dealByRegular() ,详见 Sensitive.php 文件

2.4方案3:全文搜索OR分词

关于中文分词引擎,现在市面上提供多种方案选择,比如:SCWSSphinxPHPAnalysis等等;更有结合 scws(分词引擎) + xapian(搜索引擎) 构建的开源全文搜索引擎 xunsearch、高伸缩的开源全文搜索和分析引擎Elasticsearch 等成熟的全文搜索引擎插件。更深入的有检测算法->DFA算法(全名:Deterministic Finite Automaton)即有穷自动机。其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。

下面举两个案例:

2.4.1案例一:使用PHPAnalysis提取中文分词进行匹配

下载地址:https://github.com/mtdgclub/PHPAnalysis

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/**
* Notes:返回文本的所有分词结果(一维数组)
* @param string $content
* @return array|string
*/
public static function getAllParticiple($content = ""){
if (empty ( $content )) {
return '';
}
require_once 'phpanalysis.class.php';
$pa = new \PhpAnalysis ( 'utf-8', 'utf-8', false );
$pa->SetSource($content);
$pa->resultType=2;
$pa->differMax=true;
$pa->StartAnalysis();
$arr=$pa->GetFinallyIndex();
return $arr;
}

控制器中引用如下:

1
2
3
4
<?php
//1.引入PHPAnalysis分词引擎,提取分词数组
$Analysis = new WordAnalysis();
$result = $Analysis->getAllParticiple($word);

2.4.2案例二:使用Elasticsearch全文搜索检测敏感词

使用 composer 拉取 Elasticsearch

1
2
3
4
5
6
7
8
9
10
11
{
"require": {
"elasticsearch/elasticsearch" : "~5.0"
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://packagist.phpcomposer.com"
}
}
}

暂时放放,功力不足,看的吃力,可参考如下文档:

https://www.cnblogs.com/mzhaox/p/11210025.html

https://www.cnblogs.com/subendong/p/7308647.html

2.5方案4:API

这里以阿里云提供的敏感词API为例(点击处理跳转到API)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
/**
* 阿里云api
* @param $content
* @return array|mixed|string
*/
private function aliyunApi($content)
{
$host = "http://monitoring.market.alicloudapi.com";
$path = "/neirongjiance";
$method = "POST";
$appcode = "150b1369d73e4aa3b3f2b22ed6cceeb4";
$headers = array();
array_push($headers, "Authorization:APPCODE " . $appcode);
//根据API的要求,定义相对应的Content-Type
array_push($headers, "Content-Type" . ":" . "application/x-www-form-urlencoded; charset=UTF-8");
$querys = "";
$bodys = "in={$content}";
$url = $host . $path;

$curl = curl_init();//初始化curl
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_URL, $url);//抓取指定网页
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_FAILONERROR, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);//要求结果为字符串且输出到屏幕上
curl_setopt($curl, CURLOPT_HEADER, true);
if (1 == strpos("$" . $host, "https://")) {
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
}
curl_setopt($curl, CURLOPT_POSTFIELDS, $bodys);
$result = curl_exec($curl);
$code = curl_getinfo($curl,CURLINFO_HTTP_CODE);//取得响应码
$data = '';
if($code == 200){
$data = strstr($result, '{');
$data = json_decode($data);
}
curl_close($curl);
$data = ['code' => 1, 'msg' => '敏感词API测试', 'data' => $data];
return $data;
}
}

3.总结

通过开发敏感词校测功能,我明白不要小看每个功能,看似简单的东西,其实一点也不简单,每个小功能都能够延伸一定的高度,拿到需求先多想、多思考,把能够想到实现方案列出来,然后让拍板人去选择哪种方案,但是呢,空闲时间也要对其他不采用的方案进行了解和学习,要做到以点盖全,而不是点就是点,加油~