Sphinx学习笔记2

    由于网站搜索的须要,启动了一个搜索引擎项目,其实也算不上完整的搜索引擎,需求很简单,以下:php

    1)搜索产品名、类别名、品牌名、副标题、关键字等字段
    2)数据量目前为13000左右,将来可能在5万左右,超出10万的可能性不大
    3)搜索必须精确
    4)搜索结果须要按照必定的规则排序
    5)搜索结果能够按条件过滤
    可选的产品主要有3种,sphinx、solr、ElasticSearch,其中sphinx是基于C++的,体积小,运行速度快,分布式查询较困难,查询接口支持多种语言。solr和ElasticSearch基于Lucene,开发语言是Java,提供http访问支持。简单分析一下区别:
    1)sphinx创建索引较快,通常单机使用
    2)solr和es都是基于lucene的,有一些细微的区别,其中solr更正宗,由于solr和lucene如今已经合并了,可是es的查询效果更快、更稳定,尤为是solr的集群须要zookeeper,而es不须要,它自己设计就考虑了分布式。因此solr和es更适用于大项目,数据量较大的状况。好比较流行的elk日志分析系统,基于ElasticSearch、Logstash、Kiabana,理论上能够支持上百万台机器的日志分析、PB级别的数据分析。
    相对来讲,咱们的项目比较小,只须要用sphinx就能够了,另外考虑到项目时间较紧,就没有过多的测试es和solr。
    在这里,要区分两个概念:查询分词和索引分词
    1)查询分词指的是将输入查询的词进行分词,如输入“我是中国人”会分红“我”“是”“中国人”“中国” “人”
    2)索引分词指的是在原始数据进行索引时对原始数据进行分词,即如原始数据是“我是中国人”,会和上面同样的进行分词,并存储为索引。
    3)sphinx的原始版本只能对中文进行单字切割,因此须要在查询时进行分词,而后全词匹配,不然会出现不少莫名其妙的结果,好在这种状况下速度也挺快的。若是数据量很是大,单字切割速度就会变慢,更好的办法是索引分词,有一个corseek支持中文分词,只惋惜很久不更新了
    由于原始的sphinx版本只支持单字分词,因此须要使用查询分词,选择的是scws分词,配置起来较为容易。步骤以下:
一、环境
     1)CentOS : 6.5和7.0都可
     2)编译环境: yum install gcc gcc-c++
 二、sphinx
     1)sphinx主页在http://sphinxsearch.com/,文档在http://sphinxsearch.com/docs/current.html,比较详细
     2)编译(在测试服务器172.16.8.97上的步骤)
          $ tar xvf sphinx-2.2.10-release.tar.gz
          $ cd sphinx-2.2.10
          $ ./configure
          $ make
          $ make install
          这种安装的配置文件在/usr/local/etc下,默认有三个文件example.sql,是用来建立test索引的数据库,sphinx.conf.dist是比较详细的配置文件,sphinx-min.conf.dist是最小的配置文件,能够将sphinx-min.conf.dist更名或复制为sphinx.conf,这是默认的配置文件。
          sphinx有两个命令比较重要,indexer和searchd,前者是用来建索引的,后者是查询的守护进程,提供查询服务。
     3)配置sphinx.conf
          sphinx.conf也很容易懂,大体分为source, index, indexer, searchd这几部分,source表明数据来源,支持mysql、pgsql、mssql、odbc、xmlpipe、xmlpipe2,咱们用的是mysql,基本配置以下
