手机浏览 RSS 2.0 订阅 膘叔的简单人生 , 腾讯云RDS购买 | 超便宜的Vultr , 注册 | 登陆
浏览模式: 标准 | 列表2008年10月22日的文章

walker早期文章:通过MySQL内置全文检索实现中文的相关检索

一直在搜索这块有问题,中文搜索的话,不可避免的会遇到搜索 A 的时候会出现 B 结果的情形,这种情况在MYSQL4.0的时候犹为明显,最近在翻代码的时候,翻到walker以前写的一篇文章,依稀记得大概也是05年左右的事情了吧。

05年这家伙写了不少代码,什么BMP识别啥的。如今也都随着服务器的损坏而烟消云散了。借着google的光,把walker这篇以前的文章再COPY回来。为以后也开发可以做个参考,只是它的这个方法太占数据库空间了。呵呵

如今的walker代码是几乎不写了,以wow为主,可怜的人啊。。。。。http://www.walkerlee.net 是他现在的网站。

文章如下:
/**
*   @author   :   walkerlee
*   @copyright   :   www.neatstudio.com   |   www.walkerlee.net
*/

转载请保留以上信息。

关键字:MySQL   全文检索   全文索引   中文分词   二元分词   区位码   相似度

注:本文使用的MySQL版本为:MySQL   4.0.x

在MySQL4中,是已经开始支持全文检索(索引)的了。但是只是对英文支持全文检索。
由于英文在书写上的特殊性,使得分词算法相对中文来说,简单得多。一般来说,我们可以通过单词与单词之间的空格,以及标点符号来完成这个分词过程。
但是就中文来说,就没有那么简单。MySQL无法对中文做出正确的分词,假设有如下英文句子:

"Hello   world!   Hello   PHP! "

通过上面提及的方法,可以很简单的把这个句子分词为:

1   Hello
2   world
3   PHP

我们再来看看中文的句子:

"你好世界,你好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]   the   look
[MP3]   because   of   you

因为他们都同有MP3在标题中,所以会出现上述提到的问题。

回到ft_min_word_len值的问题,我们之所以要修改他,是为了能让MySQL找到我们的二元分词,但是短小的英文又被“无辜”的卷入,我们目 前要解决的问题就是,如何使得MySQL能检索到二个字的中文词汇,又能忽略掉原本的英数?第一个反应是把中文MD5,这样以上分词就将转化为以下结果:

你好   好世   世界   你好   PHP   =>   b94ae3c6d892b29cf48d9bea819b27b9   f5625345be46432fb0fd51340fcf6679   9067de5206278a93823f9c5dc2c737fd   b94ae3c6d892b29cf48d9bea819b27b9   PHP

这样做,首先是使得中文分词的长度超越了默认的2个字,同时消除了中文的歧义性。(MySQL4对中文的处理有问题),搜索“车轮”时候,不再会出现类似“发动机”结果的问题。(车轮的例子只是为了方便理解而做出的假设)

通过上面的做法,已经解决了分词最小长度的问题,顺利的把中文词汇长度升级,从而达到把中文词汇划入检索范围,把较短的英数划出检索范围。
休息一下,然后发现这个MD5后的字符串是否太长了点……比较占用空间,要不,于是想到区位码,4位数的区位码能表示一个GB汉字,一个词有二个汉字组成,转换为区位码后是8个数字。不但能确定惟一性,也就MD5而已减少了长度。下面是转换后的:

你好   好世   世界   你好   PHP   =>   b94ae3c6d892b29cf48d9bea819b27b9   f5625345be46432fb0fd51340fcf6679   9067de5206278a93823f9c5dc2c737fd   b94ae3c6d892b29cf48d9bea819b27b9   PHP   =>   36672635   26354232   42322971   36672635   PHP

呵呵,是不是比MD5的小了很多呢?最后我们把相同的词汇留一个,多余的删除。得到

36672635   26354232   42322971   PHP

于是就完成了   "你好世界,你好PHP! "   到   "36672635   26354232   42322971   PHP "   的转换。

