Lucene.NET打造站内搜索引擎
2011-10-06 20:23
597 查看
最近项目中使用到了lucene.net,虽然网上关于lucene.net的介绍已经很多了,在这里我的总结一下我的使用心得。我使用的是lucene.net的最新版(Lucene.Net-2.9.2),分词器采用了盘古分词,效果还不错。用lucene.net做站内搜索无非就是两步:创建索引和对在索引中搜索。
一、创建索引:
创建索引其实很简单了,我们从数据库中获取到需要生成索引的数据,然后利用lucene.net的IndexWriter类进行索引的创建。
IndexWriter构造函数中第一个参数指定索引文件存储位置;第二个参数指定分词Analyzer,Analyzer有多个子类,然而其分词效果并不好,这里使用的是第三方开源分词工具盘古分词;第三个参数表示是否重新创建索引,true表示重新创建(删除之前的索引文件),最后一个参数指定Field的最大数目。
IndexWriter中最重要方法void AddDocument(Document doc),表示添加一条索引记录到索引文件中。
Document中最重要的实例方法void Add(Fieldable field),添加一个用于创建一条索引记录的字段。
Fileld类的构造函数Field(string name,string value,Field.Store store,Field.Index index),name表示字段名;value表示字段值;store表示是否存储value值,可选值Field.Store.YES存储,Field.Store.NO不存储,Field.Store.COMPRESS压缩存储;index表示如何创建索引,可选值Field.Index.NO不创建索引, Field.Index.NOT_ANALYZED,创建索引,索引为未分词的value,Field.Index.ANALYZED,创建索引,索引为分词后的value;
注意:在Lucene.Net中如果直接索引时间格式是没有用的,需要将其转换成数字类型,这里我们将其转换成了Unix时间格式,这样我们在搜索某一时间段数据时就很容易了,将时间转换成Unix时间,以及转换回来的代码如下:
二、搜索
利用lucene.net实现搜索功能,主要就是利用IndexSearcher类的Search方法,在创建的索引文件中根据由关键字构建出的BooleanQuery搜索出符合条件的结果。
可以看出BooleanQuery是一个Query的集合,我们利用一个字段构造出一个QueryParser(构造参数中的filed必须为已经创建了索引的字段),然后利用其Parse方法传入搜索关键字,得到一个Query,而且多个Query在Add到BooleanQuery中时需要给定条件之间的关系,有BooleanClause.Occur.MUST、BooleanClause.Occur.SHOULD、BooleanClause.Occur.MUST三种选择,分别对应“与”、“或”、“非”三种关系。
站内搜索时,有时我们可能只想在某一种类型下进行搜索,如我们只想搜索IT新闻,而所有的新闻信息都存放在一张表中,其中有一个字段NewsType标示着新闻的类型,按照上面的实例程序我们应该可以这样修改程序:
其中的TermQuery表示“词语搜索”也就是需要全字匹配,NewsType是一个已经被索引的字段,并且它的索引方式为Field.Index.NOT_ANALYZED。
但是问题是:这样可以达到我们的要求吗?搜索出来的新闻都是“娱乐新闻”吗?答案是否定的。这样的结果是只要匹配了Title或Url就都会被搜出来。
对于这样的问题,我们需要再声明一个BooleanQuery用来存新闻类型的条件,然后将其与搜索关键字的BooleanQuery添加到一个新的BooleanQuery中,然后设置它们之间为“and”链接,修改代码如下:
利用这一点,还可以实现在搜索结果中再搜索,即将再次搜索的条件放在一个BooleanQuery中,将原来的搜索条件放在一个BooleanQuery,然后用一个新的BooleanQuery将它们MUST起来。
一、创建索引:
创建索引其实很简单了,我们从数据库中获取到需要生成索引的数据,然后利用lucene.net的IndexWriter类进行索引的创建。
private void CreateIndex() { Lucene.Net.Store.Directory directory = FSDirectory.Open(new System.IO.DirectoryInfo(strIndexPath)); IndexWriter writer = new IndexWriter(directory, new Lucene.Net.Analysis.PanGu.PanGuAnalyzer(), true, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED); DataTable dtNews = news.GetNews(); for (int i = 0; i < dtNews.Rows.Count; i++) { Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document(); doc.Add(new Field("Title", dtNews.Rows[i]["title"].ToString(), Field.Store.YES, Field.Index.ANALYZED)); doc.Add(new Field("Url", dtNews.Rows[i]["url"].ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); doc.Add(new Field("Content", dtNews.Rows[i]["content"].ToString(), Field.Store.YES, Field.Index.ANALYZED)); doc.Add(new Field("CreateTime", DateTimeHelper.DateTimeToUnix(Convert.ToDateTime(dtNews.Rows[i]["create_time"])).ToString(), Field.Store.YES, Field.Index.ANALYZED)); writer.AddDocument(doc); } writer.Optimize(); writer.Close(); }
IndexWriter构造函数中第一个参数指定索引文件存储位置;第二个参数指定分词Analyzer,Analyzer有多个子类,然而其分词效果并不好,这里使用的是第三方开源分词工具盘古分词;第三个参数表示是否重新创建索引,true表示重新创建(删除之前的索引文件),最后一个参数指定Field的最大数目。
IndexWriter中最重要方法void AddDocument(Document doc),表示添加一条索引记录到索引文件中。
Document中最重要的实例方法void Add(Fieldable field),添加一个用于创建一条索引记录的字段。
Fileld类的构造函数Field(string name,string value,Field.Store store,Field.Index index),name表示字段名;value表示字段值;store表示是否存储value值,可选值Field.Store.YES存储,Field.Store.NO不存储,Field.Store.COMPRESS压缩存储;index表示如何创建索引,可选值Field.Index.NO不创建索引, Field.Index.NOT_ANALYZED,创建索引,索引为未分词的value,Field.Index.ANALYZED,创建索引,索引为分词后的value;
注意:在Lucene.Net中如果直接索引时间格式是没有用的,需要将其转换成数字类型,这里我们将其转换成了Unix时间格式,这样我们在搜索某一时间段数据时就很容易了,将时间转换成Unix时间,以及转换回来的代码如下:
private static readonly long _UinxBase = DateTime.Parse("1970-1-1 00:00:00").Ticks; private const long _DOT_NET_TIME_TICK = 10000000; // C#每秒所占的刻度 ///<summary> /// 把C#时间转为Unix时间 ///</summary> ///<param name="time">C#时间</param> ///<returns>转换后的Unix时间</returns> public static int DateTimeToUnix(DateTime time) { return (Int32)((time.ToUniversalTime().Ticks - _UinxBase) / _DOT_NET_TIME_TICK); } ///<summary> /// Unix时间转化为C#时间 ///</summary> ///<param name="time">Unix时间</param> ///<returns>转化后的C#时间</returns> public static DateTime UnixToDateTime(int time) { try { long t = time * _DOT_NET_TIME_TICK + _UinxBase; return new DateTime(t).ToLocalTime(); } catch { return DateTime.Today; } }
二、搜索
利用lucene.net实现搜索功能,主要就是利用IndexSearcher类的Search方法,在创建的索引文件中根据由关键字构建出的BooleanQuery搜索出符合条件的结果。
public List<EntityNews> SearchNews(string keyword, int pageSize, int pageNo, out int recCount) { string keywords = keyword; //获取用户输入关键字,以备设置高亮显示 IndexSearcher search = new IndexSearcher(FSDirectory.Open(new System.IO.DirectoryInfo(strIndexPath)), true); keyword = GetKeyWordsSplitBySpace(keyword, new PanGuTokenizer()); QueryParser titleQueryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "Title", new PanGuAnalyzer(true)); Query titleQuery = titleQueryParser.Parse(keyword); Query urlQuery = new PrefixQuery(new Term("Url", keywords)); //URL不进行分词,直接模糊匹配 BooleanQuery bq = new BooleanQuery(); bq.Add(titleQuery, BooleanClause.Occur.SHOULD);//表示条件关系为“or”,BooleanClause.Occur.MUST表示“and”,BooleanClause.Occur.MUST_NOT表示“not” bq.Add(urlQuery, BooleanClause.Occur.SHOULD); //创建一个结果收集器(收集结果最大数为1000页) TopScoreDocCollector collector = TopScoreDocCollector.create(pageSize * 1000, true); search.Search(bq, null, collector); TopDocs topDoc = collector.TopDocs(0, collector.GetTotalHits()); //搜索结果总数超出指定收集器大小,则摈弃 if (topDoc.totalHits > pageSize * 1000) recCount = pageSize * 1000; else recCount = topDoc.totalHits; int i = (pageNo - 1) * pageSize; List<EntityNews> result = new List<EntityNews>(); while (i < recCount && result.Count < pageSize) { EntityNews news = new EntityNews(); Lucene.Net.Documents.Document docs = search.Doc(topDoc.scoreDocs[i].doc); try { string strTitle = docs.Get("Title"); string strContent = docs.Get("Content"); news.Url = docs.Get("Url"); news.Time = DateTimeHelper.UnixToDateTime(Convert.ToInt32(docs.Get("CreateTime"))); //高亮显示设置 PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font style=\"color:red;\">", "</font>"); PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new PanGu.Segment()); highlighter.FragmentSize = 50; //string GetBestFragment(keywords,content)方法会按照SimpleHTMLFormatter构造的格式对content中关键字进行高亮显示 //但如果content中不包含keywords则会返回空值,故需要按照如下进行判断 news.Content = highlighter.GetBestFragment(keywords, strContent); if (string.IsNullOrEmpty(news.Content)) { news.Content = strContent; } news.Title = highlighter.GetBestFragment(keywords, strTitle); if (string.IsNullOrEmpty(news.Title)) { news.Title = strTitle; } } catch (Exception e) { throw e; } finally { result.Add(news); i++; } } search.Close(); return result; }
可以看出BooleanQuery是一个Query的集合,我们利用一个字段构造出一个QueryParser(构造参数中的filed必须为已经创建了索引的字段),然后利用其Parse方法传入搜索关键字,得到一个Query,而且多个Query在Add到BooleanQuery中时需要给定条件之间的关系,有BooleanClause.Occur.MUST、BooleanClause.Occur.SHOULD、BooleanClause.Occur.MUST三种选择,分别对应“与”、“或”、“非”三种关系。
站内搜索时,有时我们可能只想在某一种类型下进行搜索,如我们只想搜索IT新闻,而所有的新闻信息都存放在一张表中,其中有一个字段NewsType标示着新闻的类型,按照上面的实例程序我们应该可以这样修改程序:
QueryParser titleQueryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "Title", new PanGuAnalyzer(true)); Query titleQuery = titleQueryParser.Parse(keyword); Query urlQuery = new PrefixQuery(new Term("Url", keywords)); //URL不进行分词,直接模糊匹配 Query typeQuery = new TermQuery(new Term("NewsType", "娱乐新闻")); BooleanQuery bq = new BooleanQuery(); bq.Add(titleQuery, BooleanClause.Occur.SHOULD); bq.Add(urlQuery, BooleanClause.Occur.SHOULD); bq.Add(typeQuery , BooleanClause.Occur.MUST);//表示条件关系为“and”
其中的TermQuery表示“词语搜索”也就是需要全字匹配,NewsType是一个已经被索引的字段,并且它的索引方式为Field.Index.NOT_ANALYZED。
但是问题是:这样可以达到我们的要求吗?搜索出来的新闻都是“娱乐新闻”吗?答案是否定的。这样的结果是只要匹配了Title或Url就都会被搜出来。
对于这样的问题,我们需要再声明一个BooleanQuery用来存新闻类型的条件,然后将其与搜索关键字的BooleanQuery添加到一个新的BooleanQuery中,然后设置它们之间为“and”链接,修改代码如下:
BooleanQuery bqNewsType = null; //保存新闻类型的Query集合 BooleanQuery bqKeyword = new BooleanQuery(); //保存关键字的Query集合 QueryParser titleQueryParser = new QueryParser(Lucene.Net.Util.Version.LUCENE_29, "Title", new PanGuAnalyzer(true)); Query titleQuery = titleQueryParser.Parse(keyword); Query urlQuery = new PrefixQuery(new Term("URL", keywords)); //URL不进行分词,直接模糊匹配 bqKeyword.Add(titleQuery, BooleanClause.Occur.SHOULD); bqKeyword.Add(urlQuery, BooleanClause.Occur.SHOULD); if (!string.IsNullOrEmpty(newsType)) { Query typeQuery = new TermQuery(new Term("NewsType", newsType)); //不分词,直接全字匹配 bqNewsType = new BooleanQuery(); bqNewsType.Add(typeQuery, BooleanClause.Occur.SHOULD); } //把两个条件集合"and"起来 BooleanQuery bq = new BooleanQuery(); bq.Add(bqKeyword, BooleanClause.Occur.MUST); if (bqNewsType != null) { bq.Add(bqNewsType, BooleanClause.Occur.MUST); }
利用这一点,还可以实现在搜索结果中再搜索,即将再次搜索的条件放在一个BooleanQuery中,将原来的搜索条件放在一个BooleanQuery,然后用一个新的BooleanQuery将它们MUST起来。
相关文章推荐
- Lucene.NET打造站内搜索引擎
- 利用Lucene打造站内搜索引擎的思路
- Lucene.NET----站内搜索引擎资料(推荐-arvin)
- Lucene:利用Lucene打造站内搜索引擎的思路
- 利用Lucene打造站内搜索引擎的思路
- 借助 Lucene.Net 构建站内搜索引擎(上)
- 利用Lucene打造站内搜索引擎的思路
- Lucene.net站内搜索-最简单搜索引擎代码
- Lucene.net站内搜索3—最简单搜索引擎代码
- 借助 Lucene.Net 构建站内搜索引擎(下)
- 借助 Lucene.Net 构建站内搜索引擎(上)
- 借助 Lucene.Net 构建站内搜索引擎(下)
- 利用Lucene打造站内搜索引擎的思路
- 利用Lucene打造站内搜索引擎的思路
- lucene.NET 搜索引擎的开发实例
- 基于lucene.net 和ICTCLAS2014的站内搜索的实现1
- 完整的站内搜索Demo(Lucene.Net+盘古分词)
- Lucene.net 盘古分词 站内搜索
- 利用Lucene.net搭建站内搜索(2)---分词技术