Blogger Information
Blog 13
fans 1
comment 1
visits 18032
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
使用sphinx search打造你自己的中文搜索引擎
小猿猿er
Original
2013 people have browsed it

为了有一个更好的阅读体验请移步至我的技术博客:猿人学

Google搜索引擎建立至今已经快20年了,之后全球各类大大小小类似的搜索引擎也陆续出现、消亡。国内目前以百度为大,搜狗、360、必应等也势在必争。搜索引擎技术也发展的相当成熟,同时也就出现了很多开源的搜索引擎系统。比如,Solr、Lucene、Elasticsearch、Sphinx等。

本文以sphinx search为例来介绍如何打造自己的搜索引擎。该搜索引擎的架构大致如下:

Sphinx search

Sphinx search 是俄罗斯人用C++写的,速度很快,可以非常容易的与SQL数据库和脚本语言集成,内置MySQL和PostgreSQL 数据库数据源的支持。其官方网站是: http://sphinxsearch.com/

可以说Sphinx支持包括英文、中文等所有语言的搜索。英文是以空格、标点符号来分割单词的,很容易切分。而中文词汇之间是没有空格的,很难区分,所以才有了自然语言处理中的“中文分词”技术的研究。Sphinx默认把中文按字拆分的,但这样就会产生搜索出不相干的内容来。比如,搜索“中国”,它会把同时包含“中”和“国”但不包含“中国”的文档搜出来。因此,有人就给Sphinx打了中文分词的补丁。

如果没有搞错的话,最早添加中文分词的是Coreseek,好像也是中文圈用得最广的支持中文分词的Sphinx,其它还有sphinx-for-chinese。然而这二者基于的Sphinx版本都太低了,有好多年没有更新。其中存在的一些Sphinx的bug也没有解决。

github上有一个基于Sphinx 2.2.9版本的代码库添加了中文分词: https://github.com/eric1688/sphinx  经测试,该版本稳定性和速度都要好于coreseek。当然它依然支持英文等其它语言的搜索,只是对中文搜索更加准确了。

Sphinx 安装

git clone https://github.com/eric1688/sphinx
cd sphinx#编译(假设安装到/usr/local/sphinx目录,下文同) ./configure --prefix=/usr/local/sphinx# 说明: --prefix 指定安装路径 --with-mysql 编译mysql支持 --with-pgsql 编译pgsql支持make
sudo make install

安装好后,在/usr/local/sphinx目录下有以下几个子目录:
etc/  sphinx配置文件,不同的索引可以写不同的配置文件
bin/  sphinx程序,其中有建立索引的程序:indexer, 搜索守护进程:searchd
var/  一般用了放置indexer索引好的文件

Sphinx索引的建立

MySQL数据库表结构 
从上面的架构图可以看出来,我们要搜索的数据都存放在MySQL数据库中。假设我们的数据库名称叫blog_data,其中有个表叫article,表结构如下:

字段名

说明

id    文章唯一id(主键)    

title    文章标题    

content    文章内容    

created_time    文章创建时间    

该article表可以是你本身网站的文本内容存放的表格,也可以是你的网络爬虫抓取到的数据存储表。

还有建立另外一个表sph_counter用来存储indexer已经索引的最大doc id

字段名

说明

counter_id    标记是对哪个表做记录    

max_doc_id    被索引表的最大ID    

note    注释,可以是表名    

update_at    更新时间    

建立索引配置文件:  
新建或修改/usr/local/sphinx/etc/blog.conf 配置文件:

source blog_main
{
   type          = mysql
   sql_host      = localhost
   sql_user      = reader
   sql_pass      = readerpassword
   sql_db        = blog_data
   sql_port      = 3306
   sql_query_pre = SET NAMES utf8mb4
   sql_query_pre = REPLACE INTO sph_counter SELECT 1, MAX(id), 'article', NOW() FROM article

   sql_query     = SELECT id, title, content,  \
                   UNIX_TIMESTAMP(created_time) AS ctime, \
                   FROM article \
                   WHERE id <= (SELECT max_doc_id from sph_counter WHERE counter_id=1)
   sql_attr_timestamp = ctime  #从SQL读取到的值必须为整数,作为时间属性

}index blog_main{
   source       = blog_main #对应的source名称
   path         = /user/local/sphinx/var/data/blog_main
   docinfo      = extern
   mlock        = 0
   morphology   = none
   min_word_len = 1
   html_strip   = 0

   charset_type     = utf-8
   chinese_dictionary = /user/local/sphinx/etc/xdict  #中文分词的词典
   ngram_len = 0
   stopwords        = /user/local/sphinx/etc/stop_words.utf8}

