服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服务器之家 - 编程语言 - ASP.NET教程 - 使用Lucene.NET实现站内搜索

使用Lucene.NET实现站内搜索

2019-12-16 13:04hebedich ASP.NET教程

提到Lucene,想必大家都有所耳闻,已经是数年前就出现的开源技术。很多站点都是利用它搭建自己网站的站内搜索。由于最近也在做数据检索方面的东西,也学习了下Lucene.net的使用。

导入Lucene.NET 开发包

Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene.Net 是 .NET 版的Lucene。

你可以在这里下载到最新的Lucene.NET

创建索引、更新索引、删除索引

使用Lucene.NET实现站内搜索

搜索,根据索引查找

使用Lucene.NET实现站内搜索

IndexHelper 添加、更新、删除索引

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
using System;
using Lucene.Net.Store;
using Lucene.Net.Index;
using Lucene.Net.Analysis.PanGu;
using Lucene.Net.Documents;
 
namespace BLL
{
  class IndexHelper
  {
    /// <summary>
    /// 日志小助手
    /// </summary>
    static Common.LogHelper logger = new Common.LogHelper(typeof(SearchBLL));
    /// <summary>
    /// 索引保存的位置,保存在配置文件中从配置文件读取
    /// </summary>
    static string indexPath = Common.ConfigurationHelper.AppSettingMapPath("IndexPath");
    
    /// <summary>
    /// 创建索引文件或更新索引文件
    /// </summary>
    /// <param name="item">索引信息</param>
    public static void CreateIndex(Model.HelperModel.IndexFileHelper item)
    {
      try
      {
        //索引存储库
        FSDirectory directory = FSDirectory.Open(new System.IO.DirectoryInfo(indexPath), new NativeFSLockFactory());
        //判断索引是否存在
        bool isUpdate = IndexReader.IndexExists(directory);
        if (isUpdate)
        {
          //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁
          if (IndexWriter.IsLocked(directory))
          {
            //解锁索引库
            IndexWriter.Unlock(directory);
          }
        }
        //创建IndexWriter对象,添加索引
        IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
        //获取新闻 title部分
        string title = item.FileTitle;
        //获取新闻主内容
        string body = item.FileContent;
        //为避免重复索引,所以先删除number=i的记录,再重新添加
        //尤其是更新的话,更是必须要先删除之前的索引
        writer.DeleteDocuments(new Term("id", item.FileName));
        //创建索引文件 Document
        Document document = new Document();
        //只有对需要全文检索的字段才ANALYZED
        //添加id字段
        document.Add(new Field("id", item.FileName, Field.Store.YES, Field.Index.NOT_ANALYZED));
        //添加title字段
        document.Add(new Field("title", title, Field.Store.YES, Field.Index.NOT_ANALYZED));
        //添加body字段
        document.Add(new Field("body", body, Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_POSITIONS_OFFSETS));
        //添加url字段
        document.Add(new Field("url", item.FilePath, Field.Store.YES, Field.Index.NOT_ANALYZED));
        //写入索引库
        writer.AddDocument(document);
        //关闭资源
        writer.Close();
        //不要忘了Close,否则索引结果搜不到
        directory.Close();
        //记录日志
        logger.Debug(String.Format("索引{0}创建成功",item.FileName));
      }
      catch (SystemException ex)
      {
        //记录错误日志
        logger.Error(ex);
        throw;
      }
      catch (Exception ex)
      {
        //记录错误日志
        logger.Error(ex);
        throw;
      }
    }
 
