Lucene 索引 高亮显示

系统 4453 0

要说这高亮,还是比较好搞的,就是要引用luncene的highlight这个包,然后调用它里面的方法就可以了。主要代码如下:

TokenStream tokenStream = null ;
                            
/** 添加关键词高亮显示 start */
Document doc = hits.doc(i);
                
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter( "<span style='color:#FF0000; background-color:#FFFF00'>" , "</span>" );    
Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
                            
/** 对标题进行高亮处理 */
String title = "";
String titleTmp = doc.get( "title" );
highlighter.setTextFragmenter( new SimpleFragmenter(titleTmp.length()));
                            
if (titleTmp != null )
{    
  tokenStream = TokenSources.getAnyTokenStream(reader, hits.id(i), "title" , analyzer);
  title = highlighter.getBestFragment(tokenStream, titleTmp);
}
                            
/** 对内容进行高亮处理 */
String content = "";
String contentTmp = doc.get( "content" );
highlighter.setTextFragmenter( new SimpleFragmenter(contentTmp.length()));
                            
if (contentTmp != null )
{    
  tokenStream = TokenSources.getAnyTokenStream(reader, hits.id(i), "content" , analyzer);
  content = highlighter.getBestFragment(tokenStream, contentTmp);
}
/** 添加关键词高亮显示 end */

  上面这段代码就已经对关键词进行了高亮的处理,高亮处理后,关键词的在网页的显示效果为
  当初令我困扰的地方并不是如何实现高亮,毕竟实现高亮的代码google一下有很多,真正令我困扰的是如果将处理后的结果显示到网页上?因为我要返回一个List给前端页面, 这个List中存储的是Lucene的Document,但是在做高亮处理时,是将Docuemnt的内容取出放到了String类型的变量里,最初的时候,由于脑子一时没转过来所以一直不知道该如何在页面上显示,经过一个周末的休息,周一上班时脑子突然活络——将处理好的内容再重新封装到Lucene的Document中,不就可以像没处理前一样,添加到List里然后返回给前端页面了吗,我怎么早没想到呢。封装代码如下:

/** 重新封装Lucene的Docuemnt */
Document docTmp = new Document();
docTmp.add( new Field( "docid" , doc.get( "docid" ),Field.Store.YES, Field.Index.NO));
/** 添加title */
docTmp.add( new Field( "title" , title,Field.Store.YES, Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS));
/** 添加content */
docTmp.add( new Field( "content" , content, Field.Store.YES,Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS));
docTmp.add( new Field( "url" , doc.get( "url" ),Field.Store.YES, Field.Index.NO));
                            
/** 将封装好的Document添加到List中 */
listTmp.add(docTmp);

 

 

 

 

 

Lucene关键字高亮显示

在Lucene的org.apache.lucene.search.highlight包中提供了关于高亮显示检索关键字的工具。使用百度、Google搜索的时候,检索结果显示的时候,在摘要中实现与关键字相同的词条进行高亮显示,百度和Google指定红色高亮显示。

有了Lucene提供的高亮显示的工具,可以很方便地实现高亮显示的功能。

高亮显示,就是根据用户输入的检索关键字,检索找到该关键字对应的检索结果文件,提取对应于该文件的摘要文本,然后根据设置的高亮格式,将格式写入到摘要文本中对应的与关键字相同或相似的词条上,在网页上显示出来,该摘要中的与关键字有关的文本就会以高亮的格式显示出来。

Lucene中org.apache.lucene.search.highlight.SimpleHTMLFormatter类可以构造一个高亮格式,这是最简单的构造方式,例如:

SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");

构造方法声明为public SimpleHTMLFormatter(String preTag, String postTag),因为这种高亮格式是依赖于网页文件的,HTML文件中是以标记(tag)来标识的,即存在一个preTag和一个postTag。

上面构造的高亮格式是摘要中出现的关键字使用红色来显示,区分其它文本。

通过构造好的高亮格式对象,来构造一个org.apache.lucene.search.highlight.Highlighter实例,然后根据对检索结果得到的Field的文本内容(这里是指摘要文本)进行切分,找到与检索关键字相同或相似的词条,将高亮格式加入到摘要文本中,返回一个新的、带有格式的摘要文本,在网页上就可以呈现高亮显示。

