用 Sphinx 简单实现无插件的中文搜索+拼音搜索

市面上的全文搜索引擎非常多,但通常想要支持中文搜索和拼音搜索都挺费力的。又是下载编译安装,又是装插件扩展,挺烦的。

我用 Sphinx 研究出了一种可以不用任何扩展,就能支持中文搜索+拼音搜索的解决方案。性能没差,效果也不差,一般不追求完美的已经足够了。

通常搜索引擎因为都是老外开发的,而欧美国家的词语,一般都是用空格分隔,所以很好做分词。而亚洲文字就比较尴尬了,连续的并没有分隔符,这样文字的分词就是一个大问题。

所以想要让搜索引擎支持中文搜索,首先要解决分词问题。比如我想到的就是把一串中文转为英文,存入文本索引,这样搜索引擎就可以识别了。

/**
 * 编码sphinx中文存储
 * @param string $str
 * @return string
 */
function encodeSphinxChinese($str)
{
    return str_replace('\\', '%', substr(json_encode($str), 1, -1));
}

如上代码,我是用的将字符串转为 unicode 格式,sphinx建索引和搜索时候,都要将搜索词编码后才可以匹配结果。

那下面来说说拼音搜索的实现,要实现拼音搜索,就要先将中文翻译成拼音。考虑到多音字的情况,我将所有可能性的拼音都建进sphinx的索引里了。这里使用的是我开源的ChineseUtil(https://github.com/Yurunsoft/ChineseUtil)。

比如将“我的”转换为拼音后,得到两个结果:“wo de”和“wo di”,将他们都存入文本索引。

拼音索引问题解决,那接下来就是搜索的时候了。用户搜索拼音,不可能给你按空格,很有可能输入的是“wode”,那这时候是匹配不到的。这时候需要进行拼音分词。

比如用户输入“xianggang”,拼音分词后得到2个结果:“xi ang gang”和“xiang gang”,这时候处理搜索接口时候,需要将这两个词都作为搜索词。

然后通过PHP代码,将返回的搜索结果进行合并和权重排序。

$sc = new SphinxClient;
// 这里做一些其它事,省略
$results = $sc->RunQueries();
if(false === $results)
{
	throw new Exception(mb_convert_encoding($sc->GetLastError(), 'UTF-8', 'GBK'));
}
$totalFound = 0;
$matches = array();
foreach($results as $item)
{
	if(!empty($item['error']))
	{
		throw new Exception($item['error']);
	}
	$totalFound += $item['total_found'];
	if(!empty($item['matches']))
	{
		foreach($item['matches'] as $id => $match)
		{
			if(!isset($matches[$id]))
			{
				$matches[$id] = [];
			}
			if(isset($matches[$id]['count']))
			{
				++$matches[$id]['count'];
			}
			else
			{
				$matches[$id]['count'] = 2;
			}
			$matches[$id]['weight'] += $match['weight'];
		}
	}
}
unset($results);
uasort($matches, function(&$a, &$b){
	if(isset($a['count']))
	{
		$a['weight'] /= $a['count'];
		unset($a['count']);
	}
	if(isset($b['count']))
	{
		$b['weight'] /= $b['count'];
		unset($b['count']);
	}
	if($a['weight'] === $b['weight'])
	{
		return 0;
	}
	else
	{
		return $a['weight'] > $b['weight'] ? -1 : 1;
	}
});
$matches = array_keys($matches);
if(null === $limit)
{
	return $matches;
}
else
{
	return array_splice($matches, 0, $limit);
}

最终的查询结果,不追求完美的也可以了,当然还有一些不是很严重的缺陷。本文仅提供一种思路,并不代表是最佳解决方案。代码单纯复制粘贴无用,仅作参考!

  • 相关文章
  • 发表评论
当前用户:
  • 评论列表