共计 10168 个字符,预计需要花费 26 分钟才能阅读完成。
0 引言
随着万维网的发展和大数据时代的到来,每天都有大量的数字化信息在生产、存储、传递和转化,如何从大量的信息中以一定的方式找到满足自己需求的信息,使之有序化并加以利用成为一大难题。全文检索技术是现如今最普遍的信息查询应用,生活中利用搜索引擎,在博客论坛中查找信息,这些搜索的核心原理就是本文要实现的全文检索技术。随着文档信息数字化的实现,将信息有效存储并及时准确的提取是每一个公司、企业和单位要做好的基础。针对英文的全文检索已经有很多成熟的理论和方法,开放源代码的全文检索引擎 Lucene 是 Apache 软件基金会 Jakarta 项目组的一个子项目,它的目的是为软件开发人员提供一个简单易用的工具包,方便在目标系统中实现全文检索的功能。Lucene 不支持中文,但是目前已有很多开源的中文分词器可以对中文内容进行索引,本文在研究 Lucene 核心原理的基础上,分别实现了对中英文网页的爬取和检索。
1 Lucene 介绍
1.1 lucene 简介
Lucene 是一个用 Java 写的全文检索引擎工具包,实现构造了索引和搜索两大核心功能,并且两者相互独立,这使得开发人员可以方便扩展,Lucene 提供了丰富的 API , 可以与存储在索引中的信息方便的交互。需要说明的是它并不是一个完整的全文检索应用, 而是为应用程序提供索引和搜索功能。即若想让 Lucene 真正起作用, 还需在其基础上做一些必要的二次开发。
Lucene 的结构设计与数据库的设计较为相似,但 Lucene 的索引与数据库有着极大的不同。数据库和 Lucene 建立索引都是为了查找方便,但是数据库仅仅针对部分字段进行建立,且需要把数据转化为格式化信息,并予以保存。而全文检索是将全部信息按照一定方式进行索引。两种检索的不同和相似如表 1 - 1 所示。
表 1 -1:数据库检索与 Lucene 检索对比
比较项 |
Lucene 检索 |
数据库检索 |
数据检索 |
从 Lucene 的索引文件中检出 |
由数据库索引检索记录 |
索引结构 |
Document(文档) |
Record(记录) |
查询结果 |
Hit:满足关系的文档组成 |
查询结果集:包含关键字的记录组成 |
全文检索 |
支持 |
不支持 |
模糊查询 |
支持 |
不支持 |
结果排序 |
设置权重,进行相关性排序 |
不能排序 |
1.2 lucene 总体结构
Lucene 软件包的发布形式是一个 JAR 文件,版本更新较快且版本差距较大,本文使用的是 5.3.1 的版本,主要使用的子包如表 1 - 2 所示。
表 1 -2:子包和功能
包名 |
功能 |
Org .apache.lucene .analysis |
分词 |
Org .apache.lucene .document |
对索引管理的文档 |
Org .apache.lucene .index |
索引操作,包括增加、删除等 |
Org .apache.lucene .queryParser |
查询器,构造检索表达式 |
Org .apache.lucene .search |
检索管理 |
Org .apache.lucene .store |
数据存储管理 |
Org .apache.lucene .util |
公共类 |
1.3 lucene 架构设计
Lucene 功能非常强大,但从根本上来说,主要包括两块:一是从文本内容切分词后索引入库;二是根据查询条件返回结果,即建立索引和进行查询两部分。
如图 1 - 1 所示,本文抛出外部接口以及信息来源,重点对网页爬取的文本内容进行索引和查询。
图 1 -1:Lucene 的架构设计
2 JDK 的安装和环境变量的配置
1.jdk 的下载:
在 Oracle 官网下载符合系统版本的压缩包,网址如下。点击安装,根据提示进行安装,在安装过程中会提示是否安装 jre,点击是。http://www.oracle.com/technetwork/java/javase/downloads/index.html
2. 设置环境变量:
(1)右键计算机 =》属性 =》高级系统设置 =》环境变量 =》系统变量 =》新建 =》JAVA_HOME: 安装路径
(2)Path 中新增 =》%JAVA_HOME%\bin
3. 测试是否成功:
开始 =》运行 =》CMD 回车 在弹出的 DOS 窗口内
输入:java -version 会出现版本信息,
输入:javac 出现 javac 的用法信息
出现如图 2 - 1 所示为成功。
图 2 -1:cmd 命令框测试 java 配置
3 编写 Java 代码实现对网页内容的获取
因为 Lucene 针对不同语言要使用不同的分词器,英文使用标准分词器,中文选择使用 smartcn 分词器。在获取网页的时候,先获取网页存为 html 文件,在 html 中由于标签 的干扰,会对检索效果产生影响,因此需要对 html 标签进行剔除,并将文本内容转为 txt 文件进行保存。中英文除了分词器不同,其他基本一致,因此之后的代码和实验结果演 示会选择任一。本文选取五十篇中文故事和英文故事的网页为例。
具体代码设计如下图:Url2Html.java 将输入网址的网页转存为 html 文件,Html2Txt.java 文件实现 html 文档标签的去除,转存为 txt 文档。具体代码如图 3 - 1 和 3 -2。
public void way(String filePath,String url) throws Exception{File dest = new File(filePath);// 建立文件
InputStream is;// 接收字节输入流
FileOutputStream fos = new FileOutputStream(dest);// 字节输出流
URL wangzhi = new URL(url);// 设定网址 URL
is = wangzhi.openStream();
BufferedInputStream bis = new BufferedInputStream(is);// 为字节输入流加缓冲
BufferedOutputStream bos = new BufferedOutputStream(fos);// 为字节输出流加缓冲
/*
* 对字节进行读取
*/
int length;
byte[] bytes = new byte[1024*20];
while((length = bis.read(bytes, 0, bytes.length)) != -1){fos.write(bytes, 0, length);
}
/*
* 关闭缓冲流和输入输出流
*/
bos.close();
fos.close();
bis.close();
is.close();}
public String getBody(String val){
String zyf = val.replaceAll(“</?[^>]+>”, “”); // 剔出 <html> 的标签
return zyf;
}
public void writeTxt(String Str,String writePath) {
File writename = new File(writePath);
try {
writename.createNewFile();
BufferedWriter out = new BufferedWriter(new FileWriter(writename));
�� out.write(Str);
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
以童话故事《笨狼上学》的网页为例,文档路径设为”E:\work \lucene \test \data \html”和”E:\work\lucene\test\data\txt”,在每一次读取网页的时候需要设定的两个参数为文件命名 filename 和获取目标网址 url。新建一个 main 函数,实现对两个方法的调用。具体实现如图 3 - 3 所示:
public static void main(String[] args) {
String filename = “jingdizhi”;// 文件名字
String url = “http://www.51test.net/show/8072125.html”;// 需要爬取的网页 url
String filePath = “E:\\work\\lucene\\test\\data\\html\\”+filename+”.html”;// 写出 html 的文件路径 + 文件名
String writePath = “E:\\work\\lucene\\test\\data\\txt\\”+filename+”.txt”;// 写出 txt 的文件路径 + 文件名
Url2Html url2html = new Url2Html();
try {
url2html.way(filePath,url);
} catch (Exception e) {
e.printStackTrace();
}
Html2Txt html2txt = new Html2Txt();
String read=html2txt.readfile(filePath);// 读取 html 文件
String txt = html2txt.getBody(read);// 去除 html 标签
System.out.println(txt);
try {
html2txt.writeTxt(txt,writePath);
} catch (Exception e) {
e.printStackTrace();
}
}
执行程序后,分别在两个文件夹中建立”笨狼上学.html”和”笨狼上学.txt”。
4 建立索引
索引和查询的基本原理如下:
建立索引:搜索引擎的索引其实就是实现“单词 - 文档矩阵”的具体数据结构。也是进行全文检索的第一步,lucene 提供 IndexWriter 类进行索引的管理,主要包括 add()、delete()、update()。还有对权值的设定,通过不同索引权值的设定,可以在搜索的时候根据相关性大小进行返回。
进行搜索:原本的直接搜索是针对文档进行顺序检索,在建立索引之后,可以通过对索引的查找以找到索引词在文档中出现的位置,然后返回索引项所对的文档中的位置和词。Lucene 提供 IndexSearcher 类进行对文档的检索,检索形式主要分为两类,第一类是 Term,针对单个词项的检索;第二类是 Parser,可以自定义构造检索表达式,有较多的检索形式,具体的方法会在之后进行实现的演示。
4.1 实验环境
本 PC 机采用 windows 10×64 系统,8G 内存,256G 固态硬盘。开发环境为 Myeclipse 10,jdk 版本为 1.8。在实验过程中,因为部分语法的转变,若干 Class 采用 1.6 版本实现。
4.2 建立索引
建立索引库就是往索引库添加一条条索引记录,Lucene 为添加一条索引记录提供了接口,添加索引。
主要用到了“写索引器”、“文档”、“域”这 3 个类。要建立索引,首先要构造一个 Document 文档对象,确定 Document 的各个域,这类似于关系型数据库中表结构的建立,Document 相当于表中的一个记录行,域相当于一行中的列,在 Lucene 中针对不同域的属性和数据输出的需求,对域还可以选择不同的索引 / 存储字段规则,在本实验中,文件名 fileName、文件路径 fullPath 和文本内容 content 作为 Document 的域。
IndexWriter 负责接收新加入的文档,并写入索引库中。在创建“写索引器”IndexWriter 时需要指定所使用的语言分析器。建立索引分为两个类别,第一:不加权索引;第二:加权索引。
public Indexer(String indexDir)throws Exception{Directory dir=FSDirectory.open(Paths.get(indexDir));
Analyzer analyzer=new StandardAnalyzer(); // 标准分词器
//SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
writer=new IndexWriter(dir, iwc);
}
设置索引字段,Store 表示是否对索引内容存储:fileName 和 fullPath 占用内存较少可以进行存储,以方便查询返回。
private Document getDocument(File f)throws Exception {Document doc=new Document();
doc.add(new TextField("contents", new FileReader(f)));
doc.add(new TextField("fileName", f.getName(),Store.YES));
doc.add(new TextField("fullPath",f.getCanonicalPath(),Store.YES));// 路径索引
return doc;
}
执行主代码后结果如图:设计在索引某个文件的时候返回文件“索引文件:+ 文件路径”,且计算输出索引全部文件花费的时间。
4.3 对索引的删除和修改
一般对数据库的操作包括 CRUD(增加、删除、更改、查询),增加就是对索引项的选择和建立,查询作为较为核心的功能会在之后展开论述,这里主要记录一下在删除、更新索引时用到的方法。
删除分为两种类型,包括普通的删除和彻底删除,因为索引的删除影响到整个数据库,而且对于大型的系统而言,删除索引意味着对系统的底层进行更改,耗时耗力而且无法返回,前面索引的时候看到建立索引后生成若干小文件,当进行查找的时候会将各个文件进行合并然后查找。普通删除仅仅是对之前建立的索引做个简单的标记,致使无法进行查找返回。彻底删除则是对索引进行销毁,无法撤销。以删除索引项“id”为 1 的索引为例:
普通的删除 (在合并前删除):
writer.deleteDocuments(new Term("id","1"));
writer.commit();
彻底的删除(在合并后删除):
writer.deleteDocuments(new Term("id","1"));
writer.forceMergeDeletes(); // 强制删除
writer.commit();
对索引的修改原理比较简单,就是在原有索引的基础上实现覆盖,实现代码跟上文的增加索引一样,在此不多做阐述。
4.4 对索引的加权
Lucene 默认按照相关度排序,Lucene 对 Field 提供了一个可以设置的 Boosting 参数,这个参数用来表示记录的重要性,在满足搜索条件是,会优先考虑重要性高的记录,返回结果靠前,如果记录较多,权值低的记录会排到首页之后,因此,对索引的加权操作是影响返回结果满意度的重要因素,在实际设计信息系统的时候,应该有严格的权值计算公式,方便对 Field 权值的更改,更好的满足用户的需求。
例如搜索引擎将点击率高,链入链出的网页给定较高的权重,在返回的时候排到第一页。实现代码如图 4 - 1 所示,不加权和加权结果对比如图 4 - 2 所示。
TextField field = new TextField("fullPath", f.getCanonicalPath(), Store.YES);
if("A GREAT GRIEF.txt".equals(f.getName())){field.setBoost(2.0f);// 对文件名为 secondry story.txt 的 fullPath 路径加权;
} // 默认权重为 1.0,改为 1.2 即增加权重。
doc.add(field);
图 4 -1:索引加权
图 4 -2:加权之前
图 4 -2:加权之后
由图 4 - 2 结果可以看出,不加权时,按照字典顺序排列返回,因此 first 在 secondry 之前,在对 secondry 命名的文件路径加权后,返回的时候顺序发生变化,实现对权重的测试。
5 进行查询
Lucene 的检索接口主要由 QueryParser、IndexSearcher、Hits 这 3 个类构成,QueryParser 是查询解析器,负责解析用户提交的查询关键字,在新建一个解析器时需要指定要解析的域和使用什么语言分析器,这里使用的语言分析器必须与索引库建立时使用的解析器相同,否则查询结果不正确。IndexSearcher 是索引搜索器,在实例化 IndexSearcher 时需要指定索引库所在的目录,IndexSearcher 有一个 search 方法执行索引的检索,这个方法接受 Query 作为参数,返回 Hits,Hists 是一系列排好序的查询结果的集合,集合的元素是 Document。通过 Document 的 get 方法可以得到与这个文档对应文件的信息,比如:文件名、文件路径、文件内容等。
5.1 基本查询
如图查询主要有两种方式,但是推荐使用第一种构造 QueryParser 表达式,它可以有灵活的组合方式,包括布尔逻辑表达、模糊匹配等,但是第二种 Term 只能针对词汇查询。
1. 构造 QueryParser 查询式:
QueryParser parser=new QueryParser("fullPath", analyzer);
Query query=parser.parse(q);
2. 对特定项的查询:
Term t = new Term("fileName", q);
Query query = new TermQuery(t);
查询结果如图 5 - 1 所示:以查询文件名 fileName 包含“大”为例。
图 5 -1:“大”查询结果
5.2 模糊查询
在构造 QueryParser 时,通过对词项 q 的修改可以实现精确匹配和模糊匹配。模糊匹配通过在“q”之后加“~”进行修改。如图 5 - 2 所示:
图 5 -2:模糊匹配
5.3 限定条件查询
布尔逻辑查询和模糊查询只需要对查询词 q 进行更改,而限定条件查询需要对 query 表达式进行设定,主要分为以下几类:
分别为指定项范围搜索、指定数字范围、指定字符串开头和多条件查询,分别列出应用的查询,true 参数指的:是否包含上限和下限在内。
指定项范围:
TermRangeQuery query=new TermRangeQuery("desc", new BytesRef("b".getBytes()), new BytesRef("c".getBytes()), true, true);
指定数字范围:
NumericRangeQuery<Integer> query=NumericRangeQuery.newIntRange("id", 1, 2, true, true);
指定字符串开头:
PrefixQuery query=new PrefixQuery(new Term("city","a"));
多条件查询:
NumericRangeQuery<Integer>query1=NumericRangeQuery.newIntRange("id", 1, 2, true, true);
PrefixQuery query2=new PrefixQuery(new Term("city","a"));
BooleanQuery.Builder booleanQuery=new BooleanQuery.Builder();
booleanQuery.add(query1,BooleanClause.Occur.MUST);
booleanQuery.add(query2,BooleanClause.Occur.MUST);
5.4 高亮查询
在百度、谷歌等搜索引擎中,进行查询时,返回的网页包含查询关键字的时候会显示为红色,且进行摘要显示,即对包含关键字的部分内容进行截取并返回。高亮查询即为实现对关键字的样式更改,本实验在 myeclipse 中进行,返回结果并���会有样式的改变,只会对返回内容的关键字添加 html 标签,如果显示到网页即产生样式的变化。
高亮的设置代码如图 5 - 3 所示,结果如图 5 - 4 所示,会对南京匹配词添加 <b> 和 <font> 标签,显示到网页上为加粗和变红。
QueryScorer scorer=new QueryScorer(query);
Fragmenter fragmenter=new SimpleSpanFragmenter(scorer);
SimpleHTMLFormatter simpleHTMLFormatter=new SimpleHTMLFormatter("<b><font color='red'>","</font></b>");
Highlighter highlighter=new Highlighter(simpleHTMLFormatter, scorer);
highlighter.setTextFragmenter(fragmenter);
图 5 -3:高亮设置
图 5 -4:高亮显示结果
6 实验过程中遇到的问题和不足
Lucene 版本更新较快,在 jdk 版本、eclipse 版本和 lucene 版本之间需要一个良好的衔接,否则会造成很多的不兼容,在调试版本以及 jdk1.6 和 jdk1.8 的选择上出现很多困难,比如网页抓取中的 append 方法在 1.8 版本已经删除,不能使用。但是对文档路劲的读取 FSDirectory.open() 则需要 jdk1.8 才支持。
本实验的不足之处主要表现在:
代码的灵活性较低,在爬取网页的时候需要手工进行,且需要对中文和英文分别进行,应该完善代码使得对网页的语言有个判定,然后自动选择执行不同的分词器。
代码的复用性较低,没有较为合理的分类和方法的构建,为了简便,基本在几个核心代码中进行注释和标记而实现效果,有待改进。
代码的可移植性较低,对网页的爬取使用的是 jdk1.6 的版本,Lucene 的实现使用的是 jdk1.8 的版本,在导出到其他机器上,需要对环境稍加修改和配置,无法实现一键式操作。
7 总结
本文从 Lucene 的原理出发,了解了全文检索的思路和方法,并对常用的功能进行了实验和测试。在实验的过程中,了解了搜索引擎的原理,基于信息检索课程的内容上,有了一个更好的实操体验。Lucene 是一个优秀的开源全文本搜索技术框架,通过对它的深入研究,对其实现机制更加熟悉,在研究它的过程中学习了很多面向对象的编程方法和思想,它良好的系统框架和扩展性值得学习借鉴。
————————————– 分割线 ————————————–
基于 Lucene 多索引进行索引和搜索 http://www.linuxidc.com/Linux/2012-05/59757.htm
Lucene 实战 (第 2 版) 中文版 配套源代码 http://www.linuxidc.com/Linux/2013-10/91055.htm
Lucene 实战 (第 2 版) PDF 高清中文版 http://www.linuxidc.com/Linux/2013-10/91052.htm
使用 Lucene-Spatial 实现集成地理位置的全文检索 http://www.linuxidc.com/Linux/2012-02/53117.htm
Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a9 http://www.linuxidc.com/Linux/2012-02/53113.htm
Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a8 http://www.linuxidc.com/Linux/2012-02/53111.htm
Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a7 http://www.linuxidc.com/Linux/2012-02/53110.htm
Lucene 实践心得笔记 http://www.linuxidc.com/Linux/2017-03/141902.htm
Project 2-1: 配置 Lucene, 建立 WEB 查询系统 [Ubuntu 10.10] http://www.linuxidc.com/Linux/2010-11/30103.htm
Lucene 的配置及创建索引全文检索 http://www.linuxidc.com/Linux/2017-09/146773.htm
————————————– 分割线 ————————————–
Lucene 的详细介绍 :请点这里
Lucene 的下载地址 :请点这里
本文永久更新链接地址 :http://www.linuxidc.com/Linux/2017-12/149793.htm