下面实现一个简单的例子,展示实现高亮显示的处理过程。

测试类如下所示:

package org.shirdrn.lucene.learn.highlight;

import java.io.IOException;
import java.io.StringReader;

import net.teamhot.lucene.ThesaurusAnalyzer;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleFragmenter;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;

public class MyHighLighter {

private String indexPath = "F:\\index";
private Analyzer analyzer;
private IndexSearcher searcher;

public MyHighLighter(){
   analyzer = new ThesaurusAnalyzer();
}

public void createIndex() throws IOException {   // 该方法建立索引
   IndexWriter writer = new IndexWriter(indexPath,analyzer,true);
   Document docA = new Document();
   String fileTextA = "因为火烧云总是燃烧着消失在太阳冲下地平线的时刻,然后便是宁静的自然的天籁,没有谁会在这样的时光的镜片里伤感自语,因为灿烂给人以安静的舒适感。";
   Field fieldA = new Field("contents", fileTextA, Field.Store.YES,Field.Index.TOKENIZED);
   docA.add(fieldA);
  
   Document docB = new Document();
   String fileTextB = "因为带有以伤痕为代价的美丽风景总是让人不由地惴惴不安,紧接着袭面而来的抑或是病痛抑或是灾难,没有谁会能够安逸着恬然,因为模糊让人撕心裂肺地想呐喊。";
   Field fieldB = new Field("contents", fileTextB, Field.Store.YES,Field.Index.TOKENIZED);
   docB.add(fieldB);
  
   Document docC = new Document();
   String fileTextC = "我喜欢上了一个人孤独地行游,在梦与海洋的交接地带炽烈燃烧着。"+
   "因为,一条孤独的鱼喜欢上了火焰的颜色,真是荒唐地不合逻辑。";
   Field fieldC = new Field("contents", fileTextC, Field.Store.YES,Field.Index.TOKENIZED);
   docC.add(fieldC);
  
   writer.addDocument(docA);
   writer.addDocument(docB);
   writer.addDocument(docC);
   writer.optimize();
   writer.close();
}

public void search(String fieldName,String keyword) throws CorruptIndexException, IOException, ParseException{  
// 检索的方法,并实现高亮显示
   searcher = new IndexSearcher(indexPath);
   QueryParser queryParse = new QueryParser(fieldName, analyzer);     //   构造QueryParser,解析用户输入的检索关键字
   Query query = queryParse.parse(keyword);
   Hits hits = searcher.search(query);
   for(int i=0;i<hits.length();i++){
    Document doc = hits.doc(i);
    String text = doc.get(fieldName);
    SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");   
            Highlighter highlighter = new Highlighter(simpleHTMLFormatter,new QueryScorer(query));   
            highlighter.setTextFragmenter(new SimpleFragmenter(text.length()));      
            if (text != null) {   
                TokenStream tokenStream = analyzer.tokenStream(fieldName,new StringReader(text));   
                String highLightText = highlighter.getBestFragment(tokenStream, text);
                System.out.println("★高亮显示第 "+(i+1) +" 条检索结果如下所示:");
                System.out.println(highLightText);   
            }
   }
   searcher.close();
}


public static void main(String[] args) {    // 测试主函数
   MyHighLighter mhl = new MyHighLighter();
   try {
    mhl.createIndex();
    mhl.search("contents", "因为");
   } catch (CorruptIndexException e) {
    e.printStackTrace();
   } catch (IOException e) {
    e.printStackTrace();
   } catch (ParseException e) {
    e.printStackTrace();
   }
}

}

程序说明:

1、createIndex()方法:使用ThesaurusAnalyzer分析器为指定的文本建立索引。每个Document中都有一个name为contents的Field。在实际应用中,可以再构造一一个name为path的Field,指定检索到的文件的路径(本地路径或者网络上的链接地址)

2、根据已经建好的索引库进行检索。这首先需要解析用户输入的检索关键字,使用QueryParser,必须与后台使用的分析器相同,否则不能保证解析得到的查询(由词条构造)Query检索到合理的结果集。

3、根据解析出来的Query进行检索,检索结果集保存在Hits中。遍历,提取每个满足条件的Document的内容,程序中直接把它的内容当作摘要内容,实现高亮显示。在实际应用中,应该对应着一个提取摘要(或者检索数据库得到检索关键字对应的结果集文件的摘要内容)的过程。有了摘要以后,就可以为摘要内容增加高亮格式。

