【迅搜15】搜索技巧(五)其它功能

2024-01-09 15:21:09 浏览数 (1)

搜索技巧(五)其它功能

又是收尾阶段了,其实在搜索,也就是 XSSearch 这个对象中,剩下的属性方法已经不多了。很多方法和属性在之前的学习中我们都已经使用过或者接触过了,剩下的都是一些锦上添花的功能,但往往这类的功能,却又有着各种不同的惊喜。好吧,到底是惊喜还是惊吓,咱们看完再说吧。

属性

XSSearch 中的很多属性之前已经用过了,而且有一部分也是封装的属性,和很多 get、set 类方法是通用的。咱们再来一个一个看一下吧。

所有的同义词

代码语言:javascript复制
print_r($search->allSynonyms);

这个属性对应的是就是我们在同义词的学习中,使用过的 getAllSynonyms() 方法,要注意的是它是只读属性,只有这一个对应的方法,没有对应的 set 相关方法。

纠正词属性

代码语言:javascript复制
$search->query = 'sf';
print_r($search->correctedQuery);

correctedQuery 属性对应的是 getCorrectedQuery() 方法,同样也是一个只读属性。

分面

代码语言:javascript复制
$search->facets = ['category_name'];
$search->search('算法');
print_r($search->facets);

从代码上就可以看出了,它对应的是 setFacets() 和 getFacets() 方法,是可读可写的。

热门词

代码语言:javascript复制
print_r($search->hotQuery);

这个也不用多解释了吧,对应着 getHotQuery() 方法,是一个只读属性。

相关搜索词

代码语言:javascript复制
print_r($search->relatedQuery);

这个对应的是 getRelatedQuery() ,获取相关搜索词,很明显,它也是一个只读属性。

剩下的还有 dbTotal、lastCount 这两个属性,之前我们都用过,一个是返回所有的文档数量,一个是返回上次查询结果的文档数量。它们两个也是只读的,分别对应 getDbTotal() 和 getLastCount() 这两个方法。

方法

方法主要就是体现在各种功能上了。还有一些小功能非常有意思,但也有一些我没测出来效果,这里都放出来大家也可以自己尝试下。

剔除参数

setCutOff() 方法,设置百分比/权重剔除参数 通常是在开启 setFuzzy() 或使用 OR 连接搜索语句时才需要设置此项。

代码语言:javascript复制
echo $search->count('算法'), PHP_EOL; // 63
echo $search->setCutOff(95)->count('算法'), PHP_EOL; // 12
echo $search->setCutOff(95,2.821)->count('算法'), PHP_EOL; // 7
$search->setCutOff(0,0);

看出来上面的代码是什么意思了吗?setCutOff() 的第一个参数是返回的元数据中的 percent ,这里写得 95 就是表示百分比小于 95 的,过滤掉。第二个参数表示权重 weight ,同样也是对应返回文档的元数据中的值,表示小于指定的值的也过滤掉。

因此,上面第一行代码是全部的数据,和算法有关的是 63 条;第二行加了 95 的百分比剔除后,还剩 12 条;最后再加上权重 weight 小于 2.821 的也剔除掉,只剩 7 条数据了。关于百分比和权重的概念,之前也已经解释过了。

注意,setCutOff() 是作用到 XSSearch 对象的,后续还有别的操作时要把它们归零,表示不使用 setCutOff() 功能,要不会继续影响后续的搜索。如果你只是一次查询就没必要了。这一块内容之前也说过了,和 setFuzzy() 要还原的原因是一样的。

设置查询字符集

使用 setCharset() 可以设置查询字符串的字符编码格式,默认是根据 XSIndex 的设置,但我们可以手动指定。

代码语言:javascript复制
print_r($search->setCharset('gb2312')->search('算法'));
// Array
// (
// )
$search->setCharset('utf8');

上面的代码设置成 GB2312 之后就查不到内容了,查询语句乱码了嘛。注意这里如果后续还有查询操作,要将它重置回来。

按入库顺序排序

这里按入库顺序排序的意思就是根据元数据的那个真实唯一并自增长的 id 来排序了,使用 setDocOrder() 这个方法。

代码语言:javascript复制
echo $search->setDocOrder()->search('算法')[0]->id, PHP_EOL; // 1
echo $search->setDocOrder(true)->search('算法')[0]->id, PHP_EOL; // 1

echo $search->setDocOrder()->search()[0]->id, PHP_EOL; // 1
echo $search->setDocOrder(true)->search()[0]->id, PHP_EOL; // 844

这里有两段测试,主要是体现一个问题,那就是这个方法的优先级是低于权重评分的。因此,如果是带关键词的话,那么还是以关键词权重评分为主,所以前两条搜索语句返回的结果是一样的,而后面不带搜索词的则会有不同。方法的参数 false 表示正序,true 表示逆序。

匹配关键词

这个匹配关键词的意思就是搜索结束后,在返回文档的元数据中通过一个特殊的 matched 属性,展示这个文档所匹配到的关键词内容,使用 setRequireMatchedTerm() 方法开启这个功能。

