喜悦国际村 
» 游客:  注册 | 登录 | 搜索 | 统计 | 喜悦证交所 | 帮助

RSS 订阅当前论坛  

上一主题 下一主题
 22  1/3  1  2  3  > 
     
标题: [原创] 对dvbbs.php 全文搜索的完全分析  
 
fcicq
新手上路
Rank: 1
初级会员



UID 24467
精华 0
积分 21
帖子 587
金钱 20 喜悦币
威望 0
人脉 1
阅读权限 10
注册 2003-11-8
来自 fcicq.net
状态 离线
对dvbbs.php 全文搜索的完全分析

这次给大家渗透些比较高级些的.关于搜索的东西.
首先,大家先去下载一份dvbbs.php beta1的代码.解压放在手头上
(tmd,动网这代码写的真不咋的.bug成吨.)
这里不是让你安装运行,而是纯粹的研究,偶根本就没用它.
已经有的同学们就可以继续往下看了.

首先抛开php代码.
找出你的mysql手册.(没有?那直接看下面的吧.)

mysql全文搜索,sql的写法:
MATCH (fcicq,col1,col2,...) AGAINST (expr [IN BOOLEAN MODE | WITH QUERY EXPANSION])

比如:
SELECT * FROM articles WHERE MATCH (title,body) AGAINST ('database fcicqbbs');

MATCH()函数对于一个字符串执行资料库内的自然语言搜索。一个资料库就是1套1个或2个包含在FULLTEXT内的列。搜索字符串作为对AGAINST()的参数而被给定。对于表中的每一行, MATCH() 返回一个相关值,即, 搜索字符串和 MATCH()表中指定列中该行文字之间的一个相似性度量。

  下面的例子则更加复杂。询问返回相关值,同时对行按照相关性渐弱的顺序进行排序。为实现这个结果,你应该两次指定 MATCH(): 一次在 SELECT 列表中而另一次在 WHERE子句中。这不会引起额外的内务操作,原因是MySQL 优化程序注意到两个MATCH()调用是相同的,从而只会激活一次全文搜索代码。

mysql> SELECT id, body, MATCH (title,body) AGAINST

    -> ('Security implications of running MySQL as root') AS score

    -> FROM articles WHERE MATCH (title,body) AGAINST

    -> ('Security implications of running MySQL as root');


所以,到这里你应该会英文的搜索了.
===
请注意一个问题.

一些词在全文搜索中会被忽略:
    * 任何过于短的词都会被忽略。 全文搜索所能找到的词的默认最小长度为 4个字符。
    * 停止字中的词会被忽略。

===
mysql还自带查询扩展功能.这里不做过多讨论.
===
下面进行中文全文搜索的分析.
大家鼓掌.......

曾经有一个版本的mysql支持中文全文搜索(海量 mysql chinese+,说是GPL但是最终没有开源)
中文全文搜索的关键是在分词上.mysql本身不支持cjk的分词(cjk:chinese,japanese,korean),
所以

!!!!****如何模拟分词是mysql全文索引的关键****!!!!

中文分词是语言分词中最困难的.现在也没有人能够彻底完美的解决(虽然这些搜索引擎做的都还不错.)

