通过MySQL全文搜索实现中文的相关搜索.docx
- 文档编号:10425134
- 上传时间:2023-02-11
- 格式:DOCX
- 页数:19
- 大小:25.26KB
通过MySQL全文搜索实现中文的相关搜索.docx
《通过MySQL全文搜索实现中文的相关搜索.docx》由会员分享,可在线阅读,更多相关《通过MySQL全文搜索实现中文的相关搜索.docx(19页珍藏版)》请在冰豆网上搜索。
通过MySQL全文搜索实现中文的相关搜索
通过MySQL内置全文检索实现中文的相关检索
关键字:
MySQL全文检索全文索引中文分词二元分词区位码相似度
注:
本文使用的MySQL版本为:
MySQL4.0.x
在MySQL4中,是已经开始支持全文检索(索引)的了。
但是只是对英文支持全文检索。
由于英文在书写上的特殊性,使得分词算法相对中文来说,简单得多。
一般来说,我们可以通过单词与单词之间的空格,以及标点符号来完成这个分词过程。
但是就中文来说,就没有那么简单。
MySQL无法对中文做出正确的分词,假设有如下英文句子:
"Helloworld!
HelloPHP!
"
通过上面提及的方法,可以很简单的把这个句子分词为:
1Hello
2world
3PHP
我们再来看看中文的句子:
"你好世界,你好PHP!
"
按照英文的算法,分词如下:
1你好世界
2你好PHP
显然是不能满足我们的需要的。
所以,首先我们要做的是,把中文的句子转变为MySQL眼中的英文,以便使得它能以英文分词算法去对句子进行正确的分词处理。
先将上面中文句子进行标点过滤处理,得到以下句子:
你好世界你好PHP
接着再使用中文分词中较简单实现的二元分词算法对句子进行二元分词,得到以下句子:
你好好世世界你好PHP
因为把标点符号替换为空格,以及PHP本身为英文字母的关系,可以不用进行二元切分,所以得到上面句子。
这个时候,我们来看看处理过后的句子,会发现,就其书写格式上来说,已经符合英文的书写格式,既以空格,标点来对单词形成自然间隔。
只是上面句子没有标点,只有空格而已。
到此,我们已经成功的将中文“翻译”为MySQL能理解的“英文”书写格式。
但是,问题还没解决,首先,MySQL中,ft_min_word_len(分词词汇最小长度)这个参数的默认值为4,也就是4个字母以上长度的单词,才会被考虑,小于4个的,将会被忽略。
如果不改变这个长度,按照上面的分词结果,我们将无法通过你好,世界,PHP等检索到相关的结果,因为分出来的词太短了,不在MySQL的选择范围内。
我们可以通过修改ft_min_word_len的值,将其设置为2来解决上面问题,但是这样做的话,在检索列表中的原本就为英文的短小词汇,如:
PHP,MP3,也会被划入检索范围内,这样做的结果是,出现很多无意义的相关结果。
请看以下列表:
[MP3]thelook
[MP3]becauseofyou
因为他们都同有MP3在标题中,所以会出现上述提到的问题。
回到ft_min_word_len值的问题,我们之所以要修改他,是为了能让MySQL找到我们的二元分词,但是短小的英文又被“无辜”的卷入,我们目前要解决的问题就是,如何使得MySQL能检索到二个字的中文词汇,又能忽略掉原本的英数?
第一个反应是把中文MD5,这样以上分词就将转化为以下结果:
你好好世世界你好PHP=>b94ae3c6d892b29cf48d9bea819b27b9f5625345be46432fb0fd51340fcf66799067de5206278a93823f9c5dc2c737fdb94ae3c6d892b29cf48d9bea819b27b9PHP
这样做,首先是使得中文分词的长度超越了默认的2个字,同时消除了中文的歧义性。
(MySQL4对中文的处理有问题),搜索“车轮”时候,不再会出现类似“发动机”结果的问题。
(车轮的例子只是为了方便理解而做出的假设)
通过上面的做法,已经解决了分词最小长度的问题,顺利的把中文词汇长度升级,从而达到把中文词汇划入检索范围,把较短的英数划出检索范围。
休息一下,然后发现这个MD5后的字符串是否太长了点……比较占用空间,要不,于是想到区位码,4位数的区位码能表示一个GB汉字,一个词有二个汉字组成,转换为区位码后是8个数字。
不但能确定惟一性,也就MD5而已减少了长度。
下面是转换后的:
你好好世世界你好PHP=>b94ae3c6d892b29cf48d9bea819b27b9f5625345be46432fb0fd51340fcf66799067de5206278a93823f9c5dc2c737fdb94ae3c6d892b29cf48d9bea819b27b9PHP=>36672635263542324232297136672635PHP
呵呵,是不是比MD5的小了很多呢?
最后我们把相同的词汇留一个,多余的删除。
得到
366726352635423242322971PHP
于是就完成了"你好世界,你好PHP!
"到"366726352635423242322971PHP"的转换。
通过上面方法结合MySQL全文检索语句,我们可以通过给出一个标题例如:
"迈克尔·杰克逊-《危险之旅之布加勒斯特站》"找出类似以下的相关标题
迈克尔杰克逊-《迈克尔杰克逊危险布加勒斯特演唱会》
MichaelJackson-《迈克尔杰克逊罗马尼亚危险演唱会》
迈克尔杰克MichaelJackson-《危险之旅》
迈克尔杰克逊-《迈克尔杰克逊美国50annive演唱会危险片段》
迈克尔杰克逊-《迈克尔杰克逊终极收藏原版DVD危险演唱会》
迈克尔杰克逊杰克逊五兄弟-《TheJacksonMotown25演唱会》
迈克尔杰克逊-《迈克尔杰克逊BAD曰本Yokohama演唱会》
迈克尔杰克逊-《迈克尔杰克逊曰本大阪演唱会》
迈克尔杰克逊-《迈克尔杰克逊之胜利-达拉丝演唱会》
迈克尔杰克逊-《迈克尔杰克逊之胜利演唱会比丽珍片段》
迈克尔杰克逊-《迈克尔杰克逊德国危险演唱会之billiejean片段》
迈克尔杰克逊-《MichaelJackson-30周年演唱会》
MichaelJackson-《迈克尔杰克逊马尼拉历史演唱会》
迈克尔杰克逊-《1993年美国橄榄球中场休息精彩表演》
表结构article
titlevarchar200--------用于存放标题(显示用)
fttext----fulltext用于存放标题分词结果(检索用)
首先我们在把标题保存到数据库时候,就已经对标题进行分词转区位码,保存到ft字段中,用于相关性的检索。
然后把给出的标题"迈克尔·杰克逊-《危险之旅之布加勒斯特站》"转为"34853143314322912291010401042960296031433143492346034753475354145414343534355414541418281828285128513253325343254325445644565330",最后进行全文检索查询:
SELECTtitle,MATCH(ft)AGAINST('34853143314322912291010401042960296031433143492346034753475354145414343534355414541418281828285128513253325343254325445644565330'INBOOLEANMODE)ASscore
FROMarticle
WHEREMATCH(ft)AGAINST('34853143314322912291010401042960296031433143492346034753475354145414343534355414541418281828285128513253325343254325445644565330'INBOOLEANMODE)
ORDERBYscoreDESC
LIMIT0,5
从SQLQuery上来看,进行了两次全文检索,其实不然,MySQL会将其视为一次,所以不比担心。
同时使用了ASscore,这个score是相似度,分值越高,自然越与给出的标题相近。
二点建议:
1.在实际使用中,挑选score大于1的作为检索结果。
2.检索结果会将本身标题也算入其中,根据score排序,为第一条,别忘记过滤哦^_^。
站在用户的立场来说,我们给用户提供了更多的相关内容,站在搜索引擎立场上来说,给关键字提供了更多的相关链接,形成了良好的站内互联结构,提高了搜索引擎对网页的评价。
如果各位碰到错误的不合理的地方,恳请指正,共同进步。
谢谢!
============================================================================
参考资料:
============================================================================
1.Monkey的二元分词
首先,我们来想想MySQL不支持中文索引的关键原因还是中文是双字节的,如果能把中文转换成单字节的字母或数字,那不就可以使用全文索引了吗
基于这个目的,我们首先需要做的就是分词,如果要实现比较完美的分词的话,还是需要安装相应的插件,但我们很多是虚拟主机,根本没有条件来安装,所以只能采取比较原始的分词方法,二元分词法。
所谓二元分词法,就是将一句话从头到尾,两个字两个字地分开,比如:
我们的祖国是花园。
就可以划分为:
我们,们的,的祖,祖国,国是,是花,花园。
虽然有点浪费,但至少面面俱到了。
PHP的相应函数
//Monkey's二元分词
functionsp_str($str){
//所有汉字后添加ASCII的0字符,此法是为了排除特殊中文拆分错误的问题
$str=preg_replace("/[\x80-\xff]{2}/","\\0".chr(0x00),$str);
//拆分的分割符
$search=array(",","/","\\",".",";",":
","\"","!
","~","`","^","(",")","?
","-","\t","\n","'","<",">","\r","\r\n","$","&","%","#","@","+","=","{","}","[","]",":
",")","(",".","。
",",","!
",";","“","”","‘","’","[","]","、","—"," ","《","》","-","…","【","】",);
//替换所有的分割符为空格
$str=str_replace($search,'',$str);
//用正则匹配半角单个字符或者全角单个字符,存入数组$ar
preg_match_all("/[\x80-\xff]?
./",$str,$ar);$ar=$ar[0];
//去掉$ar中ASCII为0字符的项目
for($i=0;$i =chr(0x00))$ar_new[]=$ar[$i]; $ar=$ar_new;unset($ar_new);$oldsw=0; //把连续的半角存成一个数组下标,或者全角的每2个字符存成一个数组的下标 for($ar_str='',$i=0;$i $sw=strlen($ar[$i]); if($i>0and$sw! =$oldsw)$ar_str.=""; if($sw==1)$ar_str.=$ar[$i]; else if(strlen($ar[$i+1])==2)$ar_str.=$ar[$i].$ar[$i+1].''; elseif($oldsw==1or$oldsw==0)$ar_str.=$ar[$i]; $oldsw=$sw; } //去掉连续的空格 $ar_str=trim(preg_replace("#{1,}#i","",$ar_str));//$ar_str="Monkeys二元元分分词" //返回拆分后的结果 returnexplode('',$ar_str); } 接下来,就该考虑如何把分好的词转换成单字节的,可以使用base64,sha1,md5。 但有个问题就是转换后的字符有点长,那如何才能缩短字符呢,对了,就是使用区位码,因为区位码短啊,一个中文只占四个字节。 每个中文都有对应的区位码(除了标点符号和特殊符号),这样只要将上面分词的结果通过区位码转换后,然后存储到数据库里,就可以了。 PHP区位码函数 functionquweima($str){ if(preg_match("/^[a-z0-9]+$/i",$str)){ return$str; }else{ $str1=substr($str,0,2); //echo$str1; $str_qwm=sprintf("%02d%02d",ord($str[0])-160,ord($str[1])-160); $str2=substr($str,2,4); //echo$str2; $str_qwm.=sprintf("%02d%02d",ord($str[0])-160,ord($str[1])-160); return$str_qwm; } } 这里我加了判断,如果是英文或数字直接返回不做处理 经过这两步处理后,准备工作就基本完成了,下面就是建立数据库 我的数据库结构是这样的 id,title,title_ft(fulltext) 添加数据的时候,title存放标题,ft_title存放处理后的标题,内容应该是像这样的: 4355740154903471… SQL代码 $query="SELECTtitle,MATCH(title_ft)AGAINST('$title_ft'INBOOLEANMODE)ASscore FROMinfo WHEREMATCH(title_ft)AGAINST('$title_ft'INBOOLEANMODE) ORDERBYscoreDESC"; 其中$title_ft是经过两个函数处理后的字符串,用它去匹配title_ft。 [解决]monkey的二元分词,分utf编码的中文时出现乱码 DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.0Transitional//EN">
php
functiondualDecom($str)
{
//所有汉字后添加ASCII的0字符,此法是为了排除特殊中文拆分错误的问题
$str=preg_replace("/[\x80-\xff]{3}/","\\0".chr(0x00),$str);
//拆分的分割符
$search=array(",","/","\\",".",";",":
","\"","!
","~","`","^","(",")","?
","-","\t","\n","'","<",">","\r","\r\n","$","&","%","#","@","+","=","{","}","[","]",":
",")","(",".","。
",",","!
",";","“","”","‘","’","[","]","、","—"," ","《","》","-","…","【","】",);
//替换所有的分割符为空格
$str=str_replace($search,'',$str);
//用正则匹配半角单个字符或者全角单个字符,存入数组$ar
preg_match_all("/[\x80-\xff]+?
\\x00/",$str,$ar);
$ar=$ar[0];
//去掉$ar中ASCII为0字符的项目
for($i=0;$i if($ar[$i]! =chr(0x00))$ar_new[]=$ar[$i]; $ar=$ar_new; unset($ar_new); $oldsw=0; //把连续的半角存成一个数组下标,或者全角的每2个字符存成一个数组的下标 for($ar_str='',$i=0;$i { $sw=strlen($ar[$i]); if($i>0and$sw! =$oldsw)$ar_str.=""; if($sw==1) $ar_str.=$ar[$i]; else if(strlen($ar[$i+1])>=2) $ar_str.=$ar[$i].$ar[$i+1].''; elseif($oldsw==1OR$oldsw==0) $ar_str.=$ar[$i]; $oldsw=$sw; } //去掉连续的空格 $ar_str=trim(preg_replace("#{1,}#i","",$ar_str)); returnexplode('',$ar_str); } print_r(dualDecom('比如有一个字符串是“你好PHP! ”就只能分出“你好”一个词')); ? > 2.PHP里如何实现汉字转区位码 php global$PHP_SELF; //echo$PHP_SELF; $t1=$_POST['textfield1']; $t2=$_POST['textfield2']; $t3=$_POST['textfield3']; $t4=$_POST['textfield4']; //汉字--区位码 if($t1! =""){ $t2=sprintf("%02d%02d",ord($t1[0])-160,ord($t1[1])-160); //echo$t2; } //区位码--汉字 if($t3! =""){ $t4=chr(substr($t3,0,2)+160).chr(substr($t3,2,2)+160); //echo$t4; } ? > DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http: //www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> //www.w3.org/1999/xhtml"> -- .STYLE1{font-size: 18px} -->
=$PHP_SELF?
>">
=$t1? >"/> =$t2? >"/> =$PHP_SELF? >"> =$t3? >"/> =$t4? >"/>