代码语言:javascript复制
$docs = $search->setRequireMatchedTerm()->setLimit(1000)->setFuzzy()->setQuery('数据结构与算法')->search();
foreach($docs as $d){
  print_r($d->matched());
}
// Array
// (
//     [0] => 数据结构
//     [1] => 与
//     [2] => 算法
//     [3] => 数据
//     [4] => 结构
// )
// …………………………
// …………………………
// Array
// (
//     [0] => 与
//     [1] => 数据
// )
$search->setRequireMatchedTerm(false);
$search->setFuzzy(false);

在之前学习 XSDocument 时,我们没有看到这个 matched 属性,因为它是需要开启之后才会返回的。在这里测试的是 setFuzzy() 模糊的结果,这样我们的测试能看得更加清晰。比如说最后一条数据只是匹配到两个词了,也就是说,在这个文档中,只出现了“与”和“数据”这两个关键词。

设置分词复合等级

之前在索引相关的操作中也提到过这个分词复合等级,更详细的内容我们在后面分词相关的部分再详细学习。现在大家只要知道有个 setScwsMulti() 方法可以设置就可以了。

代码语言:javascript复制
echo $search->setScwsMulti(15)->setQuery('我爱北京天安门,天安门上太阳升')->getQuery(), PHP_EOL;
// Query((我爱@1 AND 北京@2 AND (天安门@3 SYNONYM (天安@80 AND 安门@81)) AND 天安@4 AND 门上@5 AND (太阳升@6 SYNONYM (太阳@83 AND 阳升@84))))

echo $search->setScwsMulti(1)->setQuery('我爱北京天安门,天安门上太阳升')->getQuery(), PHP_EOL;
// Query((我爱@1 AND 北京@2 AND (天安门@3 SYNONYM 天安@80) AND 天安@4 AND 门上@5 AND (太阳升@6 SYNONYM 太阳@83)))

明显能看出来查询分析返回的结果不一样吧,分词复合等级可以设置 1-15 的值。

同义词搜索权重比例

这个功能我没试出来效果,使用的是 setSynonymScale() 方法。

代码语言:javascript复制
print_r($search->setFuzzy()->setAutoSynonyms()->setQuery('最好')->search());
print_r($search->setAutoSynonyms()->setSynonymScale(0.01)->setQuery('最好')->search());
// 没效果

按字面意思来说,是针对同义词匹配上之后修改它的权重占比。不过怎么设置,返回的数据顺序都是一样的。希望有用过或者了解的小伙伴可以评论区留言一起学习哈。

权重评分方案

这个就是修改评分规则,还记得我们之前学过的评分算法吧,默认 Xapian 是 BM25 。如果不记得的小伙伴要回去再复习一下哦,这里使用 setWeightingScheme() 方法可以对它进行修改。

代码语言:javascript复制
echo $search->setWeightingScheme(0)->search('敏捷')[0]->id, PHP_EOL; // 238
echo $search->setWeightingScheme(1)->search('敏捷')[0]->id, PHP_EOL; // 234
echo $search->setWeightingScheme(2)->search('敏捷')[0]->id, PHP_EOL; // 234

只有三个常量,默认 0 表示使用 BM25;1 表示使用 Bool 规则;2 表示使用 Trad 规则。Trad 是 traditional 的缩写,表示 Xapian 的传统评分方案,就是 TF-IDF 方案。而 Bool 是什么就没找到相关的资料了。

从上面的测试结果可以看出,排序结果的顺序是不同的,第一条数据的 id 不一样。

查询匹配词

这个功能就是返回本次或上次 setQuery() 的分词结果,也就是可以被高亮的关键词信息。

代码语言:javascript复制
print_r($search->terms()); 
// Array
// (
//     [0] => 敏捷
//     [1] => 最好
//     [2] => 最强
//     [3] => 最棒
// )

print_r($search->setQuery('数据结构与算法')->terms()); 
// Array
// (
//     [0] => 数据结构
//     [1] => 与
//     [2] => 算法
//     [3] => 数据
//     [4] => 结构
// )

没啥多说的,也可以做为 getQuery() 的代替品,如果只是看分词结果的话。

地理位置功能

地理位置相关的搜索功能,在 Redis 中我们其实已经学过了,另外在 MongoDB 中也早就有了。而在搜索引擎方面,Sphinex 和 ES 也都有相关的地理位置索引。但是,在 XS 中,没有这种索引。不过,XS 使用了另外一种方式,实现了一个非常简单的地理位置功能,就是通过指定两个数字类型的字段,分别代表经纬度,然后通过后台计算,返回按距离远近排序的文档效果。

我们需要先准备测试数据,不想新建 ini 配置文件,所以我偷懒了,直接动态配置 Scheme ,也顺便复习一下之前学习过的内容。

代码语言:javascript复制
$xs = new XS("./config/zyarticle.ini");
$search = $xs->search;

$scheme = $xs->scheme;
$scheme->addField(new XSFieldMeta('lon', ['type'=>'numeric']));
$scheme->addField(new XSFieldMeta('lat', ['type'=>'numeric']));

$xs->index->clean();
$xs->index->add(new XSDocument([
  'id'=>uniqid(),
  'title'=>'五一广场',
  'content'=>'五一广场',
  'lon'=>112.983037,
  'lat'=>28.198986,
]));
$xs->index->add(new XSDocument([
  'id'=>uniqid(),
  'title'=>'长沙火车站',
  'content'=>'长沙火车站',
  'lon'=>113.017496,
  'lat'=>28.199495,
]));
$xs->index->add(new XSDocument([
  'id'=>uniqid(),
  'title'=>'南门口',
  'content'=>'南门口',
  'lon'=>112.982875,
  'lat'=>28.188591,
]));
$xs->index->flushIndex();

初始化数据分为两个部分,第一部分获取各实例对象,然后手动添加 Scheme ,其实就是定义两个字段。注意,字段名可以任意,但一般会按标准使用 longitude 和 latitude ,或者它们的缩写。字段必须是 numeric 类型,索引方式和其它属性就无所谓了。第二部分就是插入数据,我这里的数据是长沙的几个地标,不熟悉的同学可以换成自己所在城市的地标。然后清理之前的数据,添加新的数据,最后强制刷新一下让数据快点生效。

准备数据完成之后,就可以来测试效果了。根据指定坐标返回远近其实就是一个排序的效果,使用的是 setGeodistSort() 方法。

代码语言:javascript复制
 // 长沙南站    长沙火车站->南门口->五一广场
print_r($search->setGeodistSort(['lon'=>113.071808,'lat'=>28.153261])->setQuery('')->search());
// 黄兴广场    五一广场->南门口->长沙火车站
print_r($search->setGeodistSort(['lon'=>112.982947,'lat'=>28.195389])->setQuery('')->search());
// 贺龙体育馆  南门口->五一广场>长沙火车站
print_r($search->setGeodistSort(['lon'=>112.989442,'lat'=>28.183982])->setQuery('')->search());

可以看到,我们使用长沙南站、黄兴广场、贺龙体育馆三个地标与库中的数据进行比对,默认情况下按由近到远的顺序返回结果,上述测试代码的返回结果顺序都是对的。

这个 setGeodistSort() 方法的第一个参数是一对坐标,是我们的起始地点,也就是要与库中比对的地点坐标。注意这个数组的键要与文档的坐标字段的名称相同。它还有第二个参数,是以正序还是倒序返回的配置,默认是 false ,表示由近到远,如果改成 true ,就变成由远到近。第三个参数是是否让相关度评分排序优先,也就是以评分排序为主,这个一般保持默认的 false 就好了,要不上面的远近功能就没用了,还是以关键词的优先为主了。

好了,这就是 XS 的地理位置功能了,相比 ES 和 MongoDB 之类的有 GEO 索引的数据库肯定还是简单很多了,而且没有具体的距离数据信息,不过这个距离数据我们也可以自己实现,还记得很早的时候我们学过的 XS 对象中的 geoDistance() 方法吗?

代码语言:javascript复制
$helong = ['lon'=>112.989442,'lat'=>28.183982];
$docs = $search->setGeodistSort($helong)->setQuery('')->search();
foreach($docs as $d){
  $dis = XS::geoDistance($helong['lon'], $helong['lat'], $d->lon, $d->lat);
  echo '贺龙体育馆距离 '.$d->title.' '.$dis.' 米! ', PHP_EOL;
}
// 贺龙体育馆距离 南门口 822.2271880921 米! 
// 贺龙体育馆距离 五一广场 1781.4304737794 米! 
// 贺龙体育馆距离 长沙火车站 3243.6888814142 米! 

说实话,到这一步,其实在大部分 Geo 相关的简单应用我们也已经可以通过 XS 实现了。是不是很惊喜,官方文档对这一块基本没有什么说明哦,这不点值得点个赞?

总结

今天的内容不算多吧,但是确实是有惊喜的吧?地理位置功能、修改评分方案、匹配词信息、剔除方法,这些功能说不定什么时候你就会用上。而且更重要的是,就像讲地理位置功能时说的,这些内容在官方文档上只有一两句话的解释,但是咱们今天可是一一都试过了哦,很多人可能都不知道有这些功能呢。

好了,到此为止,XS 中搜索相关的内容,其实也就是 XSSearch 对象的相关功能就全部学习完成了。在这一部分,我们不仅学习到了各种搜索功能的使用,更重要的是我们还学习到了评分排序相关的内容,这一块可是所有搜索引擎工具通用的哦。另外搜索日志库提供的功能是意外惊喜,使用非常简单方便,是不是感觉 XS 还是有它自身的许多独特优势的。接下来,我们将继续学习分词部分的内容,也是我们最后一个大知识点了哦。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/xunsearch/source/15.php

参考文档:

http://www.xunsearch.com/doc/php/api/XSSearch

0 人点赞