The most comprehensive database of Chinese poetry
包含 5.5 万首唐诗、26 万首宋诗、2.1 万首宋词和其他古典文集。诗人包括唐宋两朝近 1.4 万古诗人,和两宋时期 1.5 千古词人。此数据库通过 JSON 格式分发,可以让你很方便的开始你的项目。
Github上有不少宝藏项目,比如👆这个:最全中华古诗词库 - chinese-poetry。前几天基于这个宝库写了个检索的的小程序,这里做下开发过程的记录。实现了一个简单的demo, 见poem
技术分析
作为一名10后。。。。。。的老母亲,学校经常会留一些古诗词背诵的作业给孩子。可是,作为一名与高考脱节几十年的纯理工科生,好多诗我真是见都没见过(其实是还给老师了)。这时候,总是特别羡慕那些学富五车的家长,孩子提问时各种诗词文学典故信手拈来。不像我,只能巴巴的求助百度,有时候还出来一堆不靠谱的结果😭。不过好在知识不够,技术来凑,刚好发现这么个诗词宝库,怎能暴殄天物呢?
有了动手写一个诗词检索的想法,自然是要行动的。立刻把code下到本地,看下数据size。嗯,不是特别大,这样要是自己用用的话直接解析文件就可以了:
1
2
3
4
5
6
7
8
9
100K ./youmengying #幽梦影
15M ./ci #宋词
348K ./wudai #五代
143M ./json #全唐诗
172K ./sishuwujing #四书五经
72K ./lunyu #论语
160K ./shijing #诗经
1.0M ./mengxue #蒙学
4.0M ./yuanqu #元曲
再看下目录下的文件,根据数据大小,有的被切分成了多个文件,例如json
,ci
,有的占地不大的就只保留了一个,例如shijing
,yuanqu
。打开文件看看,都是标准的json格式,拿诗仙的看看,其他的也差不多,除了不同类型文件的key可能不同外:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"author": "李白",
"paragraphs": [
"雲想衣裳花想容,春風拂檻露華濃。",
"若非羣玉山頭見,會向瑤臺月下逢。"
],
"tags": [
"唐诗三百首",
"女子",
"乐府",
"赞美",
"近代曲辞"
],
"title": "雜曲歌辭 清平調 一",
"id": "b5f1094b-b9be-43cf-8e04-9b984642a630"
}
发现没?全唐诗的目录下是繁体字,但其实并不是所有文件都是繁体,像宋词的数据作者已经转成简体了。至于为什么全唐诗保留的数据要是繁体,作者给出的解释是:
目前还没有计划做简体中文数据库。一对一繁简转换有很多工具可以做, 由于简体字的删减在某些诗词场景下通过一对一的转换是有问题的。 比如“发财”繁体是“發財”,而“理发”繁体应是“理髮”. 更多的人名不太容易避免。
检查完文件,再列一下这次的需求,很简单:
- 输入一个标题,或者作者,或者里面的一句话把对应的内容给检索出来
- 检索到多个匹配项时,优先返回全部匹配的,随后返回部分匹配的,并在查询时给定需要最多返回的条数
- 因为有时候我只想随便看看,有随机取篇内容返回的功能
- 为了对小盆友友好,把返回的格式转成简体
实现
这次的实现,依然选择了python,原因自然是因为方(tou)便(lan)。
确定接口
首先,要定义检索接口。根据需求,能够多维度的检索,但不同类型文件的json字段key不同,所以自然想到用dict
作为检索条件的传递index_json
,也方便以后的扩展。另外检索前最好能指定类型peom_type
(是唐诗,还是宋词,还是诗经。。。),检索后能指定一个返回条目数count
。当然,有随机看看这种设定的存在,输入参数自然不是必须的。这样,就确定了函数定义:
1
2
3
#index is a dict json str, like: '{"author":"李白", "title":"静夜思"}'
def get_poem(poem_type="", index_json="", count=1):
......
输入处理
接下来,是对输入做处理,包括:
根据诗词类型生成路径,没有指定类型的给随机确定一个
1
2
3
4
5
if not poem_type:
i = random.randint(1, len(poem_type_list))
poem_type = poem_type_list(i)
poem_type = poem_type.lower()
poem_path = "%s/%s" % (poem_path_prefix, poem_type)
由于某些数据是繁体字(如全唐诗),在检索繁体数据时我们要将输入index_json
转成繁体。这里用了个轻量级的繁简体转换库zhtools,只要两个python文件,就能实现简单的转换。并把输入的检索条件由str
转成dict
1
2
3
4
5
6
7
8
indexs = {}
if poem_type in traditional_data and index_json:
index_json = convert(index_json, "t")
if index_json:
indexs = json.loads(index_json)
def convert(text, tp="s"):
return Converter('zh-han%s' % tp).convert(text)
数据预处理
像全唐诗这种由于数据量大而被分成多个json文件的,如果传了检索条件进来的话可以先做个简单的筛选来减少load的文件数目,毕竟如果遍历所有条目来查找的话,python的效率还是挺感人的。这里调用了grep
来做简单的筛选。做完这步后,就能得到一个file_list
,里面存的是python需要load的json文件列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
file_list = []
if indexs and poem_type in grep_poems:
for i in indexs:
grep_cmd = ""
if not i in ["content", "paragraphs"]:
grep_cmd = "grep -rl \"\\\"%s\\\".*%s\" %s" % (i, indexs[i], poem_path)
else:
grep_cmd = "grep -rl \"%s\" %s" % (indexs[i], poem_path)
res = subprocess.run(grep_cmd, shell=True, stdout=subprocess.PIPE).stdout.decode()
file_list = res.strip().split("\n")
break
else:
file_list = os.listdir(poem_path)
file_list = [os.path.join(poem_path, f) for f in file_list]
检索
到了最重要的检索这一步,其实相当简单。就是遍历这把json文件load进来,然后挨个匹配。需要注意的是数据文件中某些字段的值是个list
,如content
, paragraghs
之类,在查找的时候就不能粗暴的直接比较,而要遍历去看。同时,为了应对标题或内容只记得大概的场景,对每个条件还做了模糊比配。由于能力有限,这里的模糊匹配就是contains
操作。
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
poem_list = []
poem_contain_list = []
for f in file_list:
if not f.endswith(".json"):
continue
poems = json.load(open(f,"r"))
if not indexs:
poem_list.extend(poems)
continue
for poem in poems:
match = is_match(poem, indexs)
if match == 2:
poem_list.append(poem)
elif match == 1:
poem_contain_list.append(poem)
#retrun value: 0, no match; 1, contain match; 2, all match
def is_match(poem, indexs):
match_level_final = 2
for i in indexs:
match_level = 0
if type(poem[i]) is list:
for p in poem[i]:
current_level = is_match_str(indexs[i], p)
match_level = current_level if match_level < current_level else match_level
else:
match_level = is_match_str(indexs[i], poem[i])
if match_level == 0:
match_level_final = 0
break
match_level_final = match_level if match_level < match_level_final else match_level_final
return match_level_final
def is_match_str(query, original):
if not query in original:
return 0
if query != original:
return 1
return 2
返回
检索完成后,会得到poem_list
和poem_contain_list
两个匹配的list。最后的工作就是根据指定的条数从这两个list里按优先级加随机取得需要的记录。然后看情况做个繁体到简体的转换,注意下编码的问题,就大功告成了!其中,方法convertAll
可以将str
,dict
,和list
对象中的汉字做繁简转换。
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
if count <= len(poem_list):
poem_list = random.sample(poem_list, count)
else:
count -= len(poem_list)
if count <= len(poem_contain_list):
poem_list.extend(random.sample(poem_contain_list, count))
else:
poem_list.extend(poem_contain_list)
if poem_type in traditional_data:
poem_list = convertAll(poem_list)
return json.dumps(poem_list, ensure_ascii=False)
def convertAll(in_obj, tp="s"):
in_type = type(in_obj)
if in_type is str:
return convert(in_obj, tp)
out_obj = None
if in_type is list:
out_obj=[]
elif in_type is dict:
out_obj={}
else:
print("in obj type is not list or dict: %s" % in_type)
return out_obj
for obj in in_obj:
if in_type is list:
out_obj.append(convertAll(obj, tp))
elif in_type is dict:
out_obj[obj] = convertAll(in_obj[obj], tp)
return out_obj
返回的out_obj
里就是我们需要的内容了👏
演示
完成这么一项大工程当然少不了演(de)示(se)一番。先试试诗仙的静夜思吧:
python3 poem.py -t "tang" -i '{"title":"静夜思"}' -c 1
1
2
3
4
5
6
7
[{
"author": "李白",
"paragraphs": ["牀前看月光,疑是地上霜。", "举头望山月,低头思故乡。"],
"tags": ["唐诗三百首", "新乐府辞", "思乡", "中秋", "一年级上册", "月亮", "五言绝句", "小学古诗"],
"title": "静夜思",
"id": "ca2c489a-e433-4c0f-8248-77d354f0665e"
}]
呃。。。怎么和想象的不太一样??赶紧网上去查查,才知道最早记载的宋代版本确实是上面这四句。然后经过广大劳动人民的多次改编,就成了现在课本里的这一种了😂。。。真是又长见识了。
再试一个宋词看看吧,来个熟悉的,东坡先生的水调歌头 (注意,宋词只有词牌名,没有标题,我们课本里的标题都是后人加上去的):
python3 poem.py -t "song" -i '{"author":"苏轼", "rhythmic":"水调歌头"}' -c 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[{
"author": "苏轼",
"paragraphs": ["落日绣帘卷,亭下水连空。", "知君为我,新作窗户湿青红。", "长记平山堂上,枕江南烟雨,渺渺没孤鸿。", "认得醉翁语,山色有无中。", "一千顷,都镜净,倒碧峰。", "忽然浪起,掀舞一叶白头翁。", "堪笑兰台公子,未解庄生天籁,刚道有雌雄。", "一点浩然气,千里快哉风。"],
"rhythmic": "水调歌头"
}, {
"author": "苏轼",
"paragraphs": ["安石在东海,从事鬓惊秋。", "中年亲友难别,丝竹缓离愁。", "一旦功成名遂,准拟东还海道,扶病入西州。", "雅志困轩冕,遗恨寄沧洲。", "岁云暮,须早计,要褐裘。", "故乡归去千里,佳处辄迟留。", "我醉歌时君和,醉倒须君扶我,惟酒可忘忧。", "一任刘玄德,相对卧高楼。"],
"rhythmic": "水调歌头"
}, {
"author": "苏轼",
"paragraphs": ["明月几时有,把酒问青天。", "不知天上宫阙,今夕是何年。", "我欲乘风归去,又恐琼楼玉宇,高处不胜寒。", "起舞弄清影,何似在人间。", "转朱阁,低绮户,照无眠。", "不应有恨,何事长向别时圆。", "人有悲欢离合,月有阴晴圆缺,此事古难全。", "但愿人长久,千里共婵娟。"],
"rhythmic": "水调歌头",
"tags": ["宋词三百首"]
}, {
"author": "苏轼",
"paragraphs": ["昵昵儿女语,灯火夜微明。", "恩冤尔汝来去,弹指泪和声。", "忽变轩昂勇士,一鼓填然作气,千里不留行。", "回首暮云远,飞絮搅青冥。", "众禽里,真彩凤,独不鸣。", "跻攀寸步千险,一落百寻轻。", "烦子指间风雨,置我肠中冰炭,起坐不能平。", "推手从归去,无泪与君倾。"],
"rhythmic": "水调歌头"
}]
哇,原来苏轼写过这么多水调歌头呢。当然王菲唱过的那首最有名,选自宋词三百首。
后记
其实,项目中用到的诗词文件只是这个库里的一部分内容,它还提供了诗词作者的简介,不同条目在各个搜索引擎中的热度,全唐诗的平仄读音,等等。。。诗词检索只是个最基本的使用,我们还能做的更多,例如:
- 诗词填空考试,闯关,PK……各种小游戏,玩中掌握传统文化
- 和文字转语音结合起来做个自动读诗程序,把每天的睡前故事改成睡前读诗,文化熏陶从小做起
- 自动写诗AI,写作没有灵感怎么办?AI助手来帮你
没有做不到,只有想不到😜
当然,直接读文件的效率也是个问题。不过不用担心这个,热心网友也写了把文件导入数据库之类的工具,上github上直接拿来用就行。
只有后端的诗词检索用起来还是不方便的,回头还是要记个TODO把查询页面补上!
完整代码传送:poem
今天的内容就到这了,如果觉得对你有帮助的话,请关注我的微信号,让我们共同成长进步~
本文作者:Jessychen
版权声明:本博客所有文章除特别声明外,均采用CC-BY-NC-SA 4.0 Int'l许可协议
如需转载,烦请注明出处: