用Sphinx 建立搜索引擎

1.       介绍

实际上 sphinx的网站上的title 说的很清楚,这个是一个 “免费开源的SQL 全文索引搜索引擎”。当然,它不是一个完整的搜索引擎,只提供索引 查询接口。所以,学习sphinx 主要是要学习:如何建立索引,如何调用查询接口。

他的作者只有一个人,但是,功能的确非常强大。目前,支持下面的特性:

高速索引(10M/s, 主流cpu配置)

高速查询(2-4G 文本,大概只要0.1s)

排序采用的BM25 短语相似度 相结合的排序方法,而非向量空间模型。

提供分布式搜索功能

灵活的查询接口,支持布尔、短语、词语相似度等多种检索模式;

可以支持单字节(GBK)和 utf8 编码的数据源

当然,sphinx 也有很多的缺点:

1.       分词比较难搞定,sphinx C 写的,要嵌入分词算法,还是要做很多的工作。

一般现在用分词不是很准确的 LibMMSeg 进行切分。当然,分词不准确一般不会非常的影响搜索的体验。所以没有必要过于强调这一点。如果你要做的不是一个非常精致的搜索,sphinx 肯定是够用了。

 

2.       大多数开源搜索都基本上基于纯文本搜索的理论。并没有对文本的结构进行深入的挖掘。比如pagerankhtml 标签标定的关键字,锚文字 等。如果,你要做一个比较好的垂直搜索引擎,可以考虑引入pagerank的机制,当然,存C sphinx 肯定会降低你的开发效率。

说了这样多,只是想说和lucunece 相比,可扩展性差点。但是效率非常高。
 

2.       安装
  http://faq.phpwind.net/answer-567

上面这个链接中有一个很好的教程,一步一步的做下去就能安装成功了。下面补充一点东西:

 

安装searchd 服务要用下面的命令:

Searchd -–install –c /path/to/config

注意 install 前面是两个 -”,c前面是一个 -

 

还要一点要注意配置一个 stopwords 。方法是,建立一个stopwords 的列表,每个字用空格分开来,然后在每个index 的选项里面加一个 stopwords = /path/to/stopwords.txt

 

3.       索引

索引的技巧也在 http://faq.phpwind.net/answer-567 有一个例子,这个是一个典型的增量索引的例子。当然,增量索引的配置方法不只是一种。下面谈谈怎么更新增量索引。

 

1.       10分钟,建立一次增量索引。千万不要每10分钟就合并一次索引,实际上,合并索引也非常的消耗时间。

2.       每天的晚上可以更新全部的索引。

增量索引不能反映删除 修改的内容,所以,建议主索引要经常的更新。一般来说,对于100万数据左右的情况,这样的方法都是可以的。

 

但是,如果数据量还要大的话,就采用分段索引。一个索引 是基本上不会改动的索引,比如,一年前的帖子了。一个索引是最近一年的帖子,最后一个索引是增量索引。虽然这样,会同时 要从三个索引里面搜索,影响了性能,但是,可以保证近期的比如 10W数据 很容易的被更新。而一年前的数据可以考虑一个月索引更新一次。对三个索引,还可以设置权重,增量索引设置最大,因为是最新的内容,往往是热点,大家比较关注的,可以靠前点。近一年的数据,排名第二,最后一年以外的数据,基本过时,可以排后面一些。

 

 

4.       搜索

搜索其实从手册中已经很明白的说明了。

下面要谈一下可能会出现问题的地方:

 

1.搜索的模式:

一般来说,比较常用的两种模式是:SPH_MATCH_ALL SPH_MATCH_ANY 但是 coreseek 分装的中文搜索有比较严重的问题,特别是 SPH_MATCH_ANY 是有bug的(我在GBK模式下发现是有bug的,这里有讨论:http://www.coreseek.cn/forum/2_171_0.html ),所以我选择用:SPH_MATCH_EXTENDED 模式来代替。如果是或者的关系,可以这样做:

buildKeywords 先分词,然后再组合成一个查询表达式,进行查询。详细的看后面代码中的例子。

 

   2.搜索索引的参数:
   query 函数允许传递多个索引,不同的索引用 “|” 分开,但是,加亮函数只要传递主索引就好了,千万不要传多个。否则会返回false 。加亮函数中,还会传递一个关键字,如果是多个,就用 | 隔开。

下面是我为verycms 写的一个简单的 搜索调用接口,提供大家参考:

<?php
class Cms_Search_API
{
    
private $host;

    
private $port;

    
private $db;

    
private $indexs;

    
private $searchd;

    
private $keywords;

    
private $total;

    
function __construct($db = null, $host = 'localhost', $port = 3312, $indexs = "cmsindex|addcmsindex")
    {
        
if ($db === null) {
            
global $db;
        }
        
$this->host = $host;
        
$this->port = $port;
        
$this->db = $db;
        
$this->indexs = $indexs;
        
$index = explode("|", $this->indexs);
        
$this->mainIndex = $index[0];
        
$this->searchd = new SphinxClient();
        
$this->searchd->setServer($this->host, $this->port);
        
$this->searchd->setMatchMode(SPH_MATCH_EXTENDED);
    }

    
function setLimits($page = 1, $limit = 10)
    {
        
$offset = ($page - 1* $limit;
        
$this->searchd->setLimits($offset, $limit, 1000);
        
return $this;
    }
    
    
function setWhere($field, $value)
    {
        
if (!is_array($value)) {
            
$value = array($value);
        }
        
$this->searchd->setFilter($field, $value);
        
return $this;
    }

    
/**
     * set how to sort the search result.
     *
     * @param int $mode SPH_SORT_ATTR_DESC or SPH_SORT_ATTR_ASC
     * @param string $sortby sort field.
     
*/
    
function setSortMode($sortby = 'postdate', $mode = SPH_SORT_ATTR_DESC)
    {
        
$this->searchd->setSortMode(SPH_SORT_ATTR_DESC, $sortby);
        
return $this;
    }

    
function find($query)
    {
        
$result = $this->searchd->query($this->prepare($query), $this->indexs);
        
$ids = array_keys($result['matches']);
        
unset($result['matches']);
        
$this->total = $result['total'];
        
if (empty($ids)) {
            
return false;
        }
        
$data = $this->fetchContents($ids);
        
return  $data;
    }
    
    
function getTotal()
    {
        
return $this->total;
    }
    
    
function getList($query, $page = 1, $sortby = null, $limit = 10, $sort_mode = SPH_SORT_ATTR_DESC)
    {
        
$this->setLimits($page);
        
if ($sortby) {
            
$sort_mode = empty($sort_mode? SPH_SORT_ATTR_DESC : $sort_mode;
            
$this->setSortMode($sortby, $sort_mode);
        }
        
if ($result = $this->find($query)) {
            
$this->buildExcerpts($result, "content");
            
$this->buildExcerpts($result, "title");
        }
        
return $result;
    }

    
function buildExcerpts(&$data, $field = "content")
    {
        
$contents = array();
        
foreach ($data as $item)
        {
            
$item = trim(strip_tags($item[$field]));
            
$contents[] = preg_replace("/\s+/", "", $item);
        }
        
$contents = $this->searchd->buildExcerpts($contents, $this->mainIndex, $this->keywords);
        
foreach ($data as $k => &$v) {
            
$v[$field= $contents[$k];
        }
        
return $data;
    }

    
/**
     * fetch content from database;
     *
     * @param array $ids  tids
     * @return array  search result
     
*/
    
private function fetchContents($ids)
    {
        
$q = "SELECT th.tid,th.cid, th.title, th.postdate, th.hits, t.content, t.author, th.linkurl , th.url 
                FROM cms_contentindex th
                LEFT JOIN cms_content1 t
                USING (tid)
                WHERE th.tid in (
" . implode(",", $ids. ")";
        
        
$result = $this->db->query($q);
        
$data = array();
        
        
$score = array_flip($ids);
        
$score_sort = array();
        
while ($line = $this->db->fetch_array($result))
        {
            
$line['score'= $score[$line['tid']];
            
$data[] = $line;
            
$score_sort[] = $line['score'];
        }
        
array_multisort($score_sort, SORT_ASC, SORT_NUMERIC, $data);
        
return $data;
    }

    
/**
     * prepare the search query, and then use SPH_MATCH_EXTENDED mode to query.
     *
     * @param string $query string to search
     * @return string of extend style.
     
*/
    
private function prepare($query)
    {
        
$keywords = $this->searchd->buildKeywords($query, $this->mainIndex, false);
        
$query = array();
        
foreach ($keywords as $key) {
            
$query[] = $key["tokenized"];
        }
        
        
$query = implode("|", $query);
        
$query = iconv("utf-8", "gbk", $query);
        
$this->keywords = $query;
        
return $query;
    }
}
?>

下面是调用的例子:

<?php
require_once "global.php";
require_once "require/cms_search_api.php";

$search = new Cms_Search_API($db);
$page = 1;
$sortby = 'postdate'//hits or null
$search->setWhere("cid", 5);
$result = $search->getList("提升6-12个月宝宝记忆力的游戏", $page, $sortby);
print_r($result);
?>

 

posted @ 2009-10-13 17:13  暮夏  阅读(2373)  评论(5编辑  收藏  举报