//fcicq:下面给大家看看动网的分词是怎么做的.
function &DV_ChineseWordSegment($str,$encodingName='gbk'){

        static $objEnc = null;

        if( $objEnc === null ){

                if( !class_exists('DV_Encoding') ){

                        require_once ROOT_PATH.'inc/DV_Encoding.class.php';

                }

                $objEnc =& DV_Encoding::GetEncoding($encodingName);

        }

        $strLen = $objEnc->StrLength($str);

        $returnVal = array();

        if( $strLen < = 1 ){

                return $str;

        }

        $arrStopWords =& DV_GetStopWordList();

        //print_r($arrStopWords);

        //过滤所有HTML标签

        $str = preg_replace('#<[a-zA-Z]+?.*?>|#is', '', $str);

        //过滤所有stopword

        $str = str_replace($arrStopWords['StrRepl'],' ',$str);

        $str = preg_replace($arrStopWords['PregRepl'],' ',$str);

        //echo "\$str:{$str}<br />";

        $arr = explode(' ',$str);

//fcicq:好了,这下面的才是关键 *******************************************
        foreach( $arr as $tmpStr ){

                if ( preg_match("/^[\\x00-\\x7f]+$/i",$tmpStr) === 1 ) { //fcicq:这里过滤出来的全是E文/数字,没关系,mysql可以认识并正确分词.

                        $returnVal[] = ' '.$tmpStr;

                } else{ //fcicq:中英混合...

                        preg_match_all("/([a-zA-Z]+)/i", $tmpStr, $matches);

                        if( !empty($matches) ){ //fcicq:英语部分

                                foreach( $matches[0] as $matche ){

                                        $returnVal[] = $matche;

                                }

                        }

                        //过滤ASCII字符

                        $tmpStr = preg_replace("/([\\x00-\\x7f]+)/i", '', $tmpStr); //fcicq:你看,剩下的不就全是中文了?

                        $strLen = $objEnc->StrLength($tmpStr)-1;

                        for( $i = 0 ; $i < $strLen ; $i++ ){

                                $returnVal[] = $objEnc->SubString($tmpStr,$i,2); //fcicq:注意这里的substr,不是手册上的.
//fcicq:你仔细看,所有的词都是分成两个.
//比如"数据库的应用",会被分成数据 据库 库的 的应 应用...
//这分词自然是不怎么样的
//但是,搜索的时候同样这么做.
//比如搜索数据库,就相当于搜索了数据 据库.
//这是一种相当传统的分词方法.

                        }

                }

        }

        return $returnVal;

}//end function DV_ChineseWordSegment


//fcicq:这就是传说中的substr.偶相信phpx中许多人写出来的都比这个好.
        function &SubString(&$str,$start,$length=null){

                if( !is_numeric($start) ){

                        return false;

                }

                $strLen = strlen($str);

                if( $strLen < = 0 ){

                        return false;

                }

                if( $start < 0 || $length < 0 ){

                        $mbStrLen = $this->StrLength($str);

                } else{

                        $mbStrLen = $strLen;

                }

                if( !is_numeric($length) ){

                        $length = $mbStrLen;

                } elseif( $length < 0 ){

                        $length = $mbStrLen + $length - 1;

                }

                if( $start < 0 ){

                        $start = $mbStrLen + $start;

                }

                $returnVal = '';

                $mbStart = 0;

                $mbCount = 0;

                for( $i = 0 ; $i < $strLen ; $i++ ){

                        if( $mbCount >= $length ){

                                break;

                        }

                        $currOrd = ord($str{$i});

                        if( $mbStart >= $start ){

                                $returnVal .= $str{$i};

                                if( $currOrd > 0x7f ){

                                        $returnVal .= $str{$i+1}.$str{$i+2};

                                        $i += 2;

                                }

                                $mbCount++;

                        } elseif( $currOrd > 0x7f ){

                                $i += 2;

                        }

                        $mbStart++;

                }

                return $returnVal;

        }//end function SubString

//fcicq:插入分词表.一共两个,一个 topic_ft,一个bbs_ft.

        $arrTopicIndex =& DV_ChineseWordSegment($topic);

        if( !empty($arrTopicIndex) && is_array($arrTopicIndex) ){

                $topicindex = $db->escape_string(implode(' ',$arrTopicIndex));

                if( $topicindex !== '' ){

                        $db->query("UPDATE {$dv}topic_ft SET topicindex='{$topicindex}' WHERE topicid='{$RootID}'");

                } else{

                        $db->query("DELETE FROM {$dv}topic_ft WHERE topicid='{$RootID}'");

                        }

                }
        }

明白了吧?这就是所谓的分词
mysql不会分词,而dv会.就这么简单.
这虽然是一种比较过时的方法,但被dv这么一炒作就成了香饽饽.
很好理解的.

之后,mysql把这些分词的结果
implode(' ',$arrTopicIndex)
再分词(呵呵,数据,据库...),把这些词生硬的记住了.

下面回到mysql上来,下面是查询.

        $arrFTKeyWord =& DV_ChineseWordSegment($keyword);

        //$ftKeyWords = implode(' ',$arrFTKeyWord);

        $ftKeyWords = ''.implode(' ',$arrFTKeyWord);