通过上面方法结合MySQL全文检索语句,我们可以通过给出一个标题例如: "迈克尔·杰克逊   -《危险之旅之布加勒斯特站》 "找出类似以下的相关标题

迈克尔杰克逊   -《迈克尔杰克逊危险布加勒斯特演唱会》
Michael   Jackson   -《迈克尔杰克逊   罗马尼亚   危险演唱会》
迈克尔杰克Michael   Jackson   -《危险之旅》
迈克尔杰克逊   -《迈克尔杰克逊   美国50annive演唱会危险片段》
迈克尔杰克逊   -《迈克尔杰克逊   终极收藏   原版DVD危险演唱会》
迈克尔杰克逊     杰克逊五兄弟   -《The   Jackson   Motown   25   演唱会》
迈克尔杰克逊   -《迈克尔杰克逊BAD日本Yokohama演唱会》
迈克尔杰克逊   -《迈克尔杰克逊日本大阪演唱会》
迈克尔杰克逊   -《迈克尔杰克逊之胜利-达拉丝演唱会》
迈克尔杰克逊   -《迈克尔杰克逊之胜利演唱会   比丽珍   片段》
迈克尔杰克逊   -《迈克尔杰克逊德国危险演唱会之   billie   jean片段》
迈克尔杰克逊   -《Michael   Jackson   -30周年演唱会》
Michael   Jackson   -《迈克尔杰克逊   马尼拉   历史演唱会》
迈克尔杰克逊   -《1993年美国橄榄球中场休息精彩表演》

表结构   article
title     varchar   200         --------       用于存放标题   (显示用)
ft             text           ----         fulltext     用于存放标题分词结果   (检索用)

首先我们在把标题保存到数据库时候,就已经对标题进行分词转区位码,保存到ft字段中,用于相关性的检索。
然后把给出的标题 "迈克尔·杰克逊   -《危险之旅之布加勒斯特站》 "转为 "34853143   31432291   22910104   01042960   29603143   31434923   46034753   47535414   54143435   34355414   54141828   18282851   28513253   32534325   43254456   44565330 ",最后进行全文检索查询:

SELECT   title,   MATCH(   ft   )   AGAINST(   '34853143   31432291   22910104   01042960   29603143   31434923   46034753   47535414   54143435   34355414   54141828   18282851   28513253   32534325   43254456   44565330 '   IN   BOOLEAN   MODE   )   AS   score  
FROM   article  
WHERE   MATCH(   ft   )   AGAINST(   '34853143   31432291   22910104   01042960   29603143   31434923   46034753   47535414   54143435   34355414   54141828   18282851   28513253   32534325   43254456   44565330 '   IN   BOOLEAN   MODE   )  
ORDER   BY   score   DESC  
LIMIT   0,   5

从SQL   Query上来看,进行了两次全文检索,其实不然,MySQL会将其视为一次,所以不比担心。
同时使用了AS   score,这个score是相似度,分值越高,自然越与给出的标题相近。

二点建议:
1.在实际使用中,挑选score大于1的作为检索结果。
2.检索结果会将本身标题也算入其中,根据score排序,为第一条,别忘记过滤哦   ^_^。

站在用户的立场来说,我们给用户提供了更多的相关内容,站在搜索引擎立场上来说,给关键字提供了更多的相关链接,形成了良好的站内互联结构,提高了搜索引擎对网页的评价。

如果各位碰到错误的不合理的地方,恳请指正,共同进步。谢谢!

参考资料:

1.Monkey的二元分词   作者:Monkey   http://www.baidu.com/s?wd=monkey+%B6%FE%D4%AA%B7%D6%B4%CA&cl=3
2.PHP里如何实现汉字转区位码   提供者:haoyoul   http://zhidao.baidu.com/question/5371961.html
3.对dvbbs.php全文搜索的完全分析   作者:fcicq     http://www.phpx.com/happy/viewthread.php?tid=124691

 

 

Tags: walkerlee, 全文检索, mysql, 中文