    /// <summary>
    /// 根据id删除相应索引
    /// </summary>
    /// <param name="guid">要删除的索引id</param>
    public static void DeleteIndex(string guid)
    {
      try
      {
        ////索引存储库
        FSDirectory directory = FSDirectory.Open(new System.IO.DirectoryInfo(indexPath), new NativeFSLockFactory());
        //判断索引库是否存在索引
        bool isUpdate = IndexReader.IndexExists(directory);
        if (isUpdate)
        {
          //如果索引目录被锁定(比如索引过程中程序异常退出),则首先解锁
          if (IndexWriter.IsLocked(directory))
          {
            IndexWriter.Unlock(directory);
          }
        }
        IndexWriter writer = new IndexWriter(directory, new PanGuAnalyzer(), !isUpdate, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
        //删除索引文件
        writer.DeleteDocuments(new Term("id", guid));
        writer.Close();
        directory.Close();//不要忘了Close,否则索引结果搜不到
        logger.Debug(String.Format("删除索引{0}成功", guid));
      }
      catch (Exception ex)
      {
        //记录日志
        logger.Error(ex);
        //抛出异常
        throw;
      }
    }
  }
}

Search 通过查找索引实现搜索

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.PanGu;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Model.HelperModel;
using System;
using System.Collections.Generic;
 
namespace BLL
{
  public static class SearchBLL
  {
    //一个类中可能会有多处输出到日志,多处需要记录日志,常将logger做成static 静态变量
    /// <summary>
    /// 日志助手
    /// </summary>
    static Common.LogHelper logger = new Common.LogHelper(typeof(SearchBLL));
    /// <summary>
    /// 索引保存位置
    /// </summary>
    static string indexPath = Common.ConfigurationHelper.AppSettingMapPath("IndexPath");
    /// <summary>
    /// 搜索
    /// </summary>
    /// <param name="keywords">用户搜索的关键词</param>
    /// <returns>返回搜索的结果</returns>
    public static List<SearchResult> Search(string keywords)
    {
      try
      {
        //索引存储库
        FSDirectory directory = FSDirectory.Open(new System.IO.DirectoryInfo(indexPath), new NoLockFactory());
        //创建IndexReader对象
        IndexReader reader = IndexReader.Open(directory, true);
        //创建IndexSearcher对象
        IndexSearcher searcher = new IndexSearcher(reader);
        //新建PhraseQuery 查询对象
        PhraseQuery query = new PhraseQuery();
        //把用户输入的关键词进行拆词
        foreach (string word in SplitWord(keywords))
        {
          //添加搜索关键词
          query.Add(new Term("body", word));
        }
        //设置分词间距为100字之内
        query.SetSlop(100);
        TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);
        //根据查询条件查询结果
        searcher.Search(query, null, collector);
        //搜索到的ScoreDoc结果
        ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;
        //保存搜索结果的list
        List<SearchResult> listResult = new List<SearchResult>();
        for (int i = 0; i < docs.Length; i++)
        {
          //取到文档的编号(主键,这个是Lucene .net分配的)
          //检索结果中只有文档的id,如果要取Document,则需要Doc再去取
          //降低内容占用
          int docId = docs[i].doc;
          //根据id找Document
          Document doc = searcher.Doc(docId);
          string number = doc.Get("id");
          string title = doc.Get("title");
          string body = doc.Get("body");
          string url = doc.Get("url");
          //建立一个搜索结果对象
          SearchResult result = new SearchResult();
          result.Number = number;
          result.Title = title;
          result.BodyPreview = Preview(body, keywords);
          result.Url = url;
          //添加到结果列表
          listResult.Add(result);
        }
        if (listResult.Count == 0)
        {
          return null;
        }
        else
        {
          return listResult;
        }
      }
      catch (SystemException ex)
      {
        logger.Error(ex);
        return null;
      }
      catch (Exception ex)
      {
        logger.Error(ex);
        return null;
      }
    }
 
    /// <summary>
    /// 获取内容预览
    /// </summary>
    /// <param name="body">内容</param>
    /// <param name="keyword">关键词</param>
    /// <returns></returns>
    private static string Preview(string body, string keyword)
    {
      //创建HTMLFormatter,参数为高亮单词的前后缀
      PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter = new PanGu.HighLight.SimpleHTMLFormatter("<font color=\"red\">", "</font>");
      //创建 Highlighter ,输入HTMLFormatter 和 盘古分词对象Semgent
      PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(simpleHTMLFormatter, new PanGu.Segment());
      //设置每个摘要段的字符数
      highlighter.FragmentSize = 100;
      //获取最匹配的摘要段
      string bodyPreview = highlighter.GetBestFragment(keyword, body);
      return bodyPreview;
    }
 
    /// <summary>
    /// 盘古分词,对用户输入的搜索关键词进行分词
    /// </summary>
    /// <param name="str">用户输入的关键词</param>
    /// <returns>分词之后的结果组成的数组</returns>
    private static string[] SplitWord(string str)
    {
      List<string> list = new List<string>();
      Analyzer analyzer = new PanGuAnalyzer();
      TokenStream tokenStream = analyzer.TokenStream("", new System.IO.StringReader(str));
      Lucene.Net.Analysis.Token token = null;
      while ((token = tokenStream.Next()) != null)
      {
        list.Add(token.TermText());
      }
      return list.ToArray();
    }
  }
}

SearchResult 模型

?
1
2
3
4
5
6
7
8
9
10
11
12
13
namespace Model.HelperModel
{
  public class SearchResult
  {
    public string Number { get; set; }
 
    public string Title { get; set; }
 
    public string BodyPreview { get; set; }
 
    public string Url { get; set; }
  }
}

以上所述就是本文的全部内容了,希望大家能够喜欢。

延伸 · 阅读

精彩推荐