//fcicq:中间省略了很多....

$stmt = "SELECT {$SQL_CACHE} /*SQL_CALC_FOUND_ROWS*/ ft.topicid FROM {$dv}topic_ft AS ft WHERE MATCH(ft.topicindex) AGAINST('{$ftKeyWords}' IN BOOLEAN MODE) ".($boardid>0?" AND ft.boardid='{$boardid}'":'')." AND ft.posttable='{$stable}'";
//fcicq:in boolean mode,能够保证每一条都查到.


        $TopicIDList = '0';

        if( $query = $db->query($stmt,array('absolutePage'=>$page,'pageSize'=>$pagesize,'debug'=>QUERY_DEBUG)) ){

                //$Record_Count = $db->scalar("SELECT FOUND_ROWS()");

                if( $Record_Count < = 0 ){ //fcicq:没有就输出错误.

                        head(1,0,0,$arrNavMenu);

                        showmsg($lang['str_42'].$lang['str_43']);

                        exit;

                }

                while( $tmpResult =& $db->fetch_row($query) ){ //fcicq:注意注意....这里就是保存刚才的id了.这里可以优化一下

                        $TopicIDList .= ",{$tmpResult[0]}";

                }

        }

        $PCount = ceil( (float)$Record_Count / (float)$pagesize );

//fcicq:上面进行分页预处理,不用管它.


        $__dbResults =& $db->getResultSet("SELECT {$SQL_CACHE} t.boardid,t.topicid AS rootid,t.title AS topic,t.expression,t.postusername AS username,t.postuserid,t.dateandtime,t.isbest,t.locktopic,t.child,t.hits,t.hidename FROM {$dv}topic AS t WHERE t.topicid IN({$TopicIDList}) ORDER BY t.topicid DESC");

       
//fcicq:这里就直接按主题的先后顺序(ORDER BY t.topicid DESC)进行了输出.

讲到这里大家都应该明白了吧,这就是最简单的分词方式.

//fcicq:下面是相关文章的检测***************************

