Python文本整理案例分析:《全唐诗》文本整理
在整理《全唐诗》的文本之前,我们首先需要完成以下两个步骤:
-
确定需求
-
了解文本
在完成以上步骤后,我们开始实际着手整理文本,在整理的过程中大体上也包含两个流程:
- 文本解析
- 结果输出
全唐诗文本语料在“全唐诗.txt”文件中,请参考语料阅读以下内容。
确定需求
我们计划将《全唐诗》中的每一首诗的各种信息分别提取出来,并转存为csv的形式。根据对文本的初步了解,我们发现我们需要提取的信息(即绝大部分诗文都包含的共性信息)包括:
- 诗文的所属的卷编号(后简称卷编号)
- 诗文的在当前卷中的序号(后简称诗编号)
- 诗文的标题
- 诗文的作者
- 诗文的内容
虽然有的诗并没有作者(例如卷899_19),但是在整体结构设计的时候不用考虑它们。
了解文本
在了解文本的过程中,主要围绕需要提取的信息的形式;通过了解文本,我们基本上得到可以解析大部分文本内容的规律性方法。
卷25_7【杂曲歌辞·侠客行】
李白
赵客缦胡缨,吴钩霜雪明。银鞍照白马,飒沓如流星。
十步杀一人,千里不留行。事了拂衣去,深藏身与名。
闲过信陵饮,脱剑膝前横。将炙啖朱亥,持觞劝侯嬴。
三杯吐然诺,五岳倒为轻。眼花耳热后,意气素霓生。
救赵挥金槌,邯郸先震惊。千秋二壮士,烜赫大梁城。
纵死侠骨香,不惭世上英。谁能书阁下,白首太玄经。
卷106_7 【送金城公主适西蕃应制】郑愔
下嫁戎庭远,和亲汉礼优。笳声出虏塞,箫曲背秦楼。
贵主悲黄鹤,征人怨紫骝。皇情眷亿兆,割念俯怀柔。主
首先,通过了解我们发现每首诗的形式,都类似以上的形式。以一个标题行开始,标题行中基本上都包括卷编号和诗编号(卷[0-9]+_[0-9]+
)和标题(【[^】]+】
)两部分,有的时候也会包含作者名。因此,我们可以以标题行为标志,一旦发现标题行,就认为一首诗的内容已经结束,下一首诗的内容即将开始(即完成一首诗的整理)。
接着,通过仔细观察,我们发现标题行、诗文中偶尔会有多余的空格(包括半角或全角)出现,在诗文末尾还会有校注者知古斋主的标注。因此,对每一行我们需要进行初步的清洗,包括移除空格、换行符和校注者的标注((?<=[),。])[知古斋主]$
)。
至此,我们已经可以提取出卷编号、诗编号、标题、内容这四部分,但是作者部分仍然存在一些问题。经过进一步的观察,我们发现当作者位于标题行时,作者名是除了卷编号、诗编号、标题以外的唯一的中文内容;当作者名存在于标题行之后的某一行中时,该行为不包含任何标点符号的一行。
此外,我们发现,卷的标题(“第一百六十三卷”、“卷一百六十四”等)、版权信息等内容也是非诗文的无效内容,我们也需要通过正则表达式等方法过滤掉它们。
文本解析
在对文本初步了解的基础上,我们可以实现对文本的解析。整体逻辑结构如下:按行遍历文本语料;若该行为标题行,则解析当前诗作信息;若该行非标题行,则将当前行的内容存入当前诗作的内容中。
在遍历的过程中,我们需要对各行的文本语料进行清洗,并过滤空行、无效行等。
在解析过程中,我们可以将解析的结果存储到list中,list中的每个元素为一首诗,每个元素包括卷编号、诗编号、标题、作者、内容五个属性。
读取文本语料
首先,按行读取txt格式的《全唐诗》文本语料。
with open("全唐诗.txt", encoding="UTF-8") as file:
lines = file.readlines()
print("总行数:", len(lines))
清洗文本语料
对每一行都进行的文本清洗:
line = line.replace("\n", "").replace(" ", "").replace(" ", "")
line = re.sub("卷[一二三四五六七八九十百]+", "", line)
line = re.sub("第[一二三四五六七八九十百]+卷", "", line)
仅对文本内容行进行的文本清洗:
line = line.replace("¤", "。") # 将错误句号替换为标准句号
line = re.sub("(?<=[),。])[知古斋主]$", "", line) # 剔除校注者名称
过滤无效行(先文本清洗再过滤无效行,一遍将“第一百六十三卷”等过滤掉):
if "知古斋主精校" in line or "版权所有" in line or "[email protected]" in line:
continue
文本语料解析
在标题行中提取卷编号(Python 3.8+):
if book_regex := re.search("(?<=卷)[0-9]+(?=_)", line):
book_num = int(book_regex.group()) # 读取卷编号
在标题行中提取诗编号(Python 3.8+):
if poem_regex := re.search("(?<=_)[0-9]+", line):
poem_num = int(poem_regex.group()) # 读取诗编号
在标题行中提取标题(Python 3.8+):
if title_regex := re.search("(?<=【)[^】]+(?=】)", line):
title = title_regex.group() # 读取标题
在标题行中尝试提取作者(Python 3.8+):
line = re.sub("卷[0-9]+_[0-9]+", "", line)
line = re.sub("【[^】]+】", "", line)
if author_regex := re.search("[\u4e00-\u9fa5]+", line):
author = author_regex.group() # 如果作者名位于标题行,则为清除其他所有内容后剩余的中文
在非标题行中尝试提取作者:
if not re.search("[,。?!]", line):
author_regex = re.search("[\u4e00-\u9fa5]+", line)
author = author_regex.group()
非标题行中的内容在清洗之后直接添加到诗文的内容中即可。
结果输出
在文本解析完成后,我们可以将存储在list中的临时数据存储到文件中。
with open("全唐诗(清洗后).txt", "w+", encoding="UTF-8") as file:
for poem_item in poem_list:
file.write(",".join([str(poem_item["卷编号"]), str(poem_item["诗编号"]), poem_item["标题"], poem_item["作者"],
poem_item["内容"]]) + "\n")