引入

随着博文的数量越来越多,仅通过筛选文章的类型与标签或许并不便于快速定位我想找到的内容了。因此我想到,是时候实现一个搜索引擎来解决这个问题。

实现

安装插件

经过查阅资料,发现可以使用hexo-generator-search这款插件实现我的目标。下面是安装这个插件的过程:

1.在项目目录下安装插件

1
$ npm install hexo-generator-search --save

2.在_config.yml中添加配置内容:

1
2
3
search:
path: search.xml #这是生成的xml文件的名称
field: post

生成xml文件

遗憾的是,hexo-generator-search并不能帮我们直接实现搜索引擎的逻辑,它帮我们做的仅仅是生成一个xml文件,也就是上面的search.xml,其内容为我们目前所有的文章的内容。由于hexo没有后台数据库,这个xml文件相当于担任了持久层的功能。

重新生成项目

1
$ hexo g

可以看到在/public文件夹下产生了search.xml文件。其中的每篇文章的代码结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<entry>
<title>文章标题</title>
<link href="文章的URL"/>
<url>文章的URL</url>

<content type="html"><![CDATA[文章的内容]]></content>

<categories>

<category> 文章类别 </category>

</categories>


<tags>

<tag> 标签1 </tag>

<tag> 标签2 </tag>

</tags>
</entry>

我们此后的搜索操作,都将基于我们从所有博文中提取出的上面的信息,因此也就忽略了其它的信息,比如博文发布的时间,文章类型的链接,文章标签的链接等。

不过,由于xml文件是静态内容,所以这样的搜索功能,其性能还是可以接受的。

编写前端代码与JS逻辑

在页面上方添加一个文本框与搜索图标,作为搜索引擎功能的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 搜索框 -->
<div class="flex items-center">
<form id="search-form" class="flex">
<input type="text" id="search-input" class="
bg-[var(--c-20)]
text-[var(--c-80)]
py-1 px-2
rounded-lg
" placeholder="搜索框">

<!-- 添加搜索图标 -->
<button type="submit" id="search-btn" class="ml-2">
<iconify-icon width="20" icon="mingcute:search-2-fill"></iconify-icon>
</button>
</form>
</div>

在页面中写入JavaScript代码,处理点击事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
$('#search-form').on('submit', function (e) {
e.preventDefault();
var keyword = $('#search-input').val();
if (keyword) {
// 跳转到搜索结果页面,带上搜索关键词参数
window.location.href = '/cn/searchresult?keyword=' + encodeURIComponent(keyword);
}
});

$('#search-btn').on('click', function (e) {
e.preventDefault();
var keyword = $('#search-input').val();
if (keyword) {
// 跳转到搜索结果页面,带上搜索关键词参数
window.location.href = '/cn/searchresult?keyword=' + encodeURIComponent(keyword);
}
});

</script>

在searchresult页面中,编写JavaScript页面内容获取在xml文件中搜索关键词的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var keyword = decodeURIComponent(new URLSearchParams(window.location.search).get('keyword'));

if (keyword) {
$.ajax({
url: '/search.xml',
dataType: 'xml',
success: function(data) {
var postList = $(data).find('entry');
var searchResults = [];
postList.each(function() {
var postContent = $(this).text();
if (postContent.indexOf(keyword) != -1) {
//获取文章的相关信息
var postTitle = $(this).find('title').text();
var postUrl = $(this).find('url').text();
var postCategories = [];
$(this).find('categories category').each(function() {
postCategories.push($(this).text());
});
var postTags = [];
$(this).find('tags tag').each(function() {
postTags.push($(this).text());
});
博文包括关键词,假如列表等待最后输出
searchResults.push({title: postTitle, url: postUrl, categories: postCategories, tags: postTags});
}
});
//利用获取到的searchResults对象展示输出结果
displayResults(searchResults);
}
});
}

在结合HTML与JS的语言实现displayResults()方法,就实现了搜索引擎。

待完善的内容

目前已经实现的搜索引擎虽然能达到最基本的效果,但是其实还有改进的空间。

比如,可以按关键字与博文的相关程度降序输出列表。这对于提高搜索引擎的实用价值很有意义。然而,目前的列表是按文章在xml文件中的默认顺序(也就是按时间由近到远)排序的,并不满足这个要求。我的想法是,需要在上面的JS代码中引入“关联度”这个概念。比如,关键词在文章的标题中出现或者在文章的类别,标签中出现自然具有更高的关联度。在文章正文中出现次数多,自然要比在文章中出现次数少的关联度高。我需要量化一个关联度指标,并根据关联度降序对searchResults数组排序。

另外,使用xml文件检索关键词,这个搜索算法的时间复杂度是O(nk),其中n为文章数量,k为每篇文章平均的长度。而假设我们有办法将文章内容存储到关系型数据库MySQL中,利用索引功能生成B+树,则搜索的时间复杂度可以降低到0(log(nk))。因此,想提高搜索的效率,最好是为本项目建设持久层。

这里存在开发优先级的问题。由于目前我的博客功能尚不完善,我要优先将时间放在完善博客仍然没有的功能上,而不是对已有的功能精益求精。所以目前我不打算对这个问题进行解决。

后续有时间时我会尝试实现这个功能。

项目部署到Ubuntu系统的云服务器上时出现的小问题

当我在本地实现搜索引擎的功能后,我就将其部署到了我的云服务器上。然而,在云服务器上调用搜索功能,发现出现了404报错。

此时的URL:searchResult/?keyword=关键词

检查发现云服务器上是存在页面的,其路径与本地服务器完全相同,但是却不能像本地服务器一样正常打开链接。

我尝试将请求中的searchResult修改为searchresult,居然就能正常访问了。

此时的URL:searchresult/?keyword=关键词

经过查阅资料,才发现一个小细节:

Linux(包括Ubuntu)和Windows对文件路径大小写敏感度不同

  1. Linux/Unix环境:Linux和其他Unix-like操作系统(如Ubuntu)是区分文件路径大小写的。这意味着 searchResultsearchresult 会被视为两个不同的路径。在你的Ubuntu服务器上,只有正确的大小写才能正确访问文件或目录。
  2. Windows环境:相比之下,Windows对文件路径的大小写不敏感。这意味着无论你是访问 searchResult 还是 searchresult,Windows都会将其视为同一个路径。

这个细节在后续的开发中要时刻注意。