馬軍 楊維明 周民
摘要:該文針對(duì)網(wǎng)上書(shū)城對(duì)信息實(shí)時(shí)性與準(zhǔn)確性高的要求,提出了使用lucene與盤(pán)古分詞器相結(jié)合的站內(nèi)搜索系統(tǒng)解決方案。通過(guò)分析lucene內(nèi)置分詞器與盤(pán)古分詞器的性能差異,選擇了針對(duì)中文開(kāi)發(fā)的盤(pán)古分詞器,提高了搜索的準(zhǔn)確性;通過(guò)采用“生產(chǎn)者與消費(fèi)者”多線程模式與“單例”設(shè)計(jì)模式相結(jié)合的方法,實(shí)現(xiàn)了數(shù)據(jù)的實(shí)時(shí)更新。實(shí)驗(yàn)結(jié)果證明了設(shè)計(jì)方案的有效性。
關(guān)鍵詞:lucene;盤(pán)古分詞;網(wǎng)上書(shū)城;站內(nèi)搜索
中圖分類(lèi)號(hào):TP393 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2015)20-0184-04
站內(nèi)搜索是指對(duì)網(wǎng)站內(nèi)部信息的精確檢索和資源挖掘, 它為用戶(hù)提供準(zhǔn)確、快速的站內(nèi)信息檢索服務(wù),站內(nèi)搜索效果直接決定著網(wǎng)站商品的銷(xiāo)量?,F(xiàn)有的網(wǎng)上書(shū)城網(wǎng)站大多采用鏈接google和baidu網(wǎng)站的方法實(shí)現(xiàn)搜索,不利于數(shù)據(jù)的實(shí)時(shí)更新,此外,還存在著書(shū)籍信息準(zhǔn)確性不高的缺點(diǎn)。
Lucene是目前最流行開(kāi)源檢索工具包之一, 已經(jīng)在許多搜索項(xiàng)目中得到了應(yīng)用。盤(pán)古分詞也是一個(gè)比較成熟的中文分詞組件,而且采用多元分詞技術(shù),可以很好的實(shí)現(xiàn)對(duì)中文的分詞。因而本文針對(duì)中小網(wǎng)上書(shū)城提出了一個(gè)基于lucene.net與盤(pán)古分詞的站內(nèi)搜索技術(shù)方案,提高網(wǎng)站的競(jìng)爭(zhēng)力。
1 站內(nèi)搜索方案設(shè)計(jì)
網(wǎng)上書(shū)城站內(nèi)搜索系統(tǒng)框圖如圖1所示。
本文提出的站內(nèi)搜索方案將系統(tǒng)分為書(shū)籍編輯模塊、索引模塊、檢索模塊和展示模塊4個(gè)部分。書(shū)籍編輯模塊主要負(fù)責(zé)收集書(shū)籍的信息并且轉(zhuǎn)換為純文本信息,即lucene可識(shí)別的信息。索引模塊主要針對(duì)數(shù)據(jù)庫(kù)創(chuàng)建和維護(hù)索引庫(kù),即每當(dāng)增加或修改書(shū)籍的信息到數(shù)據(jù)庫(kù)時(shí),就更新索引庫(kù)。 檢索模塊主要針對(duì)用戶(hù)輸入的關(guān)鍵字進(jìn)行分析,然后查詢(xún)索引庫(kù)找到相關(guān)聯(lián)書(shū)籍,并且按相關(guān)性程度排序。展示模塊主要負(fù)責(zé)接受用戶(hù)輸入的關(guān)鍵字,并展示搜索的結(jié)果中書(shū)籍的信息以及如何展示。根據(jù)搜索結(jié)果,可以定制個(gè)性化的展示。
圖1 網(wǎng)上書(shū)城站內(nèi)搜索系統(tǒng)框圖
2 盤(pán)古分詞器性能分析
盤(pán)古分詞是一個(gè)中英文分詞組件。它的作者通過(guò)分析比較中文分詞的一元分詞、二元分詞,多元分詞和精確分詞的性能,得出多元分詞適用性更強(qiáng)。但采用多元分詞產(chǎn)生了一些問(wèn)題,第一,多元分詞和搜索引擎結(jié)合得到較多的匹配結(jié)果,同時(shí)也增加了索引文件的大?。坏诙?,由于將一些單詞進(jìn)行了拆分,搜索結(jié)果的排序會(huì)受到影響。為了克服這兩個(gè)缺點(diǎn),盤(pán)古分詞提出了多元分詞的冗余度(Redundancy)和多元分詞結(jié)果的權(quán)重級(jí)別(Rank)的概念。盤(pán)古分詞支持3級(jí)冗余。比如“湖北大學(xué)”,冗余度為0、1、2時(shí),分詞結(jié)果分別是“湖北大學(xué)”、“湖北,湖北大學(xué),大學(xué)”、“湖北,湖北大學(xué),大,大學(xué),學(xué)”。盤(pán)古分詞將多元分詞出來(lái)的單詞根據(jù)其詞長(zhǎng),詞的間隔以及未登錄詞的取舍等條件給定了不同的權(quán)重。在搜索時(shí)對(duì)分解出來(lái)的關(guān)鍵字,我們指定權(quán)重來(lái)影響搜索結(jié)果,以實(shí)現(xiàn)結(jié)果有效排序。比如搜“湖北大學(xué)”時(shí),可以將“湖北大學(xué)”設(shè)置較高的權(quán)重,而“大學(xué)”和“湖北”設(shè)置較低權(quán)重,則包含“湖北大學(xué)”的記錄就優(yōu)先于包含“湖北”或“大學(xué)”的記錄,這樣就解決了排序問(wèn)題。
為了說(shuō)明盤(pán)古分詞優(yōu)于lucene內(nèi)置分詞器,做了如下表格的對(duì)比。從表中可以看出,StopAnalyzer針對(duì)非字母字符拆分文本,然后小寫(xiě)英文字母,再過(guò)濾掉停用詞;KeywordAnalyzer將整個(gè)文本當(dāng)作一個(gè)詞處理;SimpleAnalyzer和StopAnalyzer類(lèi)似;WhitespaceAnalyzer根據(jù)空格拆分詞匯單元;StandardAnalyzer按每個(gè)漢字拆分詞,PanGuAnalyzer按有意義的中文詞語(yǔ)分詞,顯然效果最佳。因而本文選擇盤(pán)古分詞器,提高搜索的準(zhǔn)確性。
分詞比較的結(jié)果如下表1所示。
表1 盤(pán)古分詞器與lucene內(nèi)置分詞器性能比較
[分詞器
結(jié)果
分詞內(nèi)容\&Lucen.NET分析與應(yīng)用 機(jī)械工業(yè)出版社 吳眾欣
\&StopAnalyzer\&lucene|net分析與應(yīng)用|機(jī)械工業(yè)出版社|吳眾欣\&KeywordAnalyzer\&lucene分析與應(yīng)用 機(jī)械工業(yè)出版社 吳眾欣\&SimpleAnalyzer\&lucene|net分析與應(yīng)用|機(jī)械工業(yè)出版社|吳眾欣\&WhitespaceAnalyzer\&lucene.NET分析與應(yīng)用|機(jī)械工業(yè)出版社|吳眾欣\&StandardAnalyzer\&|lucene|分|析|與|應(yīng)|用|機(jī)|械|工|業(yè)|出|版|社|吳|眾|欣\&PanGuAnalyzer\&|lucene|分析|與|應(yīng)用|機(jī)械|工業(yè)出版社|吳眾欣\&]
3 站內(nèi)搜索的實(shí)現(xiàn)
3.1 書(shū)籍編輯模塊
為了可以批量添加書(shū)籍信息,使用XML存儲(chǔ)書(shū)籍信息。網(wǎng)站管理員使用后臺(tái)管理界面添加或修改書(shū)籍信息。本文從XML讀文件信息的使用.NET內(nèi)置操作XML文件的庫(kù)函數(shù)。管理員輸入的書(shū)籍信息是純文本格式,無(wú)需解析。
本文從XML文件獲得純文本信息的核心代碼如下:
//創(chuàng)建一個(gè)XML對(duì)象
XMLDocument doc=new XMLDocument();
//加載指定的XML文檔
doc.Load(@"E:\books.xml");
//創(chuàng)建讀取器
XMLNodeReader reader=new XMLNodeReader();
//讀取節(jié)點(diǎn)的信息
While(reader.Read())
{
Switch(reader.NodeType)
{
Case XmlNodeType.Element
If(reader.Name="title")
bookTitle=reader.Value;
...
}
}
3.2 索引模塊
索引庫(kù)相當(dāng)于關(guān)系數(shù)據(jù)庫(kù),非常關(guān)鍵和重要,直接決定搜索的響應(yīng)速度與準(zhǔn)確性。針對(duì)網(wǎng)上書(shū)城,書(shū)籍關(guān)鍵信息包括書(shū)名、出版社、作者、出版時(shí)間、書(shū)籍簡(jiǎn)介等,因此在建立索引庫(kù)的時(shí)候一定要包含這些關(guān)鍵信息,方便用戶(hù)快速查詢(xún),其它信息可根據(jù)需求添加。
為了實(shí)現(xiàn)網(wǎng)上書(shū)城實(shí)現(xiàn)實(shí)時(shí)的數(shù)據(jù)更新,本文采用“單例設(shè)計(jì)模式”和“生產(chǎn)者與消費(fèi)者”多線程模型相結(jié)合的方式。主線程創(chuàng)建單例管理器,整個(gè)程序中是唯一的,保證了數(shù)據(jù)更新數(shù)據(jù)的一致性。同時(shí)主線程扮演生產(chǎn)者的角色,當(dāng)添加或修改書(shū)籍的信息時(shí),主線程(生產(chǎn)者)就會(huì)調(diào)用管理器,向任務(wù)列表新增任務(wù),不用關(guān)心任務(wù)是否執(zhí)行了。整個(gè)網(wǎng)站一旦運(yùn)行,主線程(生產(chǎn)者)調(diào)用管理器創(chuàng)建消費(fèi)者線程并啟動(dòng)該線程。消費(fèi)者線程就會(huì)循環(huán)的檢測(cè)任務(wù)列表,如果有任務(wù),就執(zhí)行索引任務(wù),向索引庫(kù)中添加或修改書(shū)籍信息,否則,可根據(jù)需要設(shè)置該線程休眠的時(shí)間,防止占用cpu,造成浪費(fèi)。索引系統(tǒng)原理框圖如圖2所示。
圖2 索引系統(tǒng)原理框圖
具體索引功能使用lucene提供的核心類(lèi)IndexWriter、Direcrtory、Analyzer、Document、Field實(shí)現(xiàn)索引。IndexWriter這個(gè)類(lèi)負(fù)責(zé)創(chuàng)建新索引或打開(kāi)已有索引,以向索引中添加、刪除或更新被索引文檔的信息。Direcrtory類(lèi)描述了lucene索引存放位置。它是一個(gè)抽象類(lèi),它的子類(lèi)負(fù)責(zé)具體指定索引的存儲(chǔ)路徑。Analyzer這個(gè)類(lèi)負(fù)責(zé)分詞,它是一個(gè)抽象類(lèi),需要具體類(lèi)實(shí)現(xiàn)它。Document類(lèi)似于關(guān)系數(shù)據(jù)庫(kù)的記錄,是一些字段(field)的集合。Field類(lèi)似于關(guān)系數(shù)據(jù)庫(kù)的字段。索引建立過(guò)程:創(chuàng)建IndexWriter類(lèi),指明索引庫(kù)位置以及使用的分詞器;創(chuàng)建一個(gè)文件記錄類(lèi)Document;把要記錄的字段加入Document;把Document寫(xiě)入到索引庫(kù)并且關(guān)閉索引。
核心代碼如下:
//單例索引管理器
public class IndexAdministration
{
//創(chuàng)建單例
public static readonly IndexAdministration Instance=new
IndexAdministration();
//私有化構(gòu)造器
Private IndexAdministration(){}
//創(chuàng)建索引任務(wù)列表
private List
//啟動(dòng)消費(fèi)者線程
public void StartComsumeThread()
{Thread threadIndexWork = new Thread(IndexWork);
threadIndexWork.Start();}
//執(zhí)行索引任務(wù)
private void IndexWork(){...}
//添加索引任務(wù),生產(chǎn)者調(diào)用
public void AddTask(IndexTask task){ ...tasks.Add(task);...}
//刪除索引任務(wù),生產(chǎn)者調(diào)用
public void RemoveTask(IndexTask task){...tasks.Remove(task)...}
}
執(zhí)行索引任務(wù)的核心代碼:
private void IndexWork()
{
while(ture)
{...
//若無(wú)任務(wù),睡眠
if (tasks.Count <= 0){ Thread.Sleep(3 * 1000);continue;}
//創(chuàng)建或打開(kāi)索引目錄
FSDirector IndexDirectory=FSDirectory.Open(
new DirectoryInfo(indexFilePath),new NativeFSLockFactory());
//創(chuàng)建索引寫(xiě)入器對(duì)象,參數(shù)1:索引庫(kù)位置,參數(shù)2:分詞器(盤(pán)古分詞),參數(shù)3:更新或添加
IndexWriter writer=new IndexWriter(IndexDirectory, new PanGuAnalyzer(),isUpdate);
//創(chuàng)建Document對(duì)象
Document doc=new Document();
//添加字段到Docment對(duì)象中,根據(jù)需要添加必要的字段
doc.Add(new Field("title",bookTitle,F(xiàn)ield.Store.YES,
Field.Index.ANALYZED));
...
//將doc添加到索引庫(kù)
writerAddDocument(doc);
//關(guān)閉寫(xiě)入器
writer.Close();
//關(guān)閉索引庫(kù)
IndexDirectory.Close();
}
}
3.3 檢索模塊
lucene檢索功能使用lucene提供檢索的核心類(lèi)IndexSearcher、Term、Query、TopScoreDocCollector實(shí)現(xiàn)。IndexSearcher類(lèi)用于搜索使用IndexWriter類(lèi)建立的索引庫(kù),它是連接索引的核心,以只讀的方式打開(kāi)索引庫(kù)。Term對(duì)象是檢索功能的最基本單元,包含域名和域文本值。Query是抽象父類(lèi),它有很多具體子類(lèi),最基本的子類(lèi)TermQuery,用來(lái)匹配指定域中包含特定項(xiàng)的文檔。TopScoreDocCollector是一個(gè)簡(jiǎn)單指針容器,指向匹配查詢(xún)條件的前N個(gè)搜索結(jié)果,N可以根據(jù)需要選擇,方便分頁(yè)展示。
檢索過(guò)程:打開(kāi)索引庫(kù)FSDirectory;創(chuàng)建一個(gè)搜索器IndexSearcher指向索引庫(kù);創(chuàng)建一個(gè)查詢(xún)類(lèi)Query的子類(lèi)并對(duì)輸入的內(nèi)容進(jìn)行分詞;組裝查詢(xún)類(lèi),lucene提供多種查詢(xún)類(lèi),根據(jù)需要選擇;通過(guò)調(diào)用搜索器的Search方法執(zhí)行查詢(xún),將結(jié)果放到TopScoreDocCollector指針容器;根據(jù)需要獲得查詢(xún)結(jié)果的文檔內(nèi)容。
核心代碼
//打開(kāi)已創(chuàng)建的索引庫(kù),
FSDirectory IndexDirectory=FSDirectory.Open(
new DirectoryInfo(indexFilePath),new NoLockFactory());
//創(chuàng)建搜索器
IndexSearcher indexSearcher = new IndexSearcher(IndexDirectory);
//創(chuàng)建Query的一個(gè)實(shí)現(xiàn)類(lèi),根據(jù)需要?jiǎng)?chuàng)建
PhraseQuery query = new PhraseQuery();
//對(duì)輸入內(nèi)容進(jìn)行分詞
List
//組裝查詢(xún)條件,根據(jù)需要設(shè)置
foreach(string word in cutWords){ query.Add(new Term("bookTitle", word));}
...
//創(chuàng)建存儲(chǔ)查詢(xún)結(jié)果的指針容器
TopScoreDocCollector collector = TopScoreDocCollector.create(100, true);
//查詢(xún),第一個(gè)參數(shù):查詢(xún)條件,第二個(gè):過(guò)濾條件,第三個(gè):指針容器
indexSearcher .Search(query, null, collector);
//從指針容器中獲得結(jié)果,第一個(gè)參數(shù):結(jié)果的起始位置,第二個(gè):獲取數(shù)量
ScoreDoc[] documents = collector.TopDocs(startIndex,Count).scoreDocs;
//從搜索結(jié)果中獲取Document
for (int i = 0; i < documents .Length; i++){
//得到文檔編號(hào),lucene內(nèi)部分配的,為了降低內(nèi)存,結(jié)果只有編號(hào)
int documentId = documents [i].doc;
/根據(jù)id找到Document
Document doc = searcher.Doc(docId);
//獲取Document的具體內(nèi)容,根據(jù)需求獲取所需內(nèi)容,便可以展示給用戶(hù)了
string bookTitle= doc.Get("title");
....
}
分詞函數(shù):
List
//存放分詞結(jié)果的集合
{List
//調(diào)用分詞器分詞
TokenStream ts = analyzer.ReusableTokenStream("", new StringReader(words));
Token token;
while ((token = ts.Next()) != null)
//添加到分詞結(jié)果
{results.Add(token.TermText());}
//關(guān)閉分詞器
ts.Close();
return results;}
3.4 展示模塊
展示模塊主要用來(lái)展示查詢(xún)到匹配的結(jié)果。lucene并沒(méi)用提供展示內(nèi)容的接口函數(shù),需要我們自己設(shè)計(jì)如何展示內(nèi)容,由于本文是針對(duì)網(wǎng)上書(shū)城開(kāi)發(fā)的站內(nèi)搜索,所以選擇一網(wǎng)頁(yè)的形式展現(xiàn)。
本文使用.NET平臺(tái)設(shè)計(jì)展示網(wǎng)頁(yè)。實(shí)現(xiàn)思路:首先在展示頁(yè)面拖放一個(gè)Reapter控件,其次把搜所到數(shù)據(jù)轉(zhuǎn)換成一個(gè)集合listResult,然后將listResult綁定到Reapter數(shù)據(jù)源便可顯示了。
為了更好的客戶(hù)體驗(yàn),像百度、Google、360搜索一樣,將結(jié)果中的關(guān)鍵字用特殊的顏色標(biāo)出,醒目的提示用戶(hù)。本文使用盤(pán)古分詞的高亮插件 PanGu.HighLight.dll實(shí)現(xiàn)。
4 實(shí)驗(yàn)結(jié)果與分析
基于上述設(shè)計(jì)與實(shí)現(xiàn)方法創(chuàng)建了一個(gè)虛擬的系統(tǒng),進(jìn)行實(shí)驗(yàn)驗(yàn)證。
使用vs2010平臺(tái)搭建,lucene.net版本3.0.3,盤(pán)古分詞版本2.3.1。
首先向索引庫(kù)中添加1000本書(shū)的信息,其中兩本書(shū)如表2所示。
表2 待搜索的書(shū)籍信息
[書(shū)名\&作者\&簡(jiǎn)介\&設(shè)計(jì)模式\&GOF4\&可復(fù)用面向?qū)ο筌浖幕A(chǔ)\&面向?qū)ο蟮某绦蛟O(shè)計(jì)\&Grady Booch\&面向?qū)ο蠓治雠c設(shè)計(jì),纖細(xì)介紹了,面向?qū)ο笤瓌t\&]
輸入“面向?qū)ο蟆?,根?jù)簡(jiǎn)介搜索,兩本書(shū)都可搜出,且排在前兩位,說(shuō)明準(zhǔn)確性高,及相關(guān)性設(shè)計(jì)合理。如圖3所示為搜索結(jié)果截圖。
圖3 輸入“面向?qū)ο蟆钡乃阉鹘Y(jié)果
其次輸入“l(fā)ucene”進(jìn)行搜索,沒(méi)有任何結(jié)果。說(shuō)明索引庫(kù)中沒(méi)有這本書(shū)的信息。添加如表3的信息。
表3 添加的書(shū)籍信息
[書(shū)名\&作者\&簡(jiǎn)介\&lucene實(shí)戰(zhàn)\&Erik Hatacher\&lucene實(shí)戰(zhàn)這本書(shū),從基礎(chǔ)講起,并運(yùn)用大量例子說(shuō)明使用方法,很實(shí)用。\&]
添加書(shū)籍信息后,立刻輸入“l(fā)ucene”,根據(jù)書(shū)名進(jìn)行搜索,搜到了這本書(shū),如圖4所示為搜索結(jié)果截圖。說(shuō)明了實(shí)時(shí)性高。
圖4 輸入“l(fā)ucene”進(jìn)行搜索的結(jié)果
5 結(jié)束語(yǔ)
本文針對(duì)網(wǎng)上書(shū)城對(duì)數(shù)據(jù)更新實(shí)時(shí)性高與書(shū)籍信息準(zhǔn)確性高的要求,提出了使用lucene與盤(pán)古分詞器相結(jié)合的站內(nèi)搜索系統(tǒng)解決方案。分析了盤(pán)古分詞器的性能優(yōu)勢(shì),選擇盤(pán)古分詞器,提高了搜索的準(zhǔn)確性;采用“生產(chǎn)者與消費(fèi)者”多線程模式與“單例”設(shè)計(jì)模式相結(jié)合的方法,實(shí)現(xiàn)了數(shù)據(jù)的實(shí)時(shí)更新。并通過(guò)實(shí)驗(yàn)驗(yàn)證了該方案的有效性。該方案主要針對(duì)中小型網(wǎng)上書(shū)城而設(shè)計(jì)的,對(duì)大型網(wǎng)上書(shū)城大數(shù)據(jù)不適應(yīng),這也是下一步繼續(xù)研究探索的方向。
參考文獻(xiàn):
[1] 邱哲, 符滔滔, 王學(xué)松. 開(kāi)發(fā)自己的搜索引擎Lucene+Heritrix[M]. 北京: 人民郵電出版社, 2010.
[2] 吳眾欣, 沈家立. Lucene分析與應(yīng)用[M]. 北京: 機(jī)械工業(yè)出版社, 2008.
[3] 李天平. 項(xiàng)目中的.NET[M]. 北京: 電子工業(yè)出版社, 2012.
[4] Martin Fowler. 重構(gòu)改善既有代碼的設(shè)計(jì)[M]. 北京: 人民郵電出版社.
[5] 周海松, 劉建明, 李龍. 基于Lucene的垂直搜索引擎研究與實(shí)現(xiàn)[J]. 桂林電子科技大學(xué)學(xué)報(bào), 2014, 34(3): 226-229.
[6] 李永春, 丁華福. Lucene 的全文檢索的研究與應(yīng)用[J]. 計(jì)算機(jī)技術(shù)與發(fā)展, 2010, 20(2): 12-15.
[7] 朱學(xué)昊, 王儒敬, 余鋒林, 唐昱. 基于 Lucene的站內(nèi)搜索設(shè)計(jì)與實(shí)現(xiàn)[J]. 計(jì)算機(jī)應(yīng)用與軟件, 2008, 25(10): 6-9.
[8] 李建林. 基于Lucene的Web搜索引擎的研究[D]. 蘭州: 蘭州理工大學(xué), 2010.
[9] 陰曉昱. 基于Lucene 多核并行索引方法的設(shè)計(jì)與實(shí)現(xiàn)[D]. 上海: 上海交通大學(xué), 2010.
[10] 胡鵬飛. Lucene與中文分詞技術(shù)的研究及應(yīng)用[D]. 北京: 北京交通大學(xué), 2010.