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

872 阅读2分钟

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

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

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

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

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

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

那下面来说说拼音搜索的实现,要实现拼音搜索,就要先将中文翻译成拼音。考虑到多音字的情况,我将所有可能性的拼音都建进sphinx的索引里了。这里使用的是我开源的ChineseUtil(github.com/Yurunsoft/C…)。比如将“我的”转换为拼音后,得到两个结果:“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);
}

本文仅提供一种思路,并不代表是最佳解决方案。代码单纯复制粘贴无用,仅作参考!