#全局index定义indexer{
   mem_limit = 512M}

#searchd服务定义searchd{
   listen          = 9900
   listen          = 9306:mysql41  # 实时索引监听的端口
   read_timeout    = 5
   max_children    = 90
   max_matches     = 100000
   max_packet_size = 32M
   read_buffer     = 1M
   subtree_docs_cache = 8M
   subtree_hits_cache = 16M
   #workers        = threads•
   dist_threads    = 2
   seamless_rotate = 0
   preopen_indexes = 0
   unlink_old      = 1
   pid_file  = /usr/local/sphinx/var/log/blog_searchd_mysql.pid
   log       = /usr/local/sphinx/var/log/blog_searchd_mysql.log
   query_log = /usr/local/sphinx/var/log/blog_query_mysql.log}

编辑好以上配置文件,就可以开始建立索引了:

cd /usr/local/sphinx/bin
./indexer -c ../etc/blog.conf# 如果已经有searchd在运行了,就要加 --roate 来进行索引

索引建立后,就会在var/data/下面有名称前缀为blog_main.XXX的索引文件生成。

建立实时索引 
上面的配置文件是建立一个静态索引,把当时数据库里面的所有数据进行索引。但是,你的数据库往往是不断增加新数据的。为了及时索引并搜索到最新加入的数据,就需要配置实时索引了。

index rt_weixin                                                                                                     {
   type = rt
   path = /usr/local/sphinx/var/data/rt_blog
   rt_field = title
   rt_field = content

   rt_attr_timestamp = pubtime
   ngram_chars = U+3000..U+2FA1F #为了支持中文
   ngram_len = 1}

该仓库代码的作者可能是忘了给实时索引加中文分词,如果不配置ngram_chars 参数就不能搜到中文,添加后搜索是按单字匹配的,可见作者确实是忘了给实时索引部分加中文分词。

添加以上实时索引后并不能搜索到实时数据。实时索引的更新/添加只能通过SphinxQL(一个类似MySQL的协议),所以还要写一个Python脚本,从数据库读取最新的数据并通过SphinxQL更新到实时索引。

import MySQLdb# 连接实时索引db_rt = MySQLdb.connect(
   '127.0.0.1',
   'nodb',  # 对于实时索引来说,db,user,password都是不需要的,随便写。
   'noname',
   'nopass',
   port=9306,  # 实时索引监听的端口)  # 向实时索引更新数据的函数def into_rt(index_name, item):
   cursor = db_rt.cursor()
   fields = item.keys()
   values = item.values()
   fieldstr = ','.join(fields)
   valstr = ','.join(["'%s'"] * len(item))
   for i in xrange(len(values)):
       if isinstance(values[i], unicode):
           values[i] = values[i].encode('utf8')
       elif isinstance(values[i], datetime):
           try:
               values[i] = int(time.mktime(values[i].timetuple()))
           except:
               traceback.print_exc()
               print values[i]
               values[i] = int(time.time())
   sql = 'INSERT INTO %s (%s) VALUES(%s)' % (index_name, fieldstr, valstr)
   # print sql
   sql = sql % tuple(values)
   try:
       cursor.execute(sql)
       db_rt.commit()
   except Exception, e:
       if e[0] == 1064:
           # ignore duplicated id error
           pass
       else:
           traceback.print_exc()
           raise 'realtime index error'
   finally:
       cursor.close()

以上是及时建立实时索引的python程序的主要部分。可以把它设置成后台一直运行的守护程序,也可以在crontab里面配置每隔几分钟运行一次。

索引的更新 
静态的主索引如果只建立一次,实时索引的数据量会越积越多,对实时索引的搜索带来很大压力,所以我们要定时重新建立主索引,清理实时索引。
清理实时索引的程序可以参考上面建立实时索引的python程序。

crontab 设置每天凌晨1点运行 indexer

crontab 设置indexer运行完毕后清理实时索引,并从新的max_doc_id开始建立实时索引

以上就是建立一个自己的搜索引擎的过程。更多配置细节可到官方网站参考文档。


Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post