#source配置
         source goods
{    
    type            = mysql
    sql_host        = localhost
    sql_user        = root
    sql_pass        = 
    sql_db            = xigang
    sql_port        = 3306    # optional, default is 3306
 
    sql_query_pre        = SET NAMES utf8
    sql_query        = \
    SELECT a.goods_id,goods_sn,a.goods_name,a.goods_brief,b.cat_name,c.brand_name,a.keywords goods_keywords,a.specification, a.goods_spec,e.region_name goods_country,c.alias brand_alias,a.brand_country,c.country brand_country1,    b.alias category_alias,    a.last_update,b.cat_id,    c.brand_id,b.keywords category_keywords,to_pinyin(a.goods_name) goods_name_pinyin,to_fpinyin(a.goods_name) goods_name_fpinyin,market_price,shop_price,origin_price,promote_price, IF(promote_price>0,1,0) promote_flag,    goods_number, IF(goods_number>0,1,0) goods_num_flag,sales_volume,if(sales_volume>0,1,0) volume_flag,is_new,a.is_delete goods_delete,a.sort_order,a.is_delete goods_delete,b.is_delete cat_delete,is_on_sale FROM ecs_goods a LEFT JOIN ecs_category b ON a.cat_id=b.cat_id LEFT JOIN ecs_brand c ON a.brand_id=c.brand_id    LEFT JOIN ecs_region e ON a.goods_country=e.region_id
 
    sql_attr_uint       = cat_id
    sql_attr_uint       = brand_id
    sql_attr_float        = market_price
    sql_attr_float        = origin_price
    sql_attr_float        = promote_price
    sql_attr_uint        = promote_flag
    sql_attr_uint        = sales_volume
    sql_attr_uint        = goods_number
    sql_attr_uint        = goods_num_flag
    sql_attr_uint        = is_new
    sql_attr_uint        = goods_delete
    sql_attr_float        = sort_order
    sql_attr_uint        = volume_flag
    sql_attr_uint        = is_on_sale
    sql_attr_uint        = cat_delete
    sql_attr_timestamp    = last_update
    sql_field_string    = goods_name
    sql_field_string    = goods_sn
    sql_field_string    = shop_price
    sql_field_string    = goods_brief
    sql_field_string    = cat_name
    sql_field_string    = brand_name
 
    sql_ranged_throttle    = 0
 
}
   配置简很是易懂,须要注意如下内容
   1)sql_query_pre        = SET NAMES utf8 是必须的,不然索引创建了,却搜索不出来,这在拷贝sphinx-min.conf.dist做为默认配置文件要特别注意,由于该文件中没有这一条,在sphinx.conf.dist中存在,若是有注释,去掉就能够了。
   2)sql_attr_*,这些字段都包含在搜索结果中,能够用来过滤、排序、分组等,须要注意的是sql_attr_string字段,这个字段也能够达到过滤、排序和分组的效果,可是这个字段不会为全文索引,因此须要用sql_field_string字段代替,它兼有过滤、分组、排序,还有索引的功能。sql_fied_string能够用在列表显示的时候,这样能够减小对mysql的查询,直接显示全部数据
  3)其余问题看手册为准
   #index配置
   index goods
{
    source            = goods    
    path            = /var/data/sphinx/goods
    docinfo            = extern
    dict            = keywords
    mlock            = 0
    min_stemming_len    = 1
    min_word_len        = 1
    min_infix_len        = 2
 
    ngram_len        = 1
    ngram_chars        =  U+4E00..U+9FBB, U+3400..U+4DB5, U+20000..U+2A6D6, U+FA0E, U+FA0F, U+FA11, U+FA13, U+FA14, U+FA1F, U+FA21, U+FA23, U+FA24, U+FA27, U+FA28, U+FA29, U+3105..U+312C, U+31A0..U+31B7, U+3041, U+3043, U+3045, U+3047, U+3049, U+304B, U+304D, U+304F, U+3051, U+3053, U+3055, U+3057, U+3059, U+305B, U+305D, U+305F, U+3061, U+3063, U+3066, U+3068, U+306A..U+306F, U+3072, U+3075, U+3078, U+307B, U+307E..U+3083, U+3085, U+3087, U+3089..U+308E, U+3090..U+3093, U+30A1, U+30A3, U+30A5, U+30A7, U+30A9, U+30AD, U+30AF, U+30B3, U+30B5, U+30BB, U+30BD, U+30BF, U+30C1, U+30C3, U+30C4, U+30C6, U+30CA, U+30CB, U+30CD, U+30CE, U+30DE, U+30DF, U+30E1, U+30E2, U+30E3, U+30E5, U+30E7, U+30EE, U+30F0..U+30F3, U+30F5, U+30F6, U+31F0, U+31F1, U+31F2, U+31F3, U+31F4, U+31F5, U+31F6, U+31F7, U+31F8, U+31F9, U+31FA, U+31FB, U+31FC, U+31FD, U+31FE, U+31FF, U+AC00..U+D7A3, U+1100..U+1159, U+1161..U+11A2, U+11A8..U+11F9, U+A000..U+A48C, U+A492..U+A4C6
 
    html_strip        = 0
}
    内容也很易懂,须要注意如下内容
    1)source是上面创建的source名字,不能写错了
    2)path是索引文件的位置,须要注意一下这个目录是否存在,是否有权限
    3)min_infix_len,主要用于单词内部搜索,由于默认状况下,单词按照空格或符号分割,若是只搜索一部分,就搜索不出来,增长这个属性,就能够了,可是会增长索引的大小,由于产生了不少小词。
    4)ngram_*是用于cjk字符的,即中文、日文和朝鲜文的分词,其中ngram_len说明分词宽度为1,ngram_chars指的那些词会被看成cjk,这是文档上的标准写法,拷贝就能够了。
    5)其余问题,看文档。
    indexer和searchd没什么可改的,对于咱们的系统也够用了。
