为了账号安全,请及时绑定邮箱和手机立即绑定

使用SimHash进行海量内容数据查重 - PHP版

工作当中我们有时候会遇到需要对用户上传的文件查重,根据重复度进行驳回还是要求用户重新上传;而且这个数据量对比有时候会很大,此时就比较适合用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人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消