工作当中我们有时候会遇到需要对用户上传的文件查重,根据重复度进行驳回还是要求用户重新上传;而且这个数据量对比有时候会很大,此时就比较适合用simhash来进行对比!!!
关于什么是simhash以及基础介绍可以看这个文章:https://blog.csdn.net/lengye7/article/details/79789206
本例使用语言:php5.3(Yaf)
mysql:5.7.2
现有需求:需要对用户上传的word/ppt/txt文件内容在库里进行对比,找出内容重复度比较高的文件
首先先确定分几步进行数据分析对比:
1. 读取用户上传的文件内容;
2. 根据SimHash算法进行数据分析对比;
既然是数据对比,那么肯定需要把不重复的数据存储起来,然后再拿出来和上传的文件内容对比,那么就需要有数据库来存储数据,结构如下:
数据执行simhash之后的值进行存储:
CREATE TABLE `tbl_resource_simhash` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主建id',
`resource_id` int(11) NOT NULL DEFAULT '0' COMMENT '资源id',
`segment1` int(11) NOT NULL DEFAULT '0' COMMENT '块1',
`segment2` int(11) NOT NULL DEFAULT '0' COMMENT '块2',
`segment3` int(11) NOT NULL DEFAULT '0' COMMENT '块3',
`segment4` int(11) NOT NULL DEFAULT '0' COMMENT '块4',
`simhash` varchar(64) NOT NULL DEFAULT '0' COMMENT '哈希值',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_resource_id` (`resource_id`) USING BTREE,
KEY `idx_segment1` (`segment1`) USING HASH,
KEY `idx_segment2` (`segment2`) USING HASH,
KEY `idx_segment3` (`segment3`) USING HASH,
KEY `idx_segment4` (`segment4`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=760 DEFAULT CHARSET=utf8 COMMENT='公开资源哈希索引表';
文件资源信息表:
CREATE TABLE `tbl_resource` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件名称',
`words` longtext CHARACTER SET utf8mb4 NOT NULL COMMENT '资源内容文字',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态 0未进行审核 1审核通过 2审核未通过',
`md5_file` char(32) NOT NULL DEFAULT '' COMMENT '文件的md5值',
PRIMARY KEY (`id`),
KEY `idx_md5` (`md5_file`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='资源文件表';
在文件上传之后,我们可以先把上传的文件存储起来,然后进行机器审核,这里就不贴文件如何存储的代码了,我这里是把源文件放到的FastDFS里面。
1、把上传的文件内容进行simhash并保存
/**
* 这里是存储tbl_resource表之后的操作:
* 在这里进行simhash,保存到tbl_resource_simhash表
* 然后后面再有文件上传就可以进行对比
*/
$dataResource = Ap_Service_Data_Resource_Resource::getInstance();
//获取资源信息
$resource = $dataResource->getById($resource_id);
if(!empty($resource)){
$md5_file = $resource['md5_file'];
$words = $resource['words'];
$simhash = "";
$words_analysis = Ap_Util_SimHash::getAnalysis($words);
if(!empty($words_analysis)){
if(key($words_analysis) == '的'){
array_shift($words_analysis);
}
// 计算64位simhash
$simhash = Ap_Util_SimHash::get($words_analysis);
}
if($simhash){
$simhash_arr = str_split($simhash,16);
//把计算好的simhash更新到tbl_resource_simhash表
$this->save($resource_id,bindec($simhash_arr[0]) ,bindec($simhash_arr[1]) ,bindec($simhash_arr[2]) ,bindec($simhash_arr[3]) ,$simhash);
}
}
/**
* 保存
*
* @param int $resource_id
* @param int $segment1
* @param int $segment2
* @param int $segment3
* @param int $segment4
* @param int $simhash
* @return void
*/
public function save($resource_id,$segment1,$segment2,$segment3,$segment4,$simhash){
$id = 0;
$where = array(
'resource_id' => $resource_id
);
$obj = $this->getRow($where);
if(empty($obj)){
$data = array(
'resource_id' => $resource_id,
'segment1' => $segment1,
'segment2' => $segment2,
'segment3' => $segment3,
'segment4' => $segment4,
'simhash' => $simhash,
'create_time' => time()
);
$id = $this->insert($data);
}else{
$data = array(
'segment1' => $segment1,
'segment2' => $segment2,
'segment3' => $segment3,
'segment4' => $segment4,
'simhash' => $simhash,
'update_time' => time()
);
$id = $obj['id'];
$this->update($data,$id);
}
return $id;
}
2、进行文件内容对比
/**
* 机器审核
*
* @param int $resource_id 资源id
* @return void
*/
public function machineAudit($resource_id){
$repeat_resource_id = 0;
$words_analysis = array();
$simhash = '';
$md5_file = '';
$dataResource = Ap_Service_Data_Resource_Resource::getInstance();
//获取未对比过的资源信息
$resource = $dataResource->getById($resource_id);
if(!empty($resource)){
$md5_file = $resource['md5_file'];
//判断是否存在MD5重复的公开文件
$repeat_file = $dataResource->hasMd5NotByResourceId($resource_id,$md5_file);
if(!empty($repeat_file)){
//#有md5重复的文件,可以在这里添加自己的逻辑
return false;
}else{
/**
* 没有md5重复的文件
* 进行simhash对比,判断文档的相似度 - 重点!!!
*/
$words = $resource['words'];
if($words){
// 获取文章的分词数据
$words_analysis = Ap_Util_SimHash::getAnalysis($words);
if(!empty($words_analysis)){
//如果词组大于30个,则弹出第一个词组后进行对比,因为第一个词组很可能都是一样的语气词
if(key($words_analysis) == '的'){
array_shift($words_analysis);
}
// 计算64位simhash
$simhash = Ap_Util_SimHash::get($words_analysis);
// 查找相似文章
$repeat_resource_id = $this->findSimilarResourceId($resource_id,$simhash);
if($repeat_resource_id){
//有重复
return true;
}else{
//无重复
return false;
}
}
}
}
}
}
}
/**
* 查询相似资源id
*
* @param int $resource_id
* @param int $simhash
* @return int resource_id
*/
public function findSimilarResourceId($resource_id,$simhash){
$simhash_16_arr = str_split($simhash,16);
$repeat_id = 0;
$is_repeat = false;//没有重复
foreach($simhash_16_arr as $key => &$value){
$index = $key + 1;
$segment = bindec($value);
$list = $this->getAllBySegment($resource_id,$segment,$index);
if(!empty($list)){
//如果存在相似的值,则计算海明距离,如果海明距离<=3,则判断其它块
foreach($list as &$v){
$hd = 0;
$hd = Ap_Util_SimHash::hd($simhash,$v['simhash']);
if($hd > 3){
//海明距离>3,说明没有重复
break;
}
if($hd <=3 ){
//海明距离<=3 说明找到了重复项,可以结束函数
$is_repeat = true;
}
}
unset($v);
unset($list);
}
}
return $is_repeat;
}
/**
* 获取指定块中相似文章的集合
*
* @param int $segment
* @param int $index
* @return array
*/
public function getAllBySegment($resource_id,$segment,$index){
$arr = array();
if(!in_array($index,array(1,2,3,4))){
return $arr;
}
$column = 'segment'.$index;
$where = array(
'resource_id' => array('NE' => $resource_id),
$column => $segment,
'status' => 0
);
$arr = $this->getAll($where);
return $arr;
}
上面提到的
Ap_Util_SimHash:$words_analysis = Ap_Util_SimHash::getAnalysis($words);
代码:
<?php
/**
* Simhash工具类,求文本的simhash进行计算海明距离
*/
require_once APP_PATH."/WordAnalysis/phpanalysis.class.php";
class Ap_Util_SimHash{
// protected static $length = 64;
protected static $length = 64;
protected static $search = array('0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f');
protected static $replace = array('0000','0001','0010','0011','0100','0101','0110','0111','1000','1001','1010','1011','1100','1101','1110','1111');
/**
* 获取Simhash
*
* @param array $set
* @return void
*/
public static function get(array &$set)
{
$boxes = array_fill(0, self::$length, 0);
if (is_int(key($set)))
$dict = array_count_values($set);
else
$dict = &$set;
foreach ($dict as $element => $weight) {
$hash = hash('md5', $element);
$hash = str_replace(self::$search, self::$replace, $hash);
$hash = substr($hash, 0, self::$length);
$hash = str_pad($hash, self::$length, '0', STR_PAD_LEFT);
for ( $i=0; $i < self::$length; $i++ ) {
$boxes[$i] += ($hash[$i] == '1') ? $weight : -$weight;
}
}
$s = '';
foreach ($boxes as $box) {
if ($box > 0)
$s .= '1';
else
$s .= '0';
}
return $s;
}
/**
* 计算海明距离Hamming Distance
*
* @param [type] $h1
* @param [type] $h2
* @return void
*/
public static function hd($h1, $h2)
{
$dist = 0;
for ($i=0;$i<64;$i++) {
if ( $h1[$i] != $h2[$i] )
$dist++;
}
// return (self::$length - $dist) / self::$length;
return $dist;
}
/**
* 分词
*
* @param string $text
* @return void
*/
public static function getAnalysis($text){
ini_set('memory_limit', '512M');
$pa = new PhpAnalysis();
$pa->SetSource($text);
$pa->resultType=2;
$pa->differMax=true;
$pa->StartAnalysis();
$arr=$pa->GetFinallyIndex();
unset($pa);
return $arr;
}
}
以上就是进行simhash对比相似内容的代码,此代码只是提供一个解决思路,部分代码可参考,具体到业务还需要自行修改新增
点击查看更多内容
1人点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