三、索引生成和查询
    1)indexer --all --rotate
         --all表明从新生成索引, --rotate用于searchd服务已经启动的状况下
    2)searchd 
        启动搜索服务
四、使用sphinx
    1)sphinx提供了两套机制来访问sphinx索引服务,一个是sphinxapi,一个是sphinxql,前者比较通用,资料较多,但性能差一些,使用的端口是9312,sphinxql其实是一种相似sql的查询语言,协议用的是mysql客户端,速度要快一些。二者在功能上是等价的,区别在于sphinxql支持实时索引,使用的接口是9306。我比较喜欢sphinxql,由于能够直接打印出来,在mysql客户端工具里执行,看到效果。
    2)匹配模式
      SPH_MATCH_ALL 匹配全部查询词(sphinxapi默认值,可是效果很差,好比‘日本’,会搜索出‘今日买本子去了’)
      SPH_MATCH_ANY 匹配任何词
      SPH_MATCH_PHRASE 查询词整个匹配,返回最佳结果(适合精确搜索)
      SPH_MATCH_BOOLEAN 将查询词看成布尔值搜索(不知道怎么用)
      SPH_MATCH_EXTENED 查询词能够做为内部查询语言,便可以使用异或、正则之类的功能(默认值)。
      SPH_MATCH_EXTENED2 和SPH_MATCH_EXTENED相似
   4)SphinxQL
      $mysql -h0 -P 9306 #链接
      myql> select * from goods where match('奶粉');     #全部匹配的字段匹配"奶粉"这两个字
      mysql>select * from goods where match('"奶粉");  #全部匹配的字段匹配“奶粉”这个词
      mysql>select * from goods where match('@goods_name 奶粉');  #商品名中匹配“奶粉”这两个字
      mysql>select * from goods where match('@goods_name "奶粉"'); #商品名中匹配“奶粉”这个词
      mysql>select * from goods where match('@(goods_name,goods_brief) "喜宝" "配方奶粉"'); 
      #这里是goods_name,goods_biref这两个字段包括"喜宝" "配方奶粉"这两个词,若是没有双引号,就是全部的字
      mysql>select id,weight() from goods where match('@(goods_name,goods_brief) "喜宝" "配方奶粉"') order by weight() desc option ranker=proximity,field_weights=(goods_name=100,goods_brief=10); 
     #这里增长权重,goods_name=100,goods_brief=10,若是二者都匹配,weight()应该是相似110,不过因为算法问题,可能会是220,440之类的。能够经过权重知道匹配状况,及时处理一些不合适的搜索问题
      mysql> select max(id),count(*),archival_sn, weight() wt from goods where is_on_sale=1 and goods_delete=0 and pr_area='01' group by archival_sn having count(*)>1 order by goods_num_flag desc ,promote_flag desc ,sort_order desc ,weight() desc ,id desc limit 0,20 option ranker=proximity ,field_weights=(goods_name=10000000,goods_sn=1000000,goods_keywords=100000,cat_name=10000,brand_name=1000,goods_brief=100,specification=10,goods_spec=1);
      #这个比较复杂,含义是按照必定的条件筛选数据,同一备案号的商品选择id最大的,只显示一个,而后去数据库中抓取相应的数据,固然也能够在sphinx中抓取数据,简单字段都保存在sphinx中了。
      #另外sql_attr_*和sql_field_string至关于索引goods的数据列,select cat_id,cat_name from goods
五、PHP访问sphinx
     PHP访问sphinx有两种方法,SphinxAPI和SphinxQL,前者是调用sphinx提供的接口函数,这些函数保存在sphinx源代码包的api目录下,包括php、python、java、ruby的接口。php调用SphinxAPI文档不少,就很少说了。使用SphinxQL也很是简单,用mysql的操做函数就能够了,区别在于端口是9306,用户名密码都是空,如
    $pdo = new PDO("mysql:host=localhost;port=9306;charset=utf-8','','');
     $query1="select * from goods where match('奶粉'); ";
     $sth1 = $pdo->prepare($query1);
     $sth1->execute();
     $result1 = $sth1->fetchAll();
六、使用scws分词
     scws分词主页在http://www.xunsearch.com/scws/,这是一个开源分词程序,能够自定义分词,速度也比较快,也比较简单,因此就用这个了。
     1)配置
     $ tar xvf scws-1.2.2.tar.bz2
     $ cd scws-1.2.2
     $ ./configure --prefix=/usr/local/scws
     $ make
     $ make install
     默认程序是安装到/usr/local/scws下,能够去这个目录看看是否安装成功。
     2)分词词典
     $ cd /usr/local/scws/etc
     $ tar xvjf scws-dict-chs-gbk.tar.bz2
     $ tar xvjf scws-dict-chs-utf8.tar.bz2
     在/usr/local/scws/etc下会产生两个文件dict.xdb和dict.utf8.xdb,前者是gbk编码的,后者是utf8编码的字典
     3)PHP扩展
     scws是一个C语言程序,能够用C语言直接调用,不过它提供了php接口,安装也很简单,以下
      $ cd phpext    #scws源程序根目录
      $ phpize         #须要安装php开发包,yum install php-devel
      $ ./configure
      $ make
      $ make install
      $ vim /etc/php.ini #也有可能在php-fpm目录下,看你的服务器状况
       增长以下内容
       [scws]
       extension = /usr/lib64/php/modules/scws.so
       scws.default.charset = utf8
       scws.default.fpath = /usr/local/scws/etc
       编写简单的demo,以下
      //scws0.php
