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屬性
相關文章
相關標籤/搜索