4、如果提取结果集文件的前N个字符串作为摘要,只需要在 highlighter.setTextFragmenter(new SimpleFragmenter(text.length())); 中设置显示摘要的字数,这里显示全部的文本作为摘要。

运行程序,结果如下所示:

词库尚未被初始化,开始初始化词库.
初始化词库结束。用时:3906毫秒;
共添加195574个词语。
★高亮显示第 1 条检索结果如下所示:
<font color='red'>因为</font> 火烧云总是燃烧着消失在太阳冲下地平线的时刻,然后便是宁静的自然的天籁,没有谁会在这样的时光的镜片里伤感自语 ,<font color='red'>因为</font> 灿烂给人以安静的舒适感。
★高亮显示第 2 条检索结果如下所示:
<font color='red'>因为</font> 带有以伤痕为代价的美丽风景总是让人不由地惴惴不安,紧接着袭面而来的抑或是病痛抑或是灾难,没有谁会能够安逸着恬然 ,<font color='red'>因为</font> 模糊让人撕心裂肺地想呐喊。
★高亮显示第 3 条检索结果如下所示:
我喜欢上了一个人孤独地行游,在梦与海洋的交接地带炽烈燃烧着。 <font color='red'>因为</font> ,一条孤独的鱼喜欢上了火焰的颜色,真是荒唐地不合逻辑。

上面的检索结果在HTML网页中,就会高亮显示关键字“因为”,显示为红色。

传智播客课程——Lucene搜索引擎
2010-04-04 11:51

http://blog.sina.com.cn/s/blog_5de48f8b0100dple.html

    Lucene不是一个现成的程序,类似文件搜索程序或web网络爬行器或是一个网站的搜索引擎。Lucene是一个软件库,一个开发工具包,而不是一个具 有完整特征的搜索应用程序。它本身只关注文本的索引和搜索。Lucene使你可以为你的应用程序添加索引和搜索能力。目前已经有很多应用程序的搜索功能是 基于 Lucene 的,比如 Eclipse 的帮助系统的搜索功能。

    Lucene 采用的是一种称为反向索引(inverted index)的机制。反向索引就是说我们维护了一个词/短语表,对于这个表中的每个词/短语,都有一个链表描述了有哪些文档包含了这个词/短语。这样在用户输入查询条件的时候,就能非常快的得到搜索结果。

    文档建立好索引后,就可以在这些索引上面进行搜索了。搜索引擎首先会对搜索的关键词进行解析,然后再在建立好的索引上面进行查找,最终返回和用户输入的关键词相关联的文档。
   今天在传智播客的课堂上,汤阳光老师教我们实现了简单的Lucene搜索引擎,使我们能够对大量的文档实现不同需求的查找。以下是我的总结。


1. 准备环境:添加jar包
  lucene-core-2.4.0.jar(核心);
  lucene-analyzers-2.4.0.jar(分词器);
  lucene-highlighter-2.4.0.jar(高亮器);


2. 构造IndexWriter。IndexWriter是Lucene用来创建索引的一个核心的类。使用构造方法IndexWriter(Directory d, Analyzer a, MaxFieldLength mfl); 如果索引不存在,就会被创建。

* 相关参数说明

<1> Directory,代表了 Lucene 的索引的存储的位置。这是一个抽象类,常用的有两个实现,第一个是 FSDirectory,它表示一个存储在文件系统中的索引位置。第二个是 RAMDirectory,它表示一个存储在内存当中的索引位置。

<2> Analyzer,在一个文档被索引之前,首先需要对文档内容进行分词处理,这部分工作就是由 Analyzer 来做的。Analyzer 类是一个抽象类,它有多个实现。针对不同的语言和应用需要选择适合的 Analyzer。Analyzer 把分词后的内容交给IndexWriter 来建立索引。

<3> MaxFieldLength,用于限制Field的大小。这个变量可以让用户有计划地对大文档Field进行截取。假如取值为10000,就只索引每个 Field的前10000个Term(关键字)。也就是说每个Field中只有前10000个Term(关键字)建立索引,除此之外的部分都不会被 Lucene索引,当然也不能被搜索到。