<?php
error_reporting(0);
$so = scws_new();
$so->set_charset('utf8');
$so->set_dict('/usr/local/scws/etc/dict.utf8.xdb');
$so->set_rule('/usr/local/scws/etc/rules.utf8.ini');
//$so->add_dict('/usr/local/scws/etc/new.txt',SCWS_XDICT_TXT);
scws_set_multi($so,2);  
$so->set_ignore(true); //忽略标点符号
//$text=$_GET["text"];
$text='我是一个中国人,我爱个人祖国';
$so->send_text($text);
$scws='';
while ($tmp = $so->get_result())   // get_result()要重复调用,直到全部的分词结果都返回为止
{
  for($i=0;$i<count($tmp);$i++){
        $scws=$scws.' "'.$tmp[$i]["word"].'" ';
  }
}
$so->close();
echo substr($scws,1);
?>
      $ php scws0.php
      "我"  "是"  "一个"  "中国人"  "我爱"  "我"  "的"  "祖国" 
      这里要说明一下,这个分词程序用起来是大同小异,能够在主页看详细文档,这里作了一个处理,将每一个词增长了双引号,这样是为了在sphinxql中调用方便。
      $so->add_dict能够增长其余词典,词典是xdb格式,也能够用文本,只不过文本要慢一些,初期能够用文本,等正式上线再生成xdb文件。下面以文本为例,内容以下
