API 文档搜索引擎

发布于:2022-12-29 ⋅ 阅读:(521) ⋅ 点赞:(0)


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

一、项目简介

 该项目类似于百度搜索引擎在搜索框内输入一个 Java API 文档的关键字,对后端发出请求,后端将处理后的若干条查询结果返回给前端展示,并且按照一定的权重排序展示出来。每条搜索结果包含了标题、描述、URL,点击标题可以跳转到官方文档。

  • 开发环境:IDEA、Maven、JDK 1.8、MySQL
  • 相关技术:正排索引、倒排索引、分词技术、线程池、Spring Boot、JSON、Ajax
  • 本地资源:Java API 文档
    官方文档下载地址

项目主要分为3个模块:

  • 索引模块:扫描本地 API 文档,分析内容(使用 Ansj 分词技术),构建正排+倒排索引;
  • 搜索模块:输入查询词,基于倒排索引检索,根据分词权重+正排索引,展示查询结果;
  • 前端模块:编写简单页面,对搜索结果作 CSS 布局,结合标签跳转到对应的 API 文档;

回到目录…

二、索引模块

2-1 ansj 分词

引入 ansj 依赖,将本地的 API 文档 进行分词处理,以便计算每个词的权重。

<!-- Java 版本的一个分词库,本身是支持中文分词的,只是咱的文档中没有中文。但英文分词它也支持 -->
<!-- https://github.com/NLPchina/ansj_seg -->
<dependency>
	<groupId>org.ansj</groupId>
	<artifactId>ansj_seg</artifactId>
	<version>5.1.6</version>
</dependency>

将分出来的词,放入 List 中。

List<String> words = ToAnalysis.parse(str);

2-1 正排索引

正向索引就是:通过title(文档ID),去找value(目标文档),然后确定出包含的 文档 URL、content,将这些文档展示给用户。
在这里插入图片描述
正排索引的表中,tilte 是不重复的,每一个 title 对应唯一的 URL 和 content。
构建: 遍历每个文档的标题,并且存储其 URL 和 content。

如何提取本地的.html文档的内容?
使用 Java 正则表达式 去除HTML文件中的<标签>, 只保留文本格式。

return contentBuilder.toString()
	// 首先去掉 <script ...>...</script>
	.replaceAll("<script.*?>.*?</script>", " ")
	// 去掉标签
	.replaceAll("<.*?>", " ")
	// 去掉转义字符
	.replaceAll("&.*?;", " ")
	.replaceAll("\\s+", " ")
	.trim();

2-2 倒排索引

倒排索引就是:通过value(关键词),去找key(文档ID),然后将找到的结果,即这些文档,展示给用户;
在这里插入图片描述
倒排索引的表中,(word, weight) 的组合是不重复的,每一组合都会有对应的文档ID,单词权重。
构建: 遍历每个文档中的所有词,每个文档插入 countDifferentWord 条数据到表中,(word[i], docid, weight(word[i]))

如何计算单词的权重?
因为按照用户的搜索逻辑来说,文档标题的权重是自然要比内容中的单词权重高一些的。所以在计算时,适当的给标题提高权重。

int weight = titleCount * 10 + contentCount;

回到目录…

三、搜索模块

  1. 键入 query 关键词,先对 query 进行分词,得到 queryList
  2. qqueryList 中可能出现多个关键词。针对每个关键词去倒排索引中查找,每个关键词都有若干条结果,把所有结果添加到 List中。
  3. 再对 List 中的所有结果,根据权重值排倒序(权重值高的靠前)。
  4. 此时,将所有结果以 JSON 的格式传递给前端,前端进行分页展示。
    每页包含20条结果,每条结果包含标题、内容(全文的前120字+后120字)、URL。

回到目录…

四、数据库

  • 我们使用的数据库是 MySQL 。
  • 我们在学习过 Spring 后,我们为了代码的便洁,不再采用传统的 JDBC 操作,而是引入了 MyBatis 。

出现的问题: 由于数据量非常大(正排索引1万条, 倒排索引20万条),导致构建索引的过程非常耗时,同时搜索时性能也比较差。

优化的过程:

  1. 最开始 —— 单线程+当行插入——耗时>3小时。

  2. 第一次优化:单线程+批量插入——耗时约3分钟。
    使用 mapper.xml + @Mapper 的方式执行 SQL 语句。
    原因:单纯的 @Insert 的使用,无法完成SQL的批量插入。
    正排索引每次插入10条,因为正排索引的单条数据量就比较大(包含了文本内容)。
    倒排索引每次插入10000条,单条数据量小。

<mapper namespace="com.wangshaoyu.search.indexer.mapper.IndexDatabaseMapper">
    <insert id="batchInsertForwardIndexes" useGeneratedKeys="true" keyProperty="docId" keyColumn="docid">
        insert into forward_indexes (title, url, content) values
        <!-- 一共有多少条记录,得根据用户传入的参数来决定,所以这里采用动态 SQL 特性 -->
        <foreach collection="list" item="doc" separator=", ">
            (#{doc.title}, #{doc.url}, #{doc.content})
        </foreach>
    </insert>

    <!-- 不关心自增 id -->
    <insert id="batchInsertInvertedIndexes">
        insert into inverted_indexes (word, docid, weight) values
        <foreach collection="list" item="record" separator=", ">
            (#{record.word}, #{record.docId}, #{record.weight})
        </foreach>
    </insert>
</mapper>
  1. 第二次优化:多线程+批量插入——耗时约16秒。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
	8, 20, 30, TimeUnit.SECONDS,
	new ArrayBlockingQueue<>(5000),
	(Runnable task) -> {
		Thread thread = new Thread(task);
		thread.setName("批量插入线程");
		return thread;
	},
	new ThreadPoolExecutor.AbortPolicy()
);
  1. 第三次优化:在构建好的倒排索引表中,添加索引关联到 word 和 weight。
-- 插入数据后,再构建索引
ALTER TABLE `searcher`.`inverted_indexes`
    ADD INDEX `INDEX_word_weight` (`word` ASC, `weight` DESC);
;

回到目录…

五、展示效果

项目访问地址http://1.15.76.95:8019

主页:
在这里插入图片描述

查询结果页:
在这里插入图片描述

点击跳转到官方文档:
在这里插入图片描述

回到目录…

六、待优化

  1. 前端页面的优化。
  2. 分词技术不够好,有些连续的单词没必要分,有些分出来的词没有意义,应该舍弃。
  3. 搜索不够好:目前不具备模糊查询的效果。
  4. 应该添加过滤器
    搜索引擎过滤器:许多搜索引擎(例如Google和Bing)都为用户提供了打开安全过滤器的选项。激活此安全过滤器后,它会过滤掉所有搜索结果中的不适当链接。如果用户知道具有显式内容或成人内容的网站的实际网址,则他们可以在不使用搜索引擎的情况下访问该内容。一些提供商提供面向儿童的引擎版本。

回到目录…


总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,该项目类似于百度搜索引擎,在搜索框内输入一个 Java API 文档的关键字,对后端发出请求,后端将处理后的若干条查询结果返回给前端展示,并且按照一定的权重排序展示出来。之后的学习内容将持续更新!!!

本文含有隐藏内容,请 开通VIP 后查看