3. 创建索引,使用方法IndexWriter.addDocument(Document doc)。

* 相关参数说明

<1> Document,是用来描述Lucene文档结构的。任何需要进行索引的数据都必须转化成Document对象。Document是索引和搜索的最基本单元,是一组Field的集合。

<2> Field,组成Document的元素,用来描述一个文档的某个属性的,比如一封电子邮件的标题和内容可以用两个Field对象分别描述。Field是 由name和value组成的,value只接受字符串(非字符串类型要先转换成字符串才行)。在构造Field时要指定Store和Index。

    Field.Store,指定Field是否或怎样存储。
   Store.NO,不存储。
   Store.YES,存储。
   Store.COMPRESS,压缩后存储(数据量很大的时候可以使用,但要考虑效率)。

    Field.Index,指定Field是否或怎么被索引。
   Index.NO,不索引(不索引就不能被搜索到)。
   Index.ANALYZED,(以前版本中为TOKENIZED),分词后索引。
   Index.NOT_ANALYZED,(以前版本中为UN_TOKENIZED),不分词,直接索引(把整个Field做为一个term)。

注意:当完成了索引操作后,一定要调用IndexWriter.close()方法。


4,删除索引:

IndexWriter.deleteDocuments(Term term);

会删除索引文件里含有指定Term的所有Document。Term,是搜索的基本单位。代表某个Field中出现的某个关键字。 


5,更新索引:

IndexWriter.updateDocument(Term term, Document doc);

实际上是先删除再创建索引,就是说如果有多条符合条件的Document,更新后只有一条。


6,搜索:

使用类IndexSearcher。查询方法为:

IndexSearcher.search(Query, Filter, int);

相关参数说明:
<1> Query,查询对象,把用户输入的查询字符串封装成Lucene能够识别的Query。
<2> Filter,用来过虑搜索结果。
<2> 第三个参数(int类型),最多返回的Document的数量。
返回的是一个TopDocs类型,调用TopDocs.scoreDocs得到查询结果。

ScoreDoc.doc返回文档的内部编号。
IndexSearcher.doc(hits[i].doc) 通过编号,拿到文档。

Query可以用QueryParser解析查询字符串生成。使用构造方法为QueryParser(String defaultFieldName, Analyzer a),第一个参数为默认查询的Field,第二个参数为使用的分词器(这里用的分词器要和建立索引时用的分词器一致,否则可能会搜索不到结果)。使用parse(String)方法解析查询内容。

相关代码:

List<Document> docs = new ArrayList<Document>();
 IndexSearcher indexSearcher = null;
 try {
  indexSearcher = new IndexSearcher(dir);

   Filter filter = null;
  int nDocs = 10000;

   TopDocs topDocs = indexSearcher.search(query, filter, nDocs);
  System.out.println("共有【" + topDocs.totalHits + "】条匹配记录");

   for (int i = 0; i < topDocs.totalHits; i++) {
   ScoreDoc scoreDoc = topDocs.scoreDocs[i];
   int docNum = scoreDoc.doc; // 文档在索引库中的编号
    Document doc = indexSearcher.doc(docNum);
// 通过编号取出相应的文档

    docs.add(doc);
  }

   return new SearchResult(topDocs.totalHits, docs);
 } catch (Exception e) {
  throw new RuntimeException(e);
 } finally {
  try {
   indexSearcher.close();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }


7,测试LuceneIndexDao的增删改查方法,把LuceneIndexDao做为练习,要求通过LuceneIndexDaoTest中的单元测试,相关的测试代码由于比较多,暂不一一列出。

8. 下面是对一些重要的类及术语进行解释:

<1> Directory,有两个:
a) FSDirectory,将索引放到磁盘中。
b) RAMDirectory,将索引放到内存中。速度快,但是在jvm退出之后,内存中的索引就不存在了。可以在jvm退出之前调用另一个使用FSDirectory的IndexWriter 把内存中的索引转存到文件系统中。
   相应的API为IndexWriter.addIndexesNoOptimize(Directory[]),注意这个调用代码要放在内存中索引的操作 RAMDirectory的IndexWriter关闭后,以便所有的document都进入RAMDirectory。在创建RAMDirectory 的实例时,可以使用无参的构造方法,或是有一个参数的构造方法:可以指定一个文件路径,以便加载磁盘中的索引到内存中。
