首页 > Python资料 博客日记
整合全文检索引擎 Lucene 添加站内搜索子模块
2024-10-25 09:00:09Python资料围观27次
整合全文检索引擎 Lucene: 添加站内搜索子模块
1. 什么是 Lucene ? 有啥优势?
Lucene 是一个开源的全文检索引擎库,由 Apache 基金会维护,官网地址:https://lucene.apache.org/ 。它提供了丰富的文本处理和搜索功能,允许开发者在应用程序中集成强大的全文检索能力。
以下是 Lucene 的一些主要特点和优势:
- 全文检索: Lucene 支持全文检索,可以在大量文本数据中快速而准确地查找关键字。
- 开源: Lucene 是开源的,可以免费使用,并且具有灵活的许可证,适用于各种项目。
- 高性能: Lucene 的搜索性能非常高效,它使用了许多优化算法和数据结构,能够在大型数据集上快速执行搜索。
- 跨平台: Lucene 是用 Java 编写的,因此可以在几乎所有的平台上运行。
- 可扩展: Lucene 提供了丰富的API和插件机制,可以轻松扩展其功能,以满足不同应用的需求。
- 丰富的查询语法: Lucene 支持复杂的查询语法,包括通配符、模糊查询、范围查询等。
2.添加依赖
<!-- 版本号统一管理 -->
<properties>
// 省略...
<lucene.version>8.11.1</lucene.version>
</properties>
<!-- 统一依赖管理 -->
<dependencyManagement>
<dependencies>
// 省略...
<!-- lucene 全文检索引擎 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>${lucene.version}</version>
</dependency>
<!-- 中文分词 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>${lucene.version}</version>
</dependency>
<!-- 关键词高亮 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>${lucene.version}</version>
</dependency>
<!-- 查询解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${lucene.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
添加 CommandLineRunner 项目启动任务:初始化 Lucene 文章索引
如何在 Spring Boot
工程启动时,执行一些任务呢? 其实实现方式有多种,这里使用的是 CommandLineRunner
。
CommandLineRunner
是什么?
CommandLineRunner
是 Spring Boot 提供的一个接口,用于在 Spring Boot 应用启动后执行一些初始化逻辑。它是一个功能接口,只包含一个run
方法,该方法会在 Spring Boot 应用启动后被调用。
我们在 weblog-module-search
模块中,创建一个 /runner
包,并添加一个 InitLuceneIndexRunner
初始化索引的任务
package com.quanxiaoha.weblog.search.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class InitLuceneIndexRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("==> 开始初始化 Lucene 索引...");
}
}
2. 扫描 CommandLineRunner
3.Lucene 相关概念
- 索引(Index): 索引是 Lucene 中的核心概念,它类似于数据库中的表。在 Lucene 中,索引是由一系列词项(terms)构成的数据结构,每个词项都关联到一个或多个文档。这允许非常快速的搜索,类似于数据库中使用索引进行快速检索的方式;
- 文档(Document):文档是 Lucene 中的基本信息单元,可以看作数据库表中的一行。每个文档由一组字段(Field)组成,每个字段包含一个值。文档在索引中存储,并且可以根据这些字段进行搜索;
- 字段(Field):字段是文档中的一个属性,它有一个名称和一个值。在搜索和检索中,我们可以使用字段来过滤和排序文档;
- 分析器(Analyzer):分析器负责将文本切分成单词,并对这些单词进行标准化处理,以便建立索引和进行搜索。Lucene 提供了各种分析器来处理不同类型的文本数据;
- 查询(Query):查询是用于在索引中搜索文档的表达式。Lucene 提供了强大的查询语言,允许我们构建复杂的搜索条件。
4.配置索引存储目录
为了能够自定义 Lucene
索引的存储目录,编辑 application-dev.yml
开发环境配置文件,自定义一个 lucene.indexDir
配置:
#=================================================================
# Lucene 全文检索
#=================================================================
lucene:
indexDir: E:\\java_workspace\\lucene-index # lucene 索引存放的位置
另外,再编辑 applicaiton-prod.yml
生产环境配置文件,自定义一个 linux
环境的索引存储目录,这里放置到了项目部署的目录下,方便后续维护:
#=================================================================
# Lucene 全文检索
#=================================================================
lucene:
indexDir: /app/weblog/lucene-index # lucene 索引存放的位置
读取 Lucene 配置
添加一个 /config
包,并新建一个 LuceneProperties
配置类,用于读取 .yml
文件中的 lucene
配置:
@ConfigurationProperties(prefix = "lucene")
@Component
@Data
public class LuceneProperties {
/**
* 索引存放的文件夹
*/
private String indexDir;
}
定义索引
接着,再添加一个 /index
包,在里面创建一个 ArticleIndex
索引接口,用于定义文章索引的名称,以及文档字段。添加哪些字段,就看你页面中需要展示哪些数据,代码如下:
public interface ArticleIndex {
/**
* 索引名称
*/
String NAME = "article";
// --------------------- 文档字段 ---------------------
String COLUMN_ID = "id";
String COLUMN_TITLE = "title";
String COLUMN_COVER = "cover";
String COLUMN_SUMMARY = "summary";
String COLUMN_CONTENT = "content";
String COLUMN_CREATE_TIME = "createTime";
}
引入 commons-io 工具包
准备工作完成后,正式进入到创建索引阶段了。因为涉及到操作文件,这里在父 pom.xml
中声明一下 commons-io
工具包,该库中封装了一些文件相关的常用操作,如创建文件,创建文件夹,删除文件夹等等,还是非常好用的:
<!-- 版本号统一管理 -->
<properties>
// 省略...
<commons-io.version>2.11.0</commons-io.version>
</properties>
<dependencies>
// 省略..
<!-- 工具包 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
封装 Lucene 工具类
@Component
@Slf4j
public class LuceneHelper {
/**
* 创建索引
* @param indexDir 索引存放的目录
* @param documents 文档
*/
public void createIndex(String indexDir, List<Document> documents) {
try {
File dir = new File(indexDir);
// 判断索引目录是否存在
if (dir.exists()) {
// 删除目录中的内容
FileUtils.cleanDirectory(dir);
} else {
// 若不存在,则创建目录
FileUtils.forceMkdir(dir);
}
// 读取索引目录
Directory directory = FSDirectory.open(Paths.get(indexDir));
// 中文分析器
Analyzer analyzer = new SmartChineseAnalyzer();
IndexWriterConfig config = new IndexWriterConfig(analyzer);
// 创建索引
IndexWriter writer = new IndexWriter(directory, config);
// 添加文档
documents.forEach(document -> {
try {
writer.addDocument(document);
} catch (IOException e) {
log.error("添加 Lucene 文档错误: ", e);
}
});
// 提交
writer.commit();
writer.close();
} catch (Exception e) {
log.error("创建 Lucene 索引失败: ", e);
}
}
}
初始化 Lucene 索引
工具类封装完成后,回到 InitLuceneIndexRunner
类中,编辑代码如下:
package com.quanxiaoha.weblog.search;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
@Component
@Slf4j
public class InitLuceneIndexRunner implements CommandLineRunner {
@Autowired
private LuceneProperties luceneProperties;
@Autowired
private LuceneHelper luceneHelper;
@Autowired
private ArticleMapper articleMapper;
@Autowired
private ArticleContentMapper articleContentMapper;
@Override
public void run(String... args) throws Exception {
log.info("==> 开始初始化 Lucene 索引...");
// 查询所有文章
List<ArticleDO> articleDOS = articleMapper.selectList(Wrappers.emptyWrapper());
// 未发布文章,则不创建 lucene 索引
if (articleDOS.isEmpty()) {
log.info("==> 未发布任何文章,暂不创建 Lucene 索引...");
return;
}
// 若配置文件中未配置索引存放目录,日志提示错误信息
if (StringUtils.isBlank(luceneProperties.getIndexDir())) {
log.error("==> 未指定 Lucene 索引存放位置,需在 application.yml 文件中添加路径配置...");
return;
}
// 文章索引存放目录, 如 /app/weblog/lucene-index/article
String articleIndexDir = luceneProperties.getIndexDir() + File.separator + ArticleIndex.NAME;
List<Document> documents = Lists.newArrayList();
articleDOS.forEach(articleDO -> {
Long articleId = articleDO.getId();
// 查询文章正文
ArticleContentDO articleContentDO = articleContentMapper.selectByArticleId(articleId);
// 构建文档
Document document = new Document();
// 设置文档字段 Field
document.add(new TextField(ArticleIndex.COLUMN_ID, String.valueOf(articleId), Field.Store.YES));
document.add(new TextField(ArticleIndex.COLUMN_TITLE, articleDO.getTitle(), Field.Store.YES));
document.add(new TextField(ArticleIndex.COLUMN_COVER, articleDO.getCover(), Field.Store.YES));
document.add(new TextField(ArticleIndex.COLUMN_SUMMARY, articleDO.getSummary(), Field.Store.YES));
document.add(new TextField(ArticleIndex.COLUMN_CONTENT, articleContentDO.getContent(), Field.Store.YES));
document.add(new TextField(ArticleIndex.COLUMN_CREATE_TIME, Constants.DATE_TIME_FORMATTER.format(articleDO.getCreateTime()), Field.Store.YES));
documents.add(document);
});
// 创建索引
luceneHelper.createIndex(articleIndexDir, documents);
log.info("==> 结束初始化 Lucene 索引...");
}
}
上述代码中,首先查询了文章表,校验了一下是否有文章,以及 .yml
文件中是否配置了索引存储目录。若通过,则对文章数据进行遍历,拿着文章 ID
查询正文,并构建 Document
文档,以及设置文档中需要存储的 Field
字段,最终调用 LuceneHelper
工具类中的 createIndex()
方法,完成对文章索引的创建工作。
封装查询总文档数方法
在动手写接口之前,先来给 LuceneHelper
工具类封装两个分页相关的方法:
- 1、中文分词分页搜索,查询总文档数;
- 2、中文分词分页搜索;
编辑 LuceneHelper
工具类,添加查询总文档数方法,代码如下:
package com.quanxiaoha.weblog.search;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
@Component
@Slf4j
public class LuceneHelper {
// 省略...
/**
* 关键词搜索, 查询总数据量
* @param indexDir 索引目录
* @param word 查询关键词
* @param columns 需要搜索的字段
* @return
*/
public long searchTotal(String indexDir, String word, String[] columns) {
try {
// 打开索引目录
Directory directory = FSDirectory.open(Paths.get(indexDir));
IndexReader reader = DirectoryReader.open(directory);
IndexSearcher searcher = new IndexSearcher(reader);
// 中文分析器
Analyzer analyzer = new SmartChineseAnalyzer();
// 查询解析器
QueryParser parser = new MultiFieldQueryParser(columns, analyzer);
// 解析查询关键字
Query query = parser.parse(word);
// 搜索文档
TopDocs totalDocs = searcher.search(query, Integer.MAX_VALUE);
// 返回文档数
return totalDocs.totalHits.value;
} catch (Exception e) {
log.error("查询 Lucene 错误: ", e);
return 0;
}
}
}
上面封装的方法,主要是用于在 Lucene 中执行搜索,然后获取匹配的文档总数。入参需要传入索引目录,被查询的关键词,以及想要搜索的文档字段。
接下来,我来逐行解释一下方法内的代码含义:
打开索引目录并创建
IndexReader
和IndexSearcher
:Directory directory = FSDirectory.open(Paths.get(indexDir)); IndexReader reader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(reader);
FSDirectory.open(Paths.get(indexDir))
打开存储索引的目录。DirectoryReader.open(directory)
创建一个IndexReader
,用于读取索引。IndexSearcher(reader)
创建一个IndexSearcher
,用于执行搜索操作。设置中文分析器和查询解析器:
Analyzer analyzer = new SmartChineseAnalyzer(); QueryParser parser = new MultiFieldQueryParser(columns, analyzer);
- 创建中文分析器
SmartChineseAnalyzer
。- 创建
MultiFieldQueryParser
,用于解析查询字符串。columns
参数指定了在哪些字段上执行查询。解析查询字符串:
Query query = parser.parse(word);
- 使用
parser.parse(word)
将查询字符串word
解析为 Lucene 的查询对象Query
。执行搜索并获取总文档数:
TopDocs totalDocs = searcher.search(query, Integer.MAX_VALUE);
searcher.search(query, Integer.MAX_VALUE)
执行搜索,获取匹配查询的所有文档。totalDocs.totalHits.value
获取匹配的文档总数。返回匹配文档的总数:
return totalDocs.totalHits.value;
ery query = parser.parse(word);
- 使用 `parser.parse(word)` 将查询字符串 `word` 解析为 Lucene 的查询对象 `Query`。 4. **执行搜索并获取总文档数:** ```java TopDocs totalDocs = searcher.search(query, Integer.MAX_VALUE);
searcher.search(query, Integer.MAX_VALUE)
执行搜索,获取匹配查询的所有文档。totalDocs.totalHits.value
获取匹配的文档总数。
返回匹配文档的总数:
return totalDocs.totalHits.value;
- 返回搜索结果中匹配的文档总数。
标签:
相关文章
最新发布
- 【Python】selenium安装+Microsoft Edge驱动器下载配置流程
- Python 中自动打开网页并点击[自动化脚本],Selenium
- Anaconda基础使用
- 【Python】成功解决 TypeError: ‘<‘ not supported between instances of ‘str’ and ‘int’
- manim边学边做--三维的点和线
- CPython是最常用的Python解释器之一,也是Python官方实现。它是用C语言编写的,旨在提供一个高效且易于使用的Python解释器。
- Anaconda安装配置Jupyter(2024最新版)
- Python中读取Excel最快的几种方法!
- Python某城市美食商家爬虫数据可视化分析和推荐查询系统毕业设计论文开题报告
- 如何使用 Python 批量检测和转换 JSONL 文件编码为 UTF-8
点击排行
- 版本匹配指南:Numpy版本和Python版本的对应关系
- 版本匹配指南:PyTorch版本、torchvision 版本和Python版本的对应关系
- Python 可视化 web 神器:streamlit、Gradio、dash、nicegui;低代码 Python Web 框架:PyWebIO
- 相关性分析——Pearson相关系数+热力图(附data和Python完整代码)
- Python与PyTorch的版本对应
- Anaconda版本和Python版本对应关系(持续更新...)
- Python pyinstaller打包exe最完整教程
- Could not build wheels for llama-cpp-python, which is required to install pyproject.toml-based proj