头疼的发现
最近在做一个网站的时候,要用到PHPCMS的搜索功能。发现一个问题,如果我想搜索的东西是英文或者数字,那如果是在标题中出现,可以正常搜索到。但是如果是在模型中自行添加的字段,怎么也搜索不到。这个事情一直没想通,因为在设置模型字段时,还特别有一项就是可以选择是否作为搜索条件。按理来说,选了这项就应该能搜到啊,可是phpcms是怎么也搜不到。我要搜索的东西,是类似英文型号的一段字符,比如:“HK-3001”,后来测试,如果不写英文或者数字,全中文的填写字段内容,是可以搜到的。结果挠破了头也没想通啊……于是,只能深入研究一下phpcms的搜索原理了。不研究不知道,研究以后,真是奇葩!
奇葩的原理
经过查询得知,phpcms使用了一项分词搜索技术。我智商确实有限的,我一直没听过分词搜索这个东西。举个通俗的例子来理解:
【假设查询】要查询的关键词为:“馒头是谁发明的”。
【假设数据】系统中存在一条数据:“有没有想过馒头是谁发明的?馒头是诸葛亮发明的。”
【常规方法】如果常规的搜索方法,一般来说就是直接进行标题或者全文的模糊匹配,匹配结果:有没有想过馒头是谁发明的?馒头是诸葛亮发明的。
【分词方法】先分词,再匹配。首先,已经存在系统中的数据要分词存储一下,分词结果类似:“馒头 发明 诸葛亮”,然后再把搜索的关键词分词,分词结果类似:“馒头”。最后进行模糊匹配:“馒头 发明 诸葛亮”。
这是一种相当智慧的查询方法,分词以后,数据库中不再需要存储大量备用的查询数据。但问题也是显而易见的,那就是必须要保证分词要很准确才行。很遗憾,phpcms的分词称为中文分词,他只对中文有效,甚至想到了把中文自动转换成拼音进行查询。但如果存储的是ABCD或者1234,那就忽略掉了!我只能用奇葩来形容,如此专注于中文搜索研究,我很想给盛大点个赞。但是从实用性来讲,我不敢苟同这种方式。
无奈的修改
如果想绕过分词搜索,那么就要在两个方面下手。一个方面是在存储系统数据时,将存储分词后的结果,改为存储原数据。另一方面要在进行搜索时,将搜索分词后的结果,改为搜索原数据。
首先,找到phpcms处理中文分词操作类,路径在:
/phpcms/libs/classes/segment.class.php
这个类很复杂,我看不懂,但是有一个关键的函数叫做get_keyword,我们要做的就是把这个函数的所有处理都删掉,直接返回原字符串。也就是改为:
function get_keyword($str,$ilen=-1) { return $str; }
分词时,会把不同字段的值,默认用空格分割,我这里不想分割,所以改动了类中前几行有一个叫做$split_char的变量,作者有注释,按需调整
public $split_char = ''; //分隔符
下一步要修改搜索时的分词,文件路径:
/phpcms/modules/search/index.php
把分词的部分删掉,从“$segment = new segment();”开始,改为:
$segment = new segment(); //分词结果 //$segment_q = $segment->get_keyword($segment->split_result($q)); //如果分词结果为空 //if(!empty($segment_q)) { // $sql = "`siteid`= '$siteid' AND `typeid` = '$typeid' $sql_time AND MATCH (`data`) AGAINST ('$segment_q' IN BOOLEAN MODE)"; //} else { $sql = "`siteid`= '$siteid' AND `typeid` = '$typeid' $sql_time AND `data` like '%$q%'"; //} //echo $sql; $result = $this->db->listinfo($sql, 'searchid DESC', $page, 10);
如果把注释也删掉,那么就是:
$segment = new segment(); $sql = "`siteid`= '$siteid' AND `typeid` = '$typeid' $sql_time AND `data` like '%$q%'"; $result = $this->db->listinfo($sql, 'searchid DESC', $page, 10);
也就是说判断神马的都删掉了,只留一个$sql语句。这样处理以后,也就绕过了分词搜索。
原来的分词查询流程:
数据输入-> 分词存储在(phpcms)_search表的`data`字段中 -> 查询输入 -> 取得分词关键字 -> 查询(phpcms)_search表
修改好以后的查询流程:
数据输入-> 所有字符串完整存储在(phpcms)_search表的`data`字段中 -> 查询输入 -> 直接模糊查询(phpcms)_search表