全文检索工具包 Lucene 入门教程
1.什么是 Lucene
Apache Lucene 是完全用 Java 编写的高性能,功能齐全的,全文检索引擎工具包,通过 lucene 可以让程序员快速开发一个全文检索功能。
1.1 什么是全文检索
在我们的生活中数据总体分为两种:结构化数据 和非结构化数据 。
结构化数据: 指具有固定格式或有限长度的数据,如数据库,元数据等。
非结构化数据: 指不定长或无固定格式的数据,如邮件,word 文档等。非结构化数据又叫全文数据。
当然有的地方还会提到第三种,半结构化数据,如 XML,HTML 等,半结构化数据可以根据需要按结构化数据来处理,也可以抽取出纯文本按非结构化数据来处理。
按照数据的分类,搜索也分为两种:
搜索结构化数据 :如对数据库的搜索,用 SQL 语句。再如对元数据的搜索,如利用 windows 搜索对文件名,类型,修改时间进行搜索等。
搜索非结构化数据 :如利用 windows 的搜索也可以搜索文件内容,Linux 下的 grep 命令,再如用 Google 和百度可以搜索大量内容数据。
对非结构化数据的搜索,即对全文数据的搜索主要有两种方法:
一种是顺序扫描法 (Serial Scanning): 比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。如利用 windows 的搜索也可以搜索文件内容,只是相当的慢。如果你有一个 80G 硬盘,如果想在上面找到一个内容包含某字符串的文件,需要花费很长的时间。Linux 下的 grep 命令也是这样一种方式。对于小数据量的文件,这种方法还是最直接,最方便的,但是对于大量的文件,这种方法就很慢了。
对非结构化数据顺序扫描很慢,对结构化数据的搜索却相对较快(由于结构化数据有一定的结构可以采取一定的搜索算法加快速度),那么我们把非结构化数据转化得有一定结构不就行了吗?
其实这就是全文检索的基本思路,即将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对有一定结构的数据进行搜索,从而达到快速搜索非结构化数据的目的。
这部分从非结构化数据中提取出来,然后重新组织的信息,我们称之索引 。例如字典,字典的拼音表和部首检字表就相当于字典的索引,由于对每一个字的解释都是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描,即一页一页进行查找。然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——即对字的解释说明。
总结:全文检索首先将要查询的目标数据中的词提取出来,组成索引,通过查询索引达到搜索目标数据的目的。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
1.2 全文检索的应用场景
对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,比如百度、Google 等搜索引擎、论坛站内搜索、电商网站站内搜索等。
1.3. 如何实现全文检索
可以使用 Lucene 实现全文检索。Lucene 是 apache 下的一个开放源代码的全文检索引擎工具包,它可以为应用程序提供多个 api 接口去调用,可以简单理解为是一套实现全文检索的类库。
2.Lucene 实现全文检索的流程
2.1. 创建索引和搜索流程图
全文检索的流程分为两大部分:索引流程、搜索流程。
索引流程:确定原始内容即要搜索的内容->采集原始内容数据->创建文档->分析文档(分词)->创建索引
搜索流程:用户通过搜索界面->创建查询->执行搜索->从索引库搜索->渲染搜索结果
2.2. 创建索引
将用户要搜索的数据内容进行索引,索引存储在索引库(index)中的过程。
采集数据技术有哪些?
1、对于互联网上的网页,通过 http 协议抓取 html 网页内容到本地。
2、针对电商站内搜索功能,全文检索的数据在数据库中,需要通过 jdbc 访问数据库表中的内容。
3、如果数据是文件系统中的某个文件,就通过 I/O 读取文件的内容。
这里我们以搜索磁盘上的文本文件为例,凡是文件名或文件内容包括关键字(albert)的文件都要找出来,这里要对文件名和文件内容创建索引。
2.2.1. 创建文档对象
获取原始数据的目的是为了创建索引,在创建索引前需要将原始数据创建成文档(Document),文档中包括一个一个的域(Field),域中存储原始数据的内容。这里可以把 Document 理解为数据库表中的一条记录,可以把域理解为数据库中的字段。
可以将磁盘上的一个文件当成一个 document,Document 中包括一些 Field(file_name 文件名称、file_path 文件路径、file_size 文件大小、file_content 文件内容),如下图:
2.2.2. 索引文件的逻辑结构
文档:
对非结构化的数据统一格式为 document 文档格式,一个文档有多个 field 域,不同的文档其 field 的个数可以不同,建议相同类型的文档包括相同的 field。 本例子一个 Document 对应一个磁盘上的文件。
索引域:
用于搜索程序从索引域中搜索一个一个词,根据词找到对应的文档。将 Document 中的 Field 的内容进行分词,将分好的词创建索引,索引=Field 域名:词。
2.2.2. 分析文档(分词)
将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。
分词 主要过程就是分词和过虑两步:
分词:就是将采集到的文档内容切分成一个一个的词,具体应该说是将 Document 中 Field 的 value 值切分成一个一个的词。
过虑:包括去除标点符号、去除停用词(的、是、a、an、the 等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式。。。)等。
什么是停用词?停用词是为节省存储空间和提高搜索效率,搜索引擎在索引页面或处理搜索请求时会自动忽略某些字或词,这些字或词即被称为 Stop Words(停用词)。比如语气助词、副词、介词、连接词等,通常自身并无明确的意义,只有将其放入一个完整的句子中才有一定作用,如常见的“的”、“在”、“是”、“啊”等。
比如下边的文档经过分析器分析如下:
原文档内容:
Lucene is a Java full-text search engine.
分析后得到的语汇单元(Token):
lucene、java、full、text、search、engine
2.2.3. 创建索引
对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到 Document(文档)。
注意:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构。
倒排索引表
传统方法是先找到文件,然后在文件中找内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大比较大的时候。搜索很慢。
倒排索引结构是根据内容(词语)找文档,倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它是在索引中匹配搜索关键字,由于索引内容量有限并且采用固定优化算法搜索速度很快,找到了索引中的词汇,词汇与文档关联,从而最终找到了文档。
倒排索引结构是根据内容(词语)找文档,如下图:
2.3. 查询索引
查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档。
和索引过程的分词一样,搜索时也要对用户输入的关键字进行分词,一般情况索引和搜索使用的分词器一致。比如:输入搜索关键字“Lucene 教程”,分词后为 Lucene 和教程两个词,与 Lucene 和教程有关的内容都会被搜索出来。
3.Lucene 搜索案例
3.1 需求分析
为磁盘上的文本文件创建索引,然后进行查找,凡是文件名或文件内容包括关键字(albert)的文件都要找出来,这里要对文件名和文件内容创建索引。
3.2 开发准备
3.2.1 Lucene 工具包下载
Lucene 是开发全文检索功能的工具包,使用时从官方网站下载,并解压。
官方网站:http://lucene.apache.org/ 目前最新版本:8.5.2
下载地址:http://archive.apache.org/dist/lucene/java/
API: https://lucene.apache.org/core/6_6_0/core/index.html
下载版本:6.6.0
JDK 要求:1.8(以上)
下载并解压 Lucene:
lucene-analyzers-common-6.6.0.jar:lucene-6.6.0/analysis/common
lucene-analyzers-smartcn-6.6.0.jar:lucene-6.6.0/analysis/smartcn/
lucene-core-6.6.0.jar:lucene-6.6.0/core/
lucene-highlighter-6.6.0.jar:lucene-6.6.0/highlighter/
lucene-memory-6.6.0.jar:lucene-6.6.0/memory/
lucene-queries-6.6.0.jar:lucene-6.6.0/queries/
lucene-queryparser-6.6.0.jar:lucene-6.6.0/queryparser/
3.2.2 创建工程并添加 jar 包
整体思想 :1.通过 IO 采集文件系统中的文档数据,放入 Lucene 的 Document 中
2.写入索引库()对文档(Document)进行分词并创建索引(利用 IndexWriter 对象 )
第一步:创建一个 java 工程,并导入 jar 包。
Analysis 的包
Core 包
QueryParser 包
Junit 包(非必须)(下载地址:https://github.com/junit-team/junit4/wiki/Download-and-Install)
commons-io(非必须)(下载地址:http://commons.apache.org/proper/commons-io/download_io.cgi)
3.2.3 代码实现
搜索结果:
3.2.4 使用工具查看索引
Luke 是用于内省 Lucene / Solr / Elasticsearch 索引的 GUI 工具。它允许:
浏览您的文档,索引词和过帐列表
搜索索引
执行索引维护:索引运行状况检查,索引优化(在运行此文件之前先备份一下!)
测试您的自定义 Lucene 分析器(Tokenizer / CharFilter / TokenFilter)
记住你的 lukeall 的版本和 lucene 的版本要一致不然可能会出问题。
luke 各个版本下载地址: https://github.com/DmitryKey/luke/releases
根据你的系统来选择执行不同的脚本:
3.4.5 Field 的常用类型
Field 是文档中的域,包括 Field 名和 Field 值两部分,一个文档可以包括多个 Field,Document 只是 Field 的一个承载体,Field 值即为要索引的内容,也是要搜索的内容。
是否分词(tokenized)
是:作分词处理,即将 Field 值进行分词,分词的目的是为了索引。
比如:商品名称、商品简介等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元索引。
否:不作分词处理
比如:商品 id、订单号、身份证号等
是否索引(indexed)
是:进行索引。将 Field 分词后的词或整个 Field 值进行索引,索引的目的是为了搜索。
比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。
否:不索引。该域的内容无法搜索到
比如:商品 id、文件路径、图片路径等,不用作为查询条件的不用索引。
是否存储(stored)
是:将 Field 值存储在文档中,存储在文档中的 Field 才可以从 Document 中获取。
比如:商品名称、订单号,凡是将来要从 Document 中获取的 Field 都要存储。
否:不存储 Field 值,不存储的 Field 无法通过 Document 获取
比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。
4.索引维护
4.1 添加索引
参考上边案例代码
4.2 删除索引
增删改操作,都是需要通过 IndexWriter 对象来操作,Term 是索引域中最小的单位。根据条件删除时,建议根据唯一键来进行删除。在 solr 中就是根据 ID 来进行删除和修改操作的。
4.2.1 根据条件删除
4.2.2 删除全部
4.3 修改索引
5.搜索
5.1 创建查询对象的两种方式
对要搜索的信息创建 Query 查询对象,Lucene 会根据 Query 查询对象生成最终的查询语法。类似关系数据库 Sql 语法一样,Lucene 也有自己的查询语法,比如:“name:lucene”表示查询 Field 的 name 为“lucene”的文档信息。
可通过两种方法创建查询对象:
1)使用 Lucene 提供 Query 子类
Query 是一个抽象类,lucene 提供了很多查询对象,比如 TermQuery 项精确查询,TermRangeQuery,范围查询, BooleanQuery 组合查询等。
如下代码:
2)使用 QueryParse 解析查询表达式
QueryParser 会将用户输入的查询表达式解析成 Query 对象实例。(QueryParser、MultiFieldQueryParser)
如下代码:
5.2 通过 Query 子类来创建查询对象
5.2.1 TermQuery
精确的词项查询,查看上边的案例。
5.2.2 查询所有
5.2.3 根据文件大小范围进行查询
5.2.4 组合查询(BooleanQuery)
组合关系代表的意思如下:
1、MUST 和 MUST 表示“与”的关系,即“并集”。
2、MUST 和 MUST_NOT 前者包含后者不包含。
3、MUST_NOT 和 MUST_NOT 没意义
4、SHOULD 与 MUST 表示 MUST,SHOULD 失去意义;
5、SHOUlD 与 MUST_NOT 相当于 MUST 与 MUST_NOT。
6、SHOULD 与 SHOULD 表示“或”的概念。
5.3 通过 QueryParser 创建查询对象
5.3.1 QueryParser
通过 QueryParser 来创建 query 对象,可以指定分词器,搜索时的分词器和创建该索引的分词器一定要一致。
5.3.2 MultiFieldQueryParser
5.3.3 查询语法
1、基础的查询语法,关键词查询:
域名+":"+搜索的关键字
例如:content:java
范围查询
域名+":"+[最小值 TO 最大值]
例如:size:[1 TO 1000]
组合条件查询
Occur.MUST 查询条件必须满足,相当于 and
+(加号)
Occur.SHOULD 查询条件可选,相当于 or
空(不用符号)
Occur.MUST_NOT 查询条件不能满足,相当于 not 非
-(减号)
1)+条件 1 +条件 2:两个条件之间是并且的关系 and
例如:+filename:apache +content:apache
2)+条件 1 条件 2:必须满足第一个条件,忽略第二个条件
例如:+filename:apache content:apache
3)条件 1 条件 2:两个条件满足其一即可
例如:filename:apache content:apache
4)-条件 1 条件 2:必须不满足条件 1,要满足条件 2
例如:-filename:apache content:apache
第二种写法:
条件 1 AND 条件 2
条件 1 OR 条件 2
条件 1 NOT 条件 2
5.3.4 TopDocs
Lucene 搜索结果可通过 TopDocs 遍历,TopDocs 类提供了少量的属性,如下:
方法或属性
说明
totalHits
匹配搜索条件的总记录数
scoreDocs
顶部匹配记录
注意:
Search 方法需要指定匹配记录数量 n:indexSearcher.search(query, n)
TopDocs.totalHits:是匹配索引库中所有记录的数量
TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs 的长度小于等于 search 方法指定的参数 n
6. 相关度排序
6.1 什么是相关度排序
相关度排序就是查询关键字与查询结果的匹配相关度。匹配越高的越靠前。Lucene 是通过打分来进行相关度排序的。
打分分两步:
根据词计算词的权重
根据词的权重进行打分
词的权重:词指的就是 term。也就是说一个 term 对一个文档的重要性,就叫词的权重。
影响词的权重的方式有两种:Tf 和 Df
词在同一个文档中出现的频率, Tf 越高,说明词的权重越高
词在多个文档中出现的频率,Df 越高,说明词的权重越低
以上是自然打分的规则。
6.2 设置 boost 值影响打分
Boost:加权值,默认是 1.0f。设置加权值可以在创建索引时设置,也可以在查询时设置。
Boost 值是设置到 Field 域上的。
6.2.1 创建索引时设置 boost 值
6.2.2 搜索时设置 boost 值
在 MultiFieldQueryParser 创建时设置 boost 值。
7 中文分词器
7.1 什么是中文分词器
对于英文,是按照空格和标点符号进行分词的, 但是对于中文来说,这是不正确的,中文应该按照具体的词来分,中文分词就是将词,切分成一个个有意义的词。
比如:“我是中国人”,分词:我、是、中国、中国人、国人。
7.2 Lucene 自带的中文分词器
StandardAnalyzer:
单字分词:就是按照中文一个字一个字地进行分词。如:“我是中国人”,
效果:“我”、“是”、“中”、“国”、“人”。
CJKAnalyzer
二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。
显然以上两个分词器很难满足我们对于中文分词的需求。
7.3 第三方中文分词器
mmseg4j:最新版已从 https://code.google.com/p/mmseg4j/ 移至 https://github.com/chenlb/mmseg4j-solr,在 github 中最新提交代码是 2014 年 6 月,从 09 年~14 年一共有:18 个版本,也就是一年几乎有 3 个大小版本,有较大的活跃度,用了 mmseg 算法。
IK-analyzer: 最新版在https://code.google.com/p/ik-analyzer/上,从 2006 年 12 月推出 1.0 版开始, IKAnalyzer 已经推出了 4 个大版本。最初,它是以开源项目 Luence 为应用主体的,结合词典分词和文法分析算法的中文分词组件。从 3.0 版本开 始,IK 发展为面向 Java 的公用分词组件,独立于 Lucene 项目,同时提供了对 Lucene 的默认优化实现。在 2012 版本中,IK 实现了简单的分词 歧义排除算法,标志着 IK 分词器从单纯的词典分词向模拟语义分词衍化。 2012 年 12 月后没有再更新。
ansj_seg:最新版本在 https://github.com/NLPchina/ansj_seg tags 仅有 1.1 版本,从 2012 年到 2014 年更新了大小 6 次,但是作者本人在 2014 年 10 月 10 日说明:“可能我以后没有精力来维护 ansj_seg 了”,现在由”nlp_china”管理。2014 年 11 月有更新。并未说明是否支持 Lucene,是一个由 CRF(条件随机场)算法所做的分词算法。
Jcseg:最新版本在 git.oschina.net/lionsoul/jcseg,支持 Lucene 4.10,作者有较高的活跃度。利用 mmseg 算法。
7.4 Ikanalyzer
7.4.1 在项目中添加 ikanalyzer 的 jar 包
7.4.2 修改分词器代码
7.4.3 扩展中文词库
将以下文件拷贝到当前项目目录下
从 ikanalyzer 包中拷贝配置文件到 classpath 下
如果想配置扩展词和停用词,就创建扩展词的文件和停用词的文件,文件的编码要是 utf-8。
注意:不要用记事本保存扩展词文件和停用词文件,这样会导致格式中是含有 bom。
今天的学习就到这里了,由于本人能力和知识有限,如果有写的不对的地方,还请各位大佬批评指正。如果想继续学习提高,欢迎关注我,每天学习进步一点点,就是领先的开始,加油。如果觉得本文对你有帮助的话,欢迎转发,评论,点赞!!!
版权声明: 本文为 InfoQ 作者【AlbertYang】的原创文章。
原文链接:【http://xie.infoq.cn/article/1c97baa5110393fb56daa8fa5】。文章转载请联系作者。
评论