$tmpTopicTitle = preg_replace('#^\[.*?]#i','',$TopicInfo['title']);

        $arrTopicTitle =& DV_ChineseWordSegment($tmpTopicTitle); //fcicq:把文章标题分词

        //echo implode(' ',$arrTopicTitle);

        if (is_array($arrTopicTitle) AND !empty($arrTopicTitle)) {

                //$topicIDList = '0';       

                $topicIDList = '';       

                $limitNumber = 5;

                //*

                $keyWordCount = 3;

                $stmt = '';

                $count = count($arrTopicTitle);

                if( $count > 1 ){ //fcicq:注意注意...如果分出的词大于1的话.

                        $stmt = '';

                        if( $count < = $keyWordCount ){

                                for( $i = 0 ; $i < $count ; $i++  ){  //fcicq:每一句写一个sql,然后用union select连接,这就是所谓的相关性

                                        if( $stmt !== '' ){

                                                $stmt .= "\r\n UNION \r\n";

                                        }

                                        $stmt .= "\r\n(SELECT {$SQL_CACHE} SQL_SMALL_RESULT ft.topicid FROM {$dv}topic_ft AS ft WHERE MATCH(ft.topicindex) AGAINST('{$arrTopicTitle[$i]}') AND (ft.boardid<>444 AND ft.locktopic=0) AND ft.posttable='{$TotalUseTable}' AND ft.topicid<>'{$TopicInfo['topicid']}' LIMIT {$limitNumber})\r\n";

                                }

                        } else{

                                $arrRand = array();

                                for( $i = 0 ; $i < $keyWordCount ; $i++  ){

                                        //$indexRand = rand(0,$count);

                                        $indexRand = array_rand($arrTopicTitle);

                                        if( isset($arrRand[$arrTopicTitle[$indexRand].'']) ){

                                                //$i--;

                                                continue;

                                        }

                                        $arrRand[$arrTopicTitle[$indexRand].''] = true;

                                        if( $stmt !== '' ){

                                                $stmt .= ' UNION ';

                                        }

                                        $stmt .= "\r\n(SELECT {$SQL_CACHE} SQL_SMALL_RESULT ft.topicid FROM {$dv}topic_ft AS ft WHERE MATCH(ft.topicindex) AGAINST('{$arrTopicTitle[$indexRand]}') AND (ft.boardid<>444 AND ft.locktopic=0) AND ft.posttable='{$TotalUseTable}' AND ft.topicid<>'{$TopicInfo['topicid']}' LIMIT {$limitNumber})\r\n";

                                }

                        }

                        $stmt .= "ORDER BY RAND()"; //fcicq:最后不忘了随机排序.

                } elseif( $count === 1 ){

                        $stmt = "SELECT {$SQL_CACHE} SQL_SMALL_RESULT ft.topicid FROM {$dv}topic_ft AS ft WHERE MATCH(ft.topicindex) AGAINST('{$arrTopicTitle[0]}') AND (ft.boardid<>444 AND ft.locktopic=0) AND ft.topicid<>'{$TopicInfo['topicid']}'";

                }

                if( $query = $db->query($stmt,array('pageSize'=>$limitNumber)) ){ //fcicq:取topicid.如果多余的话就取这个数(limitnumber)

                        $comma = '';

                        while( $tmpResult =& $db->fetch_row($query) ){

                                $topicIDList .= "{$comma}{$tmpResult[0]}";

                                $comma = ',';

                        }

                }

                if (!empty($topicIDList)) { //fcicq:最后进行一次标准查询.

                        $stmt = "SELECT {$SQL_CACHE} SQL_SMALL_RESULT  dt.topicid,dt.title,dt.boardid,dt.pollid,dt.locktopic,dt.child,dt.postusername,dt.postuserid,dt.dateandtime,dt.hits,dt.expression,dt.votetotal,dt.lastpost,dt.lastposttime,dt.istop,dt.isvote,dt.isbest,dt.posttable,dt.smsuserlist,dt.issmstopic,dt.lastsmstime,dt.topicmode,dt.mode,dt.getmoney,dt.usetools,dt.getmoneytype,dt.hidename FROM {$dv}topic AS dt WHERE dt.topicid IN({$topicIDList}) ORDER BY dt.topicid DESC";

                        $arrRelatedTopic =& $db->getResultSet($stmt,array('pageSize'=>$limitNumber));

                }

        }
//fcicq总结:
这里失去了相关性的查询功能.这种全文搜索偶一点也不提倡,除非有特殊需要.
最后的排序还是随机排序,虽然有利于收录,但是不是很好的办法.
相关性既然mysql有这个功能,就一定要用上. - 分词技术还是难点.

版权声明:fcicq原创,严禁转载.
这里BS转载偶关于pdo文章的网站,保留追究的权利.

如有问题请到blog上回复.http://www.fcicq.net/wp/?p=215

[ 本帖最后由 fcicq 于 2006-9-16 07:40 PM 编辑 ]





2006-9-16 07:39 PM#1
查看资料  访问主页  Blog  发短消息  顶部
 
LuckLrj (中国php中的爱因斯坦)
版主
Rank: 7Rank: 7Rank: 7
老会员


UID 64836
精华 0
积分 2266
帖子 3055
金钱 2266 喜悦币
威望 0
人脉 0
阅读权限 100
注册 2005-6-19
状态 离线
[推荐阅读] 怎么读取网页上特定的内容啊
顶。这个不错。



学习,工作累了,请访问我的小站,娱乐一下。http://www.52sunny.net
2006-9-16 08:23 PM#2
查看资料  Blog  发短消息  顶部
 
hivon (小P孩)
金牌会员
Rank: 6Rank: 6
风呼啦啦地飘


UID 28112
精华 0
积分 1097
帖子 644
金钱 735 喜悦币
威望 0
人脉 362
阅读权限 70
注册 2004-4-26
来自 广东深圳
状态 离线
[推荐阅读] 问题
我也要頂!
2006-9-16 09:44 PM#3
查看资料  访问主页  Blog  发短消息  QQ  顶部
 
fcicq
新手上路
Rank: 1
初级会员



UID 24467
精华 0
积分 21
帖子 587
金钱 20 喜悦币
威望 0
人脉 1
阅读权限 10
注册 2003-11-8
来自 fcicq.net
状态 离线
[推荐阅读] 新手请教!
提前了一天发.呵呵.谢楼上两位支持啦..




