乍听之下,不无道理;仔细揣摩,胡说八道

0%

我是怎么把笔记导入ElasticSearch的

尽管我最近开始用VNote做读书或读在线文档的笔记,但更多的时候,我把经验型知识都记录在一个名为my_note的Git仓库中。这个仓库中有许多.org`文件:

  1. TeX.org,记录与LaTeX相关的问题和解决方法;
  2. asm.org,记录的是与编写汇编语言程序相关的问题和解决办法;
  3. cl.org,记录的是与编写Common Lisp代码相关的问题和解决办法;

这些内容被我称为FAQ。尽管不同的文件记载着不同方面的内容,但它们的格式是一致的:

  1. 每个文件都以org-mode的语法书写;
  2. 文件中只有一级条目,没有嵌套;
  3. 每一个条目的标题就是一个问题的表述,下方的文字则是这个问题的答案。

A picture is worth a thousand words

这些问题都比较常见(不然怎么叫FAQ呢——也许上图的不算常见吧),回过头来查找的机率很高。显然,在纷繁复杂的文字中凭肉眼寻找关键字是低效的,即使是祭出grep,用正则表达式这样的大杀器来查找也不是特别称手——因为并不知道怎样的正则表达式可以匹配到寻找的内容——也许多写了关键词,也许少写了,也许顺序不对。

对于搜索这类非结构的文字资料来说,全文检索是一个更好的选择,因此,我是把这些内容丢进ElasticSearch里再查找的。

解析并导入到ElasticSearch

FAQ中的每一个条目,都对应ElasticSearch中的一个文档,它们都存储在索引faq中。一个文档有如下的字段:

  1. answer,即问题的答案;
  2. path,文件绝对路径,表示文档来自于哪一个文件中的条目;
  3. question,即问题的描述;
  4. questionLineNum,即问题存在于文件的第几行。

解析这些文件的逻辑也很简单:每当读入行首为星号的一行后(这一行即为问题),便继续读入后续的每一行直到再次遇到行首为星号的行为止,这些后续读入的行组成了这个问题的答案。有了问题和答案,便可以导入到ElasticSearch中。最终的脚本如下

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
const request = require('co-request');

const fs = require('fs');

function parseFaqOrg(path) {
const content = fs.readFileSync(path).toString('utf-8');
const lines = content.split('\n');
const qas = [];
let answer = [];
let lineNum = 0;
let mode;
let question;
let questionLineNum;
for (const line of lines) {
lineNum += 1;
if (line.startsWith('*')) {
if (mode === 'answer') {
// 在遇到星号的时候模式已经处于answer中,说明在此之前还有未处理的QA
qas.push({
answer: answer.join('\n'),
path,
question,
questionLineNum
});
answer = [];
question = null;
}
mode = 'question';
} else {
mode = 'answer';
}
if (mode === 'answer') {
answer.push(line);
} else {
question = line;
questionLineNum = lineNum;
}
}
if (question) {
qas.push({
answer: answer.join('\n'),
question
});
}
// console.log(JSON.stringify(qas, null, 2));
return qas;
}

async function dropFaq() {
await request({
method: 'delete',
url: 'http://localhost:9200/faq'
});
}

/**
* 重建faq索引并写入全量的笔记数据
*/
async function main() {
console.log(new Date().toLocaleString());
await dropFaq();
const dir = '/Users/liutos/Documents/Projects/my_note/faq/';
const basenames = fs.readdirSync(dir);
for (const basename of basenames) {
const path = dir + basename;
const type = basename.match(/(.*)\.org/)[1];
const qas = parseFaqOrg(path);
for (const qa of qas) {
await request({
body: qa,
json: true,
method: 'post',
url: 'http://localhost:9200/faq/_doc'
});
}
console.log(`文件${path}处理完毕`);
}
console.log(new Date().toLocaleString());
}

main();

后记

如果想知道我是如何在Emacs中查询这些FAQ的,可以参见《在Emacs中搭建笔记查阅系统的尝试》这篇文章。

Liutos wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
你的一点心意,我的十分动力。