面试官问:如何用Elasticsearch实现Word、PDF,TXT文件的全文内容检索?
Elasticsearch简介
开发环境
核心问题
文件上传
关键字查询
编码
导入依赖
文件上传
文件查询
多文件测试
还存在的一些问题
简单介绍一下需求
能支持文件的上传,下载
要能根据关键字,搜索出文件,要求要能搜索到文件里的文字,文件类型要支持word,pdf,txt
文件上传,下载比较简单,要能检索到文件里的文字,并且要尽量精确,这种情况下很多东西就需要考虑进去了。这种情况下,我决定使用Elasticsearch
来实现。
因为准备找工作刷牛客的原因,发现很多面试官都问到了Elasticsearch
,再加上那时候我连Elasticsearch
是什么东西都不知道,所以就决定尝试一下新东西。不得不说Elasticsearch
版本更新的是真的快,前几天才使用了7.9.1
,结果25号就出来了7.9.2
版本。
Elasticsearch简介
Elasticsearch
是一个开源的搜索文献的引擎,大概含义就是你通过Rest
请求告诉它关键字,他给你返回对应的内容,就这么简单。
Elasticsearch
封装了Lucene
,Lucene
是apache
软件基金会一个开放源代码的全文检索引擎工具包。Lucene
的调用比较复杂,所以Elasticsearch
就再次封装了一层,并且提供了分布式存储等一些比较高级的功能。
基于Elasticsearch
有很多的插件,我这次用到的主要有两个,一个是kibana
,一个是Elasticsearch-head
。
kibana
主要用来构建请求,它提供了很多自动补全的功能。Elasticsearch-head
主要用来可视化Elasticsearch
。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。
项目地址:https://github.com/YunaiV/ruoyi-vue-pro
开发环境
首先安装Elasticsearch
,Elasticsearch-head
,kibana
,三个东西都是开箱即用,双击运行 。需要注意的是kibana
的版本要和Elasticsearch
的版本对应。
Elasticsearch-head
是Elasticsearch
的可视化界面,Elasticsearch
是基于Rest
风格的API
来操作的,有了可视化界面,就不用每次都使用Get
操作来查询了,能提升开发效率。
Elasticsearch-head
是使用node.js
开发的,在安装过程中可能会遇到跨域的问题:Elasticsearch
的默认端口是9200
,而Elasticsearch-head
的默认端口是9100
,需要改一下配置文件,具体怎么改就不详细说啦,毕竟有万能的搜索引擎。
Elasticsearch
安装完成之后,访问端口,就会出现以下界面。
Elasticsearch主页面
基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。
核心问题
有两个需要解决的核心问题,文件上传和输入关键词查询。
文件上传
首先对于txt
这种纯文本的形式来说,比较简单,直接将里面的内容传入即可。但是对于pdf,word
这两种特殊格式,文件中除了文字之外有很多无关的信息,比如图片,pdf中的标签等这些信息。这就要求对文件进行预处理。
Elasticsearch5.x以后提供了名为ingest node
的功能,ingest node
可以对输入的文档进行预处理。如图,PUT请求进入后会先判断有没有pipline
,如果有的话会进入Ingest Node
进行处理,之后才会正式被处理。
引用自Elastic 中国社区官方博客
Ingest Attachment Processor Plugin
是一个文本抽取插件,本质上是利用了Elasticsearch
的ingest node
功能,提供了关键的预处理器attachment
。在安装目录下运行以下命令即可安装。
./bin/elasticsearch-plugin install ingest-attachment
定义文本抽取管道
PUT /_ingest/pipeline/attachment
{"description": "Extract attachment information","processors": [{"attachment": {"field": "content","ignore_missing": true}},{"remove": {"field": "content"}}]
}
在attachment
中指定要过滤的字段为content
,所以写入Elasticsearch
时需要将文档内容放在content
字段。
运行结果如图:
定义文本抽取管道
建立文档结构映射
文本文件通过预处理器上传后以何种形式存储,我们需要建立文档结构映射来定义。PUT定义文档结构映射的时候就会自动创建索引,所以我们先创建一个docwrite
的索引,用于测试。
PUT /docwrite
{"mappings": {"properties": {"id":{"type": "keyword"},"name":{"type": "text","analyzer": "ik_max_word"},"type":{"type": "keyword"},"attachment": {"properties": {"content":{"type": "text","analyzer": "ik_smart"}}}}}
}
在ElasticSearch
中增加了attachment
字段,这个字段是attachment
命名pipeline
抽取文档附件中文本后自动附加的字段。这是一个嵌套字段,其包含多个子字段,包括抽取文本 content 和一些文档信息元数据。
同是对文件的名字name指定分析器analyzer
为ik_max_word
,以让ElasticSearch
在建立全文索引时对它们进行中文分词。
建立文档结构
测试
经过上面两步,我们进行简单的测试。因为ElasticSearch
是基于JSON
格式的文档数据库,所以附件文档在插入ElasticSearch
之前必须进行Base64
编码。先通过下面的网站将一个pdf文件转化为base64
的文本。PDF to Base64
测试文档如图:
测试文档
然后通过以下请求上传上去,我找了一个很大的pdf文件。需要指定的是我们刚创建的pipeline
,结果如图所示。
文件上传测试
原来的索引有个
type
类型,新版本后面会被弃用,默认的版本都是_doc
然后我们通过GET
操作看看我们的文档是否上传成功。可以看到已经被解析成功。
文件上传结果查看
如果不指定pipline
的话,就会出现无法解析的情况。
没有指定pipeline的情况
根据结果我们看到,我们的PDF文件已经通过我们自行定义的pipline
,然后才正式进入索引数据库docwrite
。
关键字查询
关键字查询即对输入的文字,能进行一定的分词处理。比如说对于“数据库计算机网络我的电脑”这一串词来说,要能将其分为“数据库”,“计算机网络”,“我的电脑”三个关键词,然后分别根据关键字查询。
Elasticsearch
自带了分词器,支持所有的Unicode
字符,但是它只会做最大的划分,比如对于进口红酒
这四个字,会被分为“进”,“口”,“红”,“酒”
这四个字,这样查询出来的结果就会包括“进口”,“口红”,“红酒”
。
正在上传…重新上传取消
默认分词器
这并不是我们想要的结果。我们想要的结果是,只分为“进口”,“红酒”
这两段,然后查询相应的结果。这就需要使用支持中文的分词器了。
ik分词器
ik分词器
是开源社区比较流行的中文分词插件,我们首先安装ik分词器,注意以下代码不能直接使用。
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/...这里找你的版本
ik分词器
包括两种模式。
ik_max_word
会把中文尽可能的拆分。ik_smart
会根据常用的习惯进行划分,比如"进口红酒”
会被划分为“进口”,“红酒”
。
ik_smart模式
我们使用在查询时,指定ik分词器
进行查询文档,比如对于插入的测试文档,我们使用ik_smart
模式搜索,结果如图。
GET /docwrite/_search
{"query": {"match": {"attachment.content": {"query": "实验一","analyzer": "ik_smart"}}}
}
搜索文章
我们可以指定Elasticsearch
中的高亮,来为筛选到的文字添加标签。这样的话文字前后都会被添加上标签。如图。
highlight效果
编码
编码使用Idea+maven
的开发环境,首先导入依赖,依赖一定要与Elasticsearch
的版本相对应。
导入依赖
Elstacisearch
对于Java
来说有两个API
,我们使用的封装的比较完善的高级API
。
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.9.1</version>
</dependency>
文件上传
先建立一个与上文对应的fileObj
对象
public class FileObj {String id; //用于存储文件idString name; //文件名String type; //文件的type,pdf,word,or txtString content; //文件转化成base64编码后所有的内容。
}
首先根据上文所诉,我们要先将文件以字节数组的形式读入,然后转化成Base64
编码。
public FileObj readFile(String path) throws IOException {//读文件File file = new File(path);FileObj fileObj = new FileObj();fileObj.setName(file.getName());fileObj.setType(file.getName().substring(file.getName().lastIndexOf(".") + 1));byte[] bytes = getContent(file);//将文件内容转化为base64编码String base64 = Base64.getEncoder().encodeToString(bytes);fileObj.setContent(base64);return fileObj;
}
java.util.Base64
已经提供了现成的函数Base64.getEncoder().encodeToString
供我们使用。
接下来就可以使用Elasticsearch
的API将文件上传了。
上传需要使用IndexRequest
对象,使用FastJson
将fileObj
转化为Json
后,上传。需要使用indexRequest.setPipeline
函数指定我们上文中定义的pipline
。这样文件就会通过pipline
进行预处理,然后进入fileindex
索引中。
public void upload(FileObj file) throws IOException {IndexRequest indexRequest = new IndexRequest("fileindex");//上传同时,使用attachment pipline进行提取文件indexRequest.source(JSON.toJSONString(file), XContentType.JSON);indexRequest.setPipeline("attatchment");IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);System.out.println(indexResponse);
}
文件查询
文件查询需要使用SearchRequest
对象,首先我要指定对我们的关键字使用ik分词器
的ik_smart
模式分词
SearchSourceBuilder srb = new SearchSourceBuilder();
srb.query(QueryBuilders.matchQuery("attachment.content", keyword).analyzer("ik_smart"));
searchRequest.source(srb);
之后我们就可以通过返回的Response
对象获取每一个hits
,之后获取返回的内容。
Iterator<SearchHit> iterator = hits.iterator();
int count = 0;
while (iterator.hasNext()) {SearchHit hit = iterator.next();
}
Elasticsearh
一个非常强大的功能是文件的高亮(highlight)功能,所以我们可以设置一个highlighter
,对查询到的文本进行高亮操作。
HighlightBuilder highlightBuilder = new HighlightBuilder();
HighlightBuilder.Field highlightContent = new HighlightBuilder.Field("attachment.content");
highlightContent.highlighterType();
highlightBuilder.field(highlightContent);
highlightBuilder.preTags("<em>");
highlightBuilder.postTags("</em>");
srb.highlighter(highlightBuilder);
我设置了前置<em></em>
标签对对查询的结果进行包裹。这样查询到的结果中就会包含对应的结果。
多文件测试
简单的demo写好了,但是效果怎么样还需要使用多个文件进行测试。这是我的一个测试文件夹,里面下面放了各种类型的文件。
将这个文件夹里面的全部文件上传之后,使用elestacisearch``-head
可视化界面查看导入的文件。
导入的文件
搜索代码:
/*** 这部分会根据输入的关键字去查询数据库中的信息,然后返回对应的结果* @throws IOException*/@Testpublic void fileSearchTest() throws IOException {ElasticOperation elo = eloFactory.generate();elo.search("数据库国务院计算机网络");}
运行我们的demo,查询的结果如图所示。
正在上传…重新上传取消
搜索结果
还存在的一些问题
1. 文件长度问题
通过测试发现,对于文本内容超过10万字的文件,elasticsearch
只保留10w字,后面的就被截断了,这就需要进一步了解Elasticsearch
对10w字以上的文本的支持。
2. 编码上的一些问题
我的代码中,是将文件全部读入内存之后,在进行一系列的处理 ,毫无疑问,必定会带来问题,比如假如是一个超出内存的超大文件,或者是若干个大文件,在实际生产环境中,文件上传就会占用服务器的相当一大部分内存和带宽,这就要根据具体的需求,做进一步的优化。
面试官问:如何用Elasticsearch实现Word、PDF,TXT文件的全文内容检索?相关推荐
- 如何实现Word、PDF,TXT文件的全文内容检索?
作者 | HENG 来源 | https://www.cnblogs.com/strongchenyu/p/13777596.html 简单介绍一下需求 能支持文件的上传,下载 要能根据关键字,搜索出 ...
- 如何实现Word、PDF、TXT文件的全文内容检索?
简单介绍一下需求 能支持文件的上传,下载 要能根据关键字,搜索出文件,要求要能搜索到文件里的文字,文件类型要支持word,pdf,txt 文件上传,下载比较简单,要能检索到文件里的文字,并且要尽量精确 ...
- Android 读取本地Word/Pdf/Txt文件转文本输出
毫无疑问支持.doc和.docx的只有POI 好多jar都是积分下载,要么就是官网下载,真心垃圾,jar包免费下载: 链接:https://pan.baidu.com/s/14MWWHN3cTsr0m ...
- 你以为面试官问的是分布式缓存,其实他想问……
最近一个哥们去面试某当红大厂了,其中几个他印象深刻的面试题你们品品: 1.介绍下如何对MySQL SQL语句进行分析和优化? 2.Redis 怎样实现的分布式锁? 3.如何实现本地缓存和分布式缓存? ...
- 面试官问:为什么 Java 线程没有Running状态?我懵了
点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 title: 面 ...
- 面试官问你为什么选择做客服_在线客户服务-您的选择
面试官问你为什么选择做客服 On the Web, news travels fast - and a good customer testimonial is worth its weight in ...
- 【154期】面试官问:请你说说 B 树、B+ 树的原理及区别?
点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方留言必回,有问必答! 每天 08:35 更新文章,每天进步一点点... 之前在 ...
- 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...
面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...
- 【240期】面试官问:说说基于 Redis 实现延时队列服务?
点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜,留言必回,有问必答! 每天 08:15 更新文章,每天进步一点点... ...
最新文章
- 使用 UpdatePanel 【转by Dorian Deng】
- 图片远程保存,浏览器直接下载
- list根据对象进行排序
- 科大星云诗社动态20210815
- 亿佰特物联网无线模块通信技术分析
- 分段线性插值c语言程序_【短道速滑】OpenCV中cvResize函数使用双线性插值缩小图像长宽大小一半时速度飞快(比最近邻还快)之异象解析和自我实现。...
- mysql是gplv3,Affero-GPL和GPLv3之间的区别
- FreeBSD的起源和发展
- 利用Jackson的JsonFilter来实现动态过滤数据列(数据列权限控制)
- vue 3.2 的 script setup 语法
- 蓝桥杯 BASIC-13 基础练习 数列排序
- c语言如何输入未知数据类型的_C语言新手踩坑记!大坑小坑全部都是你的!
- 设计模式之GOF23模板模式
- 立即执行的js插件,转成可以在VUE环境下使用,
- 微信小程序排坑:请选择含app.json / project.config.json的目录
- 使用大白菜U盘重装系统
- 集合中某几个数字之和等于一个固定值 java
- 微型计算机的特点及其主板构成,第1章 计算机基础知识教案
- CNN | 06Cifar-10分类
- 22年前的9月14日中国发出的第一封电子邮件