<2> 相关度排序,Lucene的搜索结果默认按相关度排序的,所谓相关度,就是文档的得分。Lucene有一个评分机制,就是对检索结果按某种标准进行评估,然后按分值的高低来对结果进行排序。

  a) 文档的得分与用户输入的关键字有关系,而且是实时运算的结果。得分会受关键字在文档中出现的位置与次数等的影响。

  b) 可以利用Boost影响Lucene查询结果的排序,通过设置Document的Boost来影响文档的权重,以达到控制查询结果顺序的目的:Document.setBoost(float)。默认值为1f,值越大,得分越高。

  c) 也可以在查询时给Filed指定boost。当同样的term属于不同的field时,如果field的boost不一样,其所属的文件的得分也不一样。
<3> Analyzer,分词器,对文本资源进行切分,将文本按规则切分为一个个可以进行索引的最小单位(关键字)。

    对于英文的分词,是按照标点、空白等拆分单词,比较简单;而中文的分词就比较复杂了,如”中华人民共和国“可以分为”中华”、“人民”、“共和国”,但不应有“华人”这个词(不符合语义)。

    中文分词几种常用的方式:
a) 单字分词,就是按照中文一个字一个字地进行分词。如:我们是中国人,效果:我\们\是\中\国\人。(StandardAnalyzer就是这样)。
b) 二分法:按两个字进行切分。如:我们是中国人,效果:我们\们是\是中\中国\国人。(CJKAnalyzer就是这样)。
c) 词库分词:按某种算法构造词然后去匹配已建好的词库集合,如果匹配到就切分出来成为词语。通常词库分词被认为是最理想的中文分词算法如:我们是中国人,效果为:我们\是\中国\中国人。
<4> Highlighter,高亮器,用于高亮显示匹配的关键字。
包含三个主要部分:
1) 格式化器:Formatter(使用SimpleHTMLFormatter,在构造时指定前缀和后缀)。
2) 计分器:Scorer(使用QueryScorer)。
3) 段划分器:Fragmenter(使用SimpleFragmenter,在构造时指定关键字所在的内容片段的长度)。

通过方法getBestFragment(Analyzer a, String fieldName,String text)实现高亮。(如果进行高亮的field中没有出现关键字,返回null)。
<5> 查询,有两种方式:通过Query Parser解析查询字符串或使用API构建查询。
使用Query Parser时不匹分大小写。以下是常用的查询:

  1) TermQuery,按Term(关键字)查询(term的值应是最终的关键字,英文应全部小写)。
 
TermQuery(Term t);
  syntax: propertyName:keyword;

  2) RangeQuery,指定范围查询。
  RangeQuery(Term lowerTerm, Term upperTerm, boolean inclusive);
   syntax: propertyName:[lower TO upper] (包含lower和upper);
  syntax: propertyName:{lower TO upper} (不包含lower和upper);

  3) PrefixQuery,前缀查询。
 
PrefixQuery(Term prefix);
  syntax:propertyName:prefix*;

  4) WildcardQuery,通配符查询,可以使用"?"代表一个字符,"*"代表0个或多个字符。(通配符不能出现在第一个位置上)
  WildcardQuery(Term term);
  syntax: propertyName:chars*chars?chars

  5) MultiFieldQueryParser,在多个Field中查询。
  queryParser = MultiFieldQueryParser(String[] fields, Analyzer analyzer);
  Query query = parse(String query);  
  6) BooleanQuery,布尔查询。这个是一个组合的Query,可以把各种Query添加进去并标明他们的逻辑关系。
  TermQuery q1 = new TermQuery(new Term("title","javadoc"));
  TermQuery q2 = new TermQuery(new Term("content","term"));
  BooleanQuery boolQuery = new BooleanQuery();
  boolQuery.add(q1, Occur.MUST);
  boolQuery.add(q2, Occur.MUST_NOT);

  Occur.MUST,必须出现。
  Occur.MUST_NOT,必须未出现。
  Occur.SHOULD,只有一个SHOULD时为必须出现,多于一个时为或的关系。

   syntax: + - AND NOT OR (必须为大写)。

Lucene 索引 高亮显示


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论