首页技术文档Linux › 用Sphinx 建立搜索引擎

用Sphinx 建立搜索引擎

1. 介绍

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

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

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

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

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

n 提供分布式搜索功能

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

n 可以支持单字节(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);
?>

本站技术交流群:24735919,欢迎大家进群交流探讨!

发表评论