用Python解析古籍史记(一)
文本数据挖掘,分析,信息可视化等学科的作用是通过对文本数据的整理和研究,从各种角度全方位探索文本中的信息,从中提取出有价值的东西,特别是有些隐藏的,不易被普通阅读方式发现的信息模式。
当我们面对海量的文本大数据,无法通过普通的逐字阅读的方式来探索时,这些计算机信息技术和方法就可辅助人们完成曾经不可能完成的任务。
众所周知,这种方法已经被人们大量用于各个领域,工商业,科研,医疗,教育,等等,它的用处就不必多言。
今天,我也想用这种方法来探索一下古籍,先选一本比较有名的古书《史记》来做一下实验。
古人说:"工欲善其事,必先利其器"。技术工具的选用上可以有很多种,对我来说,Python系的工具是我目前最熟悉的,而且它除了支持所有基本的数据科学功能以外,还方便用来对文本进行进行人工智能处理,所以我把它当作首选利器,以下是我的实验计划: 工作台的搭建,文本数据的获取。 文本预处理,去标点,空行,分割,融合,数据结构化,等等。 对文本进行总览分析,信息可视化。 探索文本各方面的细节,信息可视化。
首先,工作台的搭建,我选用云工作台,上面自带有网盘和Python Jupyter Notebook虚拟运行环境,这样比较方便我切换电脑,无论在哪台电脑上,只要有浏览器,我就可以登录到云工作台上,进行研究,保存数据,不需要重新搭建一番工作台。
当然,个人有各自的偏好,也可以在本地搭建工作台。工具库我选择比较通用的Python数据科学三件套,Pandas用于结构化数据处理,Numpy用于矩阵运算, Matplotlib用于信息可视化制图,这三样是必备的,当然还有其他的工具包,等使用到的时候才来介绍一下。
然后我从网上找到《史记》文本的,用任意文本编辑器打开,如下。
先大体看看文本结构,这是一个单文本的《史记》文件,包括十二本纪,十表,八书,三十世家,七十列传,一共有一百三十卷内容。
全文字数有505056字,没有进行文本校对,所以,这不一定是准确的字数,因为网上的文本资料可能是不全的或者有错漏的。但还是非常感谢那些免费发放电子文本的人和机构,让我们有大量的古籍资料唾手可得。
为了便于分析,我打算先进行文本分割,把它分成130份单独的文本。
对于古文的文本分割,我目前所知有几种方法,一是根据关键字,比如"史记卷第xxx"这样的字眼来作为分隔符,把整个文本读入程序中的字符串,再用分割函数,从选定的分隔符入手,自动分割成不同的子字符串,再把每一个子字符串重新存储为单独的文本,就达到了基本能的分割效果。
但并不是每一个原始文本都自带有这样合适的关键字,在这种情况下,还可以手动加入自定义的分隔符,然后用同样的原理来分割。
对于不讲究章节目录的原始文本,还可以进行等量分割,按长度或者文件大小进行等量分割。
我用的是手动加入分隔符的方法,在每一卷后面加入一个自定义的分割符来处理,如下:
然后,我用Python写了一个程序,如下,这不一定是最好,最优雅的程序,但目前够用就行: def 用分隔符分割(path, fileName, bianhao): os.chdir(path) with open(fileName+".htm","r", encoding="utf-8") as file: content = file.read() if content.find("") == -1: print("Skip") return duanluo = content.split("") if os.path.exists(fileName): shutil.rmtree(fileName) if not os.path.exists(fileName): os.makedirs(fileName) os.chdir(path+fileName) index = 1 print(len(duanluo)) for duan in duanluo: duanhao = 数字转汉字(index) if bianhao == 3: prefix = str("{:03}".format(index))+"-" elif bianhao == 2: prefix = str("{:02}".format(index))+"-" with open(prefix+fileName+"卷"+duanhao+".htm", "w", encoding="utf-8") as juan: juan.write(duan) index += 1 os.chdir(path)
这个函数需要用到一个子函数,来进行数字转汉字,以便于给子文件命名为史记卷一,史记卷二...,的汉字序列,如下: def 数字转汉字(num): num_dict = {"1":"一", "2":"二", "3":"三", "4":"四", "5":"五", "6":"六", "7":"七", "8":"八", "9":"九", "0":"零", } index_dict = {1:"", 2:"十", 3:"百", 4:"千", 5:"万", 6:"十", 7:"百", 8:"千", 9:"亿"} nums = list(f"{num}") nums_index = [x for x in range(1, len(nums)+1)][-1::-1] str = "" for index, item in enumerate(nums): str = "".join((str, num_dict[item], index_dict[nums_index[index]])) str = re.sub("零[十百千零]*", "零", str) str = re.sub("零万", "万", str) str = re.sub("亿万", "亿零", str) str = re.sub("零零", "零", str) str = re.sub("零b" , "", str) return str
在传入文本参数执行以上函数后,便得到了以下的分割结果,我的文本是以.htm结尾的,这不重要,这种格式是方便我的古籍网站(http://xinyige.cool)的需要,各人可以根据自己的情况而改变。
我在分割文本时,特意在文件名上加了序号001,002等,不然文本排列的顺序会乱,因为操作系统不会按照汉字的卷一,卷二的顺序来排列,所以在文件名上加上数字序号,操作系统才会识别出文本的顺序来显示。
然后再用一段代码把文本去除掉标点和空行,以及非汉字字符(多是乱码),再把所有子文件转化为一个Pandas数据结构表,以方便结构化处理数据,为了增加代码的中文可读性,我用中文写下了以下代码。 def 去除标点(文句): 标点集 = ",。;?‘’:""!,.;:""""…`○""!()/·•、《》,()" 无标点文句 = [x for x in 文句 if x not in 标点集] 无标点文句 = "".join(无标点文句) return 无标点文句def 去除空行(文句): 无空行文句 = [x for x in 文句 if x.split()] 无空行文句 = "".join(无空行文句) return 无空行文句def 去除非汉字字符(文句): 字符集 = "abcdefghijklmnopqrstuvwxyzàèíùúāīǒ△々◎" 汉字字符 = [x for x in 文句 if x not in 字符集] 汉字字符 = "".join(汉字字符) return 汉字字符文档= "/GujiCool/GujiLib/心一阁藏/史藏/正史/史记/" os.chdir(文档) 文表 = {} 卷目 = [] 卷文 = [] 卷字数 = [] 全文字数 = 0 for 路径, _, 文本 in os.walk(文档): 文本.sort() for 卷 in 文本: 卷目.append(卷.split("-")[1].split(".")[0]) with open(卷) as file: 文句 = 文具.去除标点(file.read()) 文句 = 文具.去除空行(文句) 文句 = 文具.去除非汉字字符(文句) 字数 = len(文句) 全文字数 += 字数 卷文.append(文句) 卷字数.append(字数) 文表 = { "卷目": 卷目, "卷文": 卷文, "卷字数": 卷字数 } 史记文表 = pd.DataFrame(文表)
再用了另一段代码加入预先手工提取出来的卷名(自动提取太麻烦,就直接从文本的目录里拷贝出来,用代码插入Pandas数据表) 史记文表.insert(loc=1, column="卷名", value=卷名) 史记文表.head()
处理的结果如下(只显示前5行),这样就形成了一个便于进一步分析的结构化数据,为什么选择使用Pandas的数据结构呢?因为学过数据科学的都知道,这是一个很强大的数据分析工具,后面大家会看到一些效果。
到此,数据预处理便初步完成了,接下来可以对这样的结构化数据进行一些总览性的探索,以下代码可以用来展示《史记》中每一卷的字数对比图。 tu = 史记文表.plot(x="卷名", y="卷字数", kind="barh", figsize=(16, 50), color="#5fba34") for p in tu.patches: tu.annotate(p.get_width(),(p.get_x()+p.get_width(), p.get_y()), size=15) tu.legend(fancybox=True,shadow=True,fontsize=15) plt.xlabel("卷字数", fontsize=20) plt.ylabel("卷名", fontsize=20) plt.xticks(fontsize=20) plt.yticks(fontsize=20) plt.tight_layout() plt.show() tu.figure.savefig("/GujiCool/文表/史记文表.png")
可以看出,在一百三十多卷《史记》文本中,它们各自的篇幅差别还是挺大的,篇幅最长的是《秦始皇本纪》,还有几篇字数上万的篇章如下:
而字数最少的篇章只有几十个,上百个字,应该是原始的文本资料不全。
查看一下原始数据,果然,这是《史记》十表的内容,原始文件把表的内容省略了,估计是因为原始文本是OCR识别得出的结果,可能因为那时的OCR技术识别不出古籍影印图片中的这部分内容。
我特意查了一下《史记》的古籍影音资料,表是确实存在的,只是文本资料省略去了。看来,古籍数字化的工作还任重道远啊。不过我们比起古人来说,这样已经好太多了。许多古人一辈子可能都看不到一本完整的书。
接下来我想做一下文字的统计,看整部《史记》中用到的单字的频率分布,看那些单字用得最多,通过这样的分布,可以惊鸿一瞥,大概了解一下整部《史记》的文字面貌。
当然,除了单字,最好是看词汇分布,但是古文词汇分词是一个很困难的事情,不像单字分词那么容易,即使是最先进的人工智能模型,也不能够好好解决这个问题。所以,古文的词汇分词我留到下一篇文章再来讨论,那是人工智能自然语言处理科目中一个很基础,很重要的话题。
下面代码用来把史记Pandas文表中的卷文融合在一起,装到一个字符串中,由此可以得到全文的所有单字字符串。 史记全文= "".join(史记文表["卷文"].tolist()) print("全文字数:",len(史记全文)) print("前50个字:", 史记全文[:50])
运行结果如下:
再来看看无重复的单字与全文单字的对比情况,可见无重复的单字只占很少的比例,中文只需要用少量的单字排列组合就可以书写很多内容。 #有多少无重复的单字? 无重复单字 = set(史记全文) print("无重复单字的数量: ", len(无重复单字)) print("无重复单字与全文单字的比例: ", "{:.00%}".format(len(无重复单字)/len(史记全文))) print("前30个无重复的单字: ",sorted(无重复单字)[:30])
再来去除一些停词,也就是助词以及一些常用但可以忽略的词语,如,之乎者也之类的词。可以看出停词占的比例有13%左右。 # 除去停词(助词以及一些常用的但可以忽略的词) 停词 = ["之","乎","者","也","哉","矣","而","不","其","于","曰","以","有","故", "则"] 无停词史记全文 = [ t for t in 史记全文 if t not in 停词 ] print(无停词史记全文[:10]) print("史记全文字数: ", len(史记全文)) print("去停词史记全文字数: ", len(无停词史记全文)) print("无停词文占全文的比例: ", "{:.00%}".format(len(无停词史记全文)/len(史记全文)))
然后可以通过nltk工具包来查看一下《史记》全文单字的频率分布,看哪些字出现的频率最高,来看看排名前十的单字都是些什么(要查看更多的内容,也随时可以查,但是这里篇幅有限,就不一一列出了)。我们可以看出出现最多的是"王"字,有8000多次。 #查看词频分布 史记词频分布 = FreqDist(无停词史记全文) 首十个字 = FreqDist(dict(史记词频分布.most_common(10))) plt.figure(figsize=(25, 10)) plt.xticks(fontsize=20) plt.yticks(fontsize=20) plt.tight_layout() 首十个字.plot()
对于陌生的文本,这些单字或词汇,甚至重复句子的出现频率等,这些信息可以有助于我们了解文本的大体内容是什么,还有作者的行文风格,等等,从而得出一些初步的评估。
我记得有人通过这样的方式,来研究《红楼梦》前八十回和后续的四十回章节是否是同一个作者写的,研究发现,似乎两者的行文风格,用词确实存在着一些隐藏的差别,所以有人估计确实不是同一个人写的。以后我有空也来做一篇《红楼梦》的文本分析,看看有什么发现。
对于词频的分布,除了图表的展示,还有一种特别的可视化方式,叫做词云,我们来做一个史记全文的词云看看是什么效果: # 词云分布 plt.figure(figsize=(25, 10)) wc_fd = WordCloud(background_color="white", font_path="/content/gdrive/My Drive/GujiCool/GujiLib/msyh.ttf") wc_fd.generate_from_frequencies(史记词频分布) plt.imshow(wc_fd, interpolation="bilinear") plt.axis("off") plt.show()
可以看出来,这种频率分布的效果更加显著了,字面越大代表它出现的频率越多,这样更加能够认识到全文的风貌了。
今天就到这里吧,这篇主要集中在全文的单字分析上,下一篇尝试进行词汇分析,以及对单个章节进行更详细的细节分析,了解一下不同章节各自的具体情况。
喜欢的话,请关注与点赞哦!
任琳孙振民全球治理变局及我国的应对方略当前,全球治理格局呈现新变化全球化的发展速度和发展质量面临弱化,全球经济整体正在滑入新一轮滞胀全球化的安全基础面临异化,传统安全的影响再次上升全球化的支撑载体面临分化,全球产业链供
养老金重算补发开始,企业退休人员统一补发1700元吗?头条创作挑战赛养老金重算补发开始,企业退休人员统一补发1700元吗?养老金是大部分退休人员唯一的经济来源,养老金的多少直接决定了退休人员生活质量的好坏。因此,养老金的发放和调整问题
统一企业中国财务长刘子强资历深子公司开盖中奖率不实去年罚6万运营商财经网张杨文统一企业中国作为市场领先饮料及方便面制造商之一,旗下有汤达人统一茄皇藤娇等方便面品牌,且该公司已于2007年12月在港交所上市。公司高层中有位资历很深的高管,目前
这汤好喝好做,五分钟能上桌,天冷热乎乎煮一大锅暖身暖胃少感冒头条创作挑战赛时间飞逝,转眼就过了立冬节气,这时气温不断下降,尤其是早晚温差大,很容易患上感冒等流行的疾病,因此一定要合理安排好日常的饮食起居,注意要添加衣物,饮食上要适当多食甘甜
07年渣滓洞被冲毁,意外发现一个地洞,洞中藏品揭开一段沉痛历史对于国人来说,渣滓洞无疑是一个非常熟悉的名字。这里曾作为国民党的监狱,关押过无数的革命烈士。2007年,渣滓洞受到山洪的影响,监狱的遗址受到了一定程度的损坏。得知这个情况后,有关部
王者荣耀里当我发现对面全是脆皮,就该祭出猴哥了王者荣耀里面的猴哥可谓神奇的存在,虽然在高端局中需要谨慎,但他在中低端局中那玩起来可是相当的爽呀!为什么呢?因为猴子是经常被玩家低估的一个英雄。首先他技能强,有控有输出,其次配合好
血月现象,2022年留念2021年出现的血月现象,今晚又会出现。错过这一次可能要等到2025年。根据天文消息,月全食从1816开始。在东北的小伙伴可以看到月食的全过程,中西部地区可见代食月初,也就是月亮升
就在今晚!错过要等2000年今天(8日)晚上天宇将上演一次精彩的月全食这次月全食有两大特点我国绝大部分地区都可以看到月全食的红月亮阶段届时,我们还能看到更罕见月全食掩天王星月掩天王星罕见伴随月全食出现,下一次
难得一见的红月亮月全食就在今晚,怎么赏?点进来来源交汇点新闻客户端交汇点讯月全食是一种较为少见的天文奇观。今天,一次我国大部地区可观测的月全食奇观将现身天宇,预计月亮初亏阶段将始于17时09分左右,此时月亮开始出现缺口,近4个
不再居次要位置,中国与美俄并肩据美国华盛顿邮报网站11月5日报道,多年来,中国的太空计划一直居于美国之后。但情况不再如此。报道称,本周,中国向近地轨道发射空间站的最后一个舱段并完成对接,实现太空计划的重大飞跃中
再看复古潮流中的新音响贵涵LS58和LS58S原创胡卓勋(无忌)新音响NewAudiophileGRAHAM贵涵LS58和LS58S再次看到这张图,是新音响杂志社美编根据BBC音箱族谱重新绘制的,每次介绍BBC经典监听型号都会