2006-9-16 09:58 PM#4
查看资料  访问主页  Blog  发短消息  顶部
 
只爱一个人 (只爱一个人)
论坛元老
Rank: 8Rank: 8
超级管理员



UID 29639
精华 0
积分 3815
帖子 1604
金钱 3815 喜悦币
威望 0
人脉 0
阅读权限 90
注册 2004-6-26
状态 离线
[推荐阅读] 如何在PHP里实现文件的批量下载?
不错的文章,做个记号,反正全文搜索这东西有用
2006-9-17 02:08 AM#5
查看资料  访问主页  Blog  发短消息  顶部
 
fcicq
新手上路
Rank: 1
初级会员



UID 24467
精华 0
积分 21
帖子 587
金钱 20 喜悦币
威望 0
人脉 1
阅读权限 10
注册 2003-11-8
来自 fcicq.net
状态 离线
[推荐阅读] 位运算符
再说两句
全文搜索方面这里介绍的太不清楚了.
具体的可以参见奶瓶关于全文搜索的文章.
这个根本就没有深入底层.





2006-9-17 12:10 PM#6
查看资料  访问主页  Blog  发短消息  顶部
 
游戏人间
版主
Rank: 7Rank: 7Rank: 7
风云山庄大少爷


UID 62639
精华 2
积分 14338
帖子 7211
金钱 14198 喜悦币
威望 20
人脉 120
阅读权限 100
注册 2004-12-21
来自 广西人在北京
状态 离线
[推荐阅读] 请教一个程序的问题?
全文搜索,N大的一个问题,留个脚印.



 
群号:6025396/6025252/19520091(广西PHP交流/PHP高级编程/算法交流)、饿踢Blog

寧可在嘗試中失敗,也不在保守中成功!

不為失敗找理由,只為成功找方法!
 
2006-9-18 01:15 AM#7
查看资料  访问主页  Blog  发短消息  顶部
 
wuwei517200
注册会员
Rank: 2
初级会员



UID 65500
精华 0
积分 106
帖子 160
金钱 106 喜悦币
威望 0
人脉 0
阅读权限 20
注册 2005-8-4
来自 广东河源
状态 离线
[推荐阅读] 点击复选框提交问题
看DEDECMS吧!!
2006-9-18 10:27 AM#8
查看资料  发短消息  顶部
 
qh663
版主
Rank: 7Rank: 7Rank: 7
中级会员


UID 30020
精华 0
积分 546
帖子 580
金钱 546 喜悦币
威望 0
人脉 0
阅读权限 100
注册 2004-7-13
来自 河源
状态 离线
[推荐阅读] 图片地址的传递???
好长啊,占位,以后慢慢看……



五百多篇PHP学习资料
喜悦村BLOG
php团队接兼职项目QQ:80031807
PHP群:768844
2006-9-18 06:58 PM#9
查看资料  访问主页  Blog  发短消息  QQ  顶部
 
phphp
版主
Rank: 7Rank: 7Rank: 7
加班一族


UID 65188
精华 1
积分 182
帖子 3005
金钱 171 喜悦币
威望 10
人脉 1
阅读权限 100
注册 2005-7-20
来自 北京市海淀区
状态 离线
[推荐阅读] 发布一个表单各元素格式检测代码
哪篇PDO啊? BS就算了吧,影响感情。
看来下次复制东西的时候要小心了

[ 本帖最后由 phphp 于 2006-9-20 11:44 AM 编辑 ]




书到用时方恨少, 事非经过不知难
2006-9-20 11:42 AM#10
查看资料  访问主页  Blog  发短消息  QQ  顶部
 22  1/3  1  2  3  > 
     


  可打印版本 | 推荐给朋友 | 订阅主题 | 收藏主题 | 开通个人空间  


 


Powered by Discuz! 6.1.0  © 2001-2010 Comsenz Inc.
Processed in 0.035516 second(s), 6 queries

(冀ICP备05009913号) 管理员:sadly 邮箱/MSN: sadly@phpx.com QQ:824008(长隐) 清除 Cookies - - Archiver - WAP