爱他美  100     100     nt
施华蔻  100.01  100.01  nt
瘦身    100.02  100.02  nt
护眼    100.03  100.03  nt
1段     100.04  100.04  nt
2段     100.04  100.04  nt
3段     100.05  100.05  nt
4段     100.06  100.06  nt
5段     100.07  100.07  nt
结构很简单,第一列是分词,第二列是tf,第三列是idf,第四列是词性,用起来很容易,若是没有这个文件,将上面的程序中的$text值修改成“爱他美”,会输出“爱”“他”“美”,若是增长了这个分词文件,并将scws0.php中的注释去掉,执行结果就变成了“爱他美”。
到此,分词问题就解决了,若是多个服务器使用也很简单,能够将这个程序对外发布。

七、作一个简单的demohtml

     # Pf.php
<?php
error_reporting(0);
class Pf {
        public static function splitSearch($search){
                $so = scws_new();
                $so->set_charset('utf8');
                $so->set_dict('/usr/local/scws/etc/dict.utf8.xdb');
                $so->set_rule('/usr/local/scws/etc/rules.utf8.ini');
                $so->add_dict('/usr/local/scws/etc/new.txt',SCWS_XDICT_TXT);
                scws_set_multi($so,2);
                $so->set_ignore(true);
                $text=$search;
                //$text='爱他美';
                $so->send_text($text);
                //scws_add_dict($so,'/usr/local/scws/etc/new.txt',SCWS_XDICT_TXT);
                $scws='';
                while ($tmp = $so->get_result())
                {
                        for($i=0;$i<count($tmp);$i++){
                                $scws=$scws.' "'.$tmp[$i]["word"].'" ';
                        }
                }
                $so->close();
                return substr($scws,1);
 
        }
}
#test.php
<?php
require('./Pf.php');
$query = "爱他美";
$search =Pf::splitSearch($query);
$host = "127.0.0.1";
$port = 9306;
$pdo = new PDO("mysql:host=".$host.';port='.$port.';charset=utf-8','','');
$sql = "select id,goods_name from goods where match('".$search."') limit 1,2";
$sth=$pdo->prepare($sql);
$sth->execute();
$result = $sth->fetchAll();
echo "<pre>";
print_r($result);
echo "</pre>";
输出结果为
 
Array
(
    [0] => Array
        (
            [id] => 2927
            [0] => 2927
            [goods_name] => 德国原装 Aptamil爱他美 婴儿配方奶粉2段800g
            [1] => 德国原装 Aptamil爱他美 婴儿配方奶粉2段800g
        )

    [1] => Array
        (
            [id] => 3223
            [0] => 3223
            [goods_name] => 德国原装 Aptamil爱他美 婴儿配方奶粉2+段600g
            [1] => 德国原装 Aptamil爱他美 婴儿配方奶粉2+段600g
        )

)

八、词典生成导出工具java

  2)unzip phptool_for_scws_xdb.zip
  3)解压缩有四个文件readme.txt xdb.class.php make_xdb_file.php dump_xdb_file.php,其中make_xdb_file.php是从文件生成xdb的,dump_xdb_file.php是生成文本文件的,执行过程以下
      php make_xdb_file.php 字典文件 文本文件
      php dump_xdb_file.php 字典文件 文本文件
      dump比较快,make很慢,因此自定义分词不要放在标准库里,仍是单独作文件吧,而后生成独立的字典
九、正式上线须要作的
   1)要使用字典文件,而且加载到内存里,这样能够提升一下分词速度,以下
      $ php make_xdb_file.php new.xdb new.txt
      $ cp new.xdb /usr/local/scws/etc
      $ vim Pf.php 
      $so->set_dict('/usr/local/scws/etc/dict.utf8.xdb',SCWS_XDICT_MEM);
      $so->add_dict('/usr/local/scws/etc/new.xdb',SCWS_XDICT_MEM);
      修改set_dict和add_dict函数的参数
  2)若是要支持英文部分搜索,如搜索deb便可看到deben,使用*匹配,修改Pf.php,以下
        $scws=$scws.' "'.$tmp[$i]["word"].'*" ';
       这须要英文切词支持,indexer中须要有min_infix_len属性
相关文章
相关标签/搜索