原创世界: 【Dragicland】
头衔: 记录世界的探险家
- 帖子
- 6831
- 主题
- 1374
- 探险经验
- 31364
- 卡币
- 14398 F
- 头像出处
- 一只萌灿
|
本帖最后由 羽·凌风 于 2021-12-4 14:56 编辑
事情是这样的,我上周突然犯瘾想找点比普通的word文档更专业的写文工具来用用,但大多数工具都只有手机版,并且实时同步也不如我正在用的在线文档。最后写文工具没找到,我倒是发现了一款叫做“云上写作”的APP,除了基础的写文功能之外,那个APP还自带分析文章中人物登场次数、人物关系统计、常用词统计、人物关系图之类有趣的功能。
详见云上写作APP的宣传页面:http://www.yunshangxiezuo.com/web/ 看起来很有趣是吧
于是我下载了这个APP打算尝试,稍加尝试发现免费版的限制那是相当得多啊,只能建100个关键词、所有的统计页面都只能显示关键词的第一个字,并且只有手机版才有统计分析页面,不能在电脑上查阅!这样玩起来一点也不尽兴啊!因此我继续寻找具有类似功能的软件,别的软件没找到,却找到了许多可以实现类似功能的代码教程!原来这个功能是可以通过Python语言实现的,并且网上也有很多相关教程。
可是这些教程基本上都是面向程序猿的,如果没有编程基础去看那些教程很容易走弯路也很容易从入门到放弃(X)。因此,我决定来写一个面向创作者的零基础教程,手把手教你从零开始,直到为自己的小说搭建起一套自动化导出常用词云图、关键词统计和人物关系图的算法!那么还等什么?开始动手吧!希望每一个作者看完后都能学会哦
顺道一提因为我自己用的系统是Windows,所以这个教程的搭建环境部分只有Windows的内容,但语法部分应该是通用的。
1. 文本分析原理
首先,介绍一下文本分析究竟是如何实现的。程序没办法直接读取自然语言,因此想让程序处理小说,就需要先对小说文本进行处理。这里使用的处理方式是分词,即按照已有的词汇库,把文本中的一个个词汇都拆分出来,然后再让程序去处理这些单独的词汇与它们之间的关系。
举个例子就是,如果我们现在有两句自然语言的文本: - 第一句话:“我今天中午吃了不很辣的火鸡面。”
- 第二句话:“我今天中午吃的火鸡面没有很辣。”
复制代码对于使用自然语言的人类而言,这两句话是相同的意思,但是对于程序而言,它们就只是两个不同的字符串而已,它们之间的关系甚至还不如1和2大。这样是没有办法继续对这两个字符串进行分析的。而如果我们把它们分词,拆开成一个个的词汇,就会变成这样: - 第一句话:“我”+“今天”+“中午”+“吃”+“了”+“不”+“很”+“辣”+“的”+“火鸡面”+“。”
- 第二句话:“我”+“今天”+“中午”+“吃”+“的”+“火鸡面”+“没有”+“很”+“辣”+“。”
复制代码看,拆开之后,这两句话中相同的词组就变得非常多且明显了,程序也就可以直接通过对比这些单独的词组的方式,得出这两句话有很大关联的结论,和我们使用自然语言时的直观感受相同。通过分词,程序就能成功get到,原来这两个完全不同的字符串,其实是有很深关联的,如此一来,程序就相当于“理解”了自然语言。
这里涉及到一个“词典”的概念,词典即程序分词时的基础,能够告诉程序哪些字属于同一个词组,哪些字应该要切开,就和我们在学习自然语言时使用的词典概念差不多。如果词典中没有某个词组,程序就无法学会它,也不会在分词时将其从句子中切割出来。例如上文中的“火鸡面”一词,如果不告诉程序这是一个词组,那么程序就可能将其切割成“火鸡”+“面”。
一个好消息是,本教程后续将使用的分词组件(结巴分词库)自带绝大多数中文日常用语词库,因此不需要自己编写日常用语词典就能满足切词的需求。但坏消息是每个作者都有自己习惯的用词方式和自造词汇,这些词在程序自带词库中是没有的,因此需要我们自行编写。而如何编写属于自己的词典,将在后文着手编写代码时进行讲解。
将小说文本分词之后,我们现在就有了一连串组成小说的词组,这里我使用 《暗月到来的那一天》作为例子,文中的前三段是: - 周末,又是凉爽的秋日,赛琳娜一如往常地宅在寝室床上刷手机,别提多惬意了。维姬则是自律地早早起床洗漱完毕,此刻正一边噘着面包一边浏览购物网站。两人的宠物皆缩在室内,于秋日的肃穆气氛中昏昏欲睡,整个寝室环境安静舒适得让人觉得不太真实。
- 可就在这所有人难得享受闲适之时,校园上空突然响起刺耳的警报声,比警车更急促、比救护车还慑人,是抢险救灾时才会拉响的笛声。快要睡着的一鸟一狗顿时惊醒,随即毫无意外地开始了它们惯例的“天籁”二重奏,震得俩主人脑子嗡嗡响。
- 赛琳娜条件反射地将头探出上铺栏杆外,正欲暴喝制止,维姬却抢先一步猛然站起身,惊叫道:“今天是不是暗月纪念日?”她说话时,寝室门外隐约响起纷杂的脚步声,许多人在走廊上不知聊着什么。
复制代码按照自然段切词完毕后,就会变成这样: - '周末' + ',' + '又' + '是' + '凉爽' + '的' + '秋日' + ',' + '赛琳娜' + '一如' + '往常' + '地' + '宅' + '在' + '寝室' + '床上' + '刷' + '手机' + ',' + '别提' + '多' + '惬意' + '了' + '。' + '维姬' + '则' + '是' + '自律' + '地' + '早早' + '起床' + '洗漱' + '完毕' + ',' + '此刻' + '正' + '一边' + '噘' + '着' + '面包' + '一边' + '浏览' + '购物' + '网站' + '。' + '两人' + '的' + '宠物' + '皆' + '缩' + '在' + '室内' + ',' + '于' + '秋日' + '的' + '肃穆' + '气氛' + '中' + '昏昏欲睡' + ',' + '整个' + '寝室' + '环境' + '安静' + '舒适' + '得' + '让' + '人' + '觉得' + '不太' + '真实' + '。'
- '可' + '就' + '在' + '这' + '所有人' + '难得' + '享受' + '闲适' + '之' + '时' + ',' + '校园' + '上空' + '突然' + '响起' + '刺耳' + '的' + '警报声' + ',' + '比' + '警车' + '更' + '急促' + '、' + '比' + '救护车' + '还' + '慑人' + ',' + '是' + '抢险救灾' + '时才' + '会拉响' + '的' + '笛声' + '。' + '快要' + '睡着' + '的' + '一' + '鸟' + '一' + '狗' + '顿时' + '惊醒' + ',' + '随即' + '毫无' + '意外' + '地' + '开始' + '了' + '它们' + '惯例' + '的' + '“' + '天籁' + '”' + '二重奏' + ',' + '震得' + '俩' + '主人' + '脑子' + '嗡嗡响' + '。'
- '赛琳娜' + '条件反射' + '地' + '将' + '头' + '探出' + '上铺' + '栏杆' + '外' + ',' + '正欲' + "暴喝' + '制止' + ',' + '维姬' + '却' + '抢先一步' + '猛然' + '站' + '起身' + ',' + '惊叫' + '道' + ':' + '“' + '今天' + '是不是' + '暗月' + '纪念日' + '?' + '”' + '她' + '说话' + '时' + ',' + '寝室' + '门外' + '隐约' + '响起' + '纷杂' + '的' + '脚步声' + ',' + '许多' + '人' + '在' + '走廊' + '上' + '不知' + '聊着' + '什么' + '。'
复制代码我们如果想计算小说中每个词组的出现次数,我们就直接让程序遍历这个切词结果,储存每一个词组的数量。例如一开始出现了一个“寝室”,那么就让程序记下 寝室数量 为 1,之后每出现一次“寝室”,就 寝室数量 加 1,最后就能得到“寝室”这个词的数量为 3。如此一来,就能得出文章中每一个词汇出现的次数。
这里涉及到同义词的问题,如这个例子里,“寝室”和“室内”其实可以视为同义词,如果只是判断“寝室”的数量的话,由于不同的字符串在程序眼中是完全不同的东西,那么程序就不会把“室内”的出现次数也记录在“寝室”的数量里。因此,如果我们在判断每个词组时加入同义词判断,即如果“室内”一次是“寝室”的同义词,那么匹配到“室内”时,就给 寝室数量 加 1,这样最后得到的 寝室数量 就能包含“室内”一词出现的次数了。
到这一步,我们已经获取了小说中所有使用到的词汇,以及这些词汇的数量,我们就可以把这些数据拿给制图程序,做词云图和词频图了,最基础的文本分析已经完成
除了单纯的词组计数,文本分析中还有一个重要课题就是词组间的关系。同样以上文为例,我们现在想要计算文中出现的两个人物(赛琳娜和维姬)之间的关联。程序计算关联的原理是,如果两个需要计算的词汇在同一个区域(如同一个自然段、同一个句子、或同一个分句)中同时出现,那么就记录这两个词汇间存在关联。
例如在上文第一段中,程序判断到“赛琳娜”和“维姬”都出现了一次,那么就可以记录下 赛琳娜/维姬的关系 为 1。在上文第三段中,两人的名字又同时出现了,因此可以记录下 赛琳娜/维姬的关系 加 1。最后三段计算结束,两人的关系就是 2。如此一来,以自然段为单位遍历这些词组,我们就可以计算出任意两个选定词汇之间的关联了。
具体要如何用程序实现这个功能呢?就让我们继续往下看吧~
或者,这个系统目前已经上线论坛,大家也可以直接在论坛上玩了(炸):https://www.dragicland.com/forum/viewthread.php?tid=3812
2. Python基础知识
Python:
在开始编程之前,我们需要了解什么是Python。Python是 蟒蛇一种简洁、易读、可扩展性高的计算机编程语言。Python的运算速度快、代码简洁,经常被用作数据分析。
Python库:
Python语言的可扩展模块。你可以把Python理解成游戏,那么Python库就是游戏的DLC,必须先加载库/DLC,你才可以在编写/游戏时使用相应的扩展方法/DLC道具。
Python编辑器:
用于编写和运行Python语言的程序。如果Python是游戏,库是DLC,那么编辑器就是游戏的展示窗口或者说Steam,它可以帮你检查游戏是否安装好DLC、游戏操作是否正确、以及一键启动游戏等等。
命令窗口:
这是Windows系统自带的东西,点开你的Windows开始菜单,在“搜索程序和文件”处输入cmd,即可打开Windows的命令窗口。
打开命令窗口后就是这样的:
3. 编程环境搭建
了解基础知识后,我们就可以开始搭建Python语言所需要的环境了。
安装Python和编辑器:
首先,我们需要安装Python,让电脑“学习”这门语言,这样你才可以自由地调用它。
安装Python可以查看这个教程,讲得非常详细了: https://zhuanlan.zhihu.com/p/111168324
编辑器也推荐安装这个教程里面的Sublime,界面清晰、非常好用。
这里有一点需要注意的是,如果你点击Python安装包后没有出现教程中的界面,而是出现这样的界面:
这就说明你下载的Python安装包版本和电脑版本不匹配,比如我这里电脑是Windows7,下载了适配Windows8.1以上版本的Python 3.9.7,就会提示安装失败。
这时候重新去Python官网下一个匹配Windows7的Python版本(如3.8.10)就好。
Sublime使用Tips:
记住一点就好,Sublime的运行快捷键是Ctrl+B,写好代码后点击快捷键就可以直接运行当前的代码,并将运行结果显示在界面下方的分栏里,快速又方便。
需要注意的是,和安装教程中说的一样,用编辑器写入Python代码后不要直接运行,而是需要保存成扩展名为【.py】的文件、看到代码根据Python语法的规则变色之后,再运行。
如果没有保存文件直接点Ctrl+B运行,是莫得反应的。
4. Python常用代码
安装好Python和编辑器后,打开编辑器(这里使用Sublime Text 3,以下的颜色描述也以此为准),就可以开始学习基础代码啦。
这里先介绍一些我们会在后面使用到的常用代码,这里只会做一些最简单的讲解,目的是帮助大家读我们之后的文本分析处理代码,以及可以有能力按照自己的喜好和需求修改代码(?)
需要注意的是,以下所有代码中的英文字母和符号(如井号、等号、引号、冒号、括号等),皆需要用 英文半角形式输入:
注释
注释从理论上讲不算代码,而是由你自己写的对代码的解释。写在注释中的内容,是 不参与代码运行的,你只能在编写代码的界面看到它们。
注释的方式有两种,即单行注释和多行注释。单行注释在注释前面写【#】(井号)即可,多行注释则用【'''】(三个连续的单引号)将注释内容包裹起来即可。具体如下: - #这是一条单行注释
- '''
- 这是多行注释的第一行
- 这是多行注释的第二行
- 这是多行注释的第三行
- '''
复制代码注释在编辑器中的展示效果为灰色文字,按下运行键后不参与运行,什么事都不会发生:
而没有被包在注释代码中的文本,就会被视为变量——
建立变量
Python中所有的变量在使用之前都需要定义和赋值,使用【=】(等号)定义变量,在定义变量时,变量名称在等号左侧、值在等号右侧。
变量名请尽量使用英文(拼音也可),可加下划线,以避免编码导致的文中变量名乱码、无法调用等问题
变量的赋值可以是数字:- #为变量【number】定义值【0】
- number = 0
复制代码 赋值也可以是文本,注意文本(无论是中文还是英文)的两侧需要使用【''】(单引号)或者【""】(双引号)进行包裹,否则赋的值就不是文本,而是一个以文本为变量名称的变量- #为变量【text】定义值【你好】
- text = "你好"
复制代码 赋值还可以是表示“是非”的布尔值(Boolean),布尔值仅有两种【True】(代表逻辑上的是/对/正确)和【False】(代表逻辑上的非/错),用于进行逻辑判断,注意Python里的True和False的首字母都需要大写:- #为变量【boolean】定义值【True】
- boolean = True
复制代码 以上代码在编辑器中的效果如下,变量表示为白色,等号(赋值和判断)表示为橘红色,数字表示为橘黄色,文本表示为绿色,布尔值表示为斜体红色:
在建立变量的时候需要注意的是,在建立变量时你也许会发现变量名称并不是白色,而是变成了蓝色或者粉色
其中蓝色为预设变量,为预设变量赋值可能会影响到后续代码调用预设的功能;而粉色为预设语法,为其赋值会直接报错
因此如果建立变量时看到颜色不是白色,请更换一个变量名称。
使用变量
变量定义好就可以进行使用了,比如数字可以进行运算,布尔值可以参与逻辑判定等等,数字运算部分很简单:- #为变量【number】的值增加【1】
- number = number + 1
- #或者这样写也可以
- number += 1
复制代码 一些常用的运算符号:加【+】,减【-】,乘【*】,除【/】,乘方【**】(开方也是这个符号,后面接的数字为小于1的数即为开方,如 ** 0.5 就是开二次方)
所有的等式规则都是一致的,即将等号右侧的值赋到等号左侧的变量上。
注意如果你没有对这个变量预先进行定义,直接运算就会报错:
输出栏告诉你【NameError: name 'number' is not defined(名为“number”的变量未定义)】,于是回到变量运算之前去把它定义好就行:
说逻辑判定前先说逻辑判定的等号,需要注意的是【变量 = 值】这个语法在编程中是意味着【给变量赋值】的,因此我们在判断变量是否等于值时不能直接这样写,而是应该写【变量 == 值】,有连续的两个等号,这才意味着判断变量和值是否相等。
逻辑判定需要使用到【if】语法,具体形式如下:- if 条件语句:
- 符合条件时运行这段代码
- else:
- 不符合条件时运行这段代码
复制代码 需要注意的是【if】的条件语句写完后,一定要加上一个【:】(冒号),同理【else】后面也一定要加上【:】(冒号),否则会报错。
同时也需要严格注意Python的缩进,当你用编辑器自己输入这段代码时,你会发现冒号之后的文本缩进了四个字符的宽度,这就代表缩进后的内容属于这个逻辑判断语句,如果冒号之后跟随的内容没有缩进,就会报错。每个冒号后面至少需要一行处理语句。按下【Tab键】可以输入一个缩进,最好不要用【空格键】处理缩进。
如果有多行语句的话该怎么办呢?那就保持缩进继续往下面添加代码即可:- if 条件语句:
- 符合条件时运行这段代码第一行
- 符合条件时运行这段代码第二行
- else:
- 不符合条件时运行这段代码第一行
- 不符合条件时运行这段代码第二行
复制代码 注意,如果某一行语句没有缩进,那么Python会识别这行语句在冒号所管辖的范围之外,也就是说不再受到【if】判断的控制,具体表现如下:- if 条件语句:
- 符合条件时运行这段代码第一行
- 符合条件时运行这段代码第二行
- else:
- 不符合条件时运行这段代码第一行
- 这一行代码将单独执行,不受if控制
复制代码 于是不管number是否大于1,都会输出“我不好”这三个字。
【if】语句可以只进行一次判断,不进行【else】判断。如果你只需要判断条件满足,不需要考虑条件不满足的情况,就不需要写【else】,如此一来当条件不满足的时候就会完全跳过这段【if】判断:- if 条件语句:
- 符合条件时运行这段代码第一行
- 符合条件时运行这段代码第二行
复制代码 同理,当语句不缩进时,就代表从这一句开始,【if】判定就结束了:- if 条件语句:
- 符合条件时运行这段代码第一行
- 符合条件时运行这段代码第二行
- 这一句不再受if影响
复制代码 【if】语句还支持多维的逻辑调用,举个例子就是【if 条件语句1 and 条件语句2】就意味着两个或多个条件同时满足时才进入符合条件的代码,只要有一个条件不满足即忽略这个if
同理把中间的【and】换成【or】就是两个或多个条件只要有一个满足即判定成功。
数组和字典
这是一种更复杂的进阶变量形式,由于我们之后会经常用到它们,所以也在这里一并讲解了。
数组(Array)是一串有序的元素序列,每个元素之间用【,】(逗号)隔开,最外面再用【[]】(方括号)进行包裹。
其形式为:数组 = [元素1, 元素2, 元素3] 以此类推,可以通过数组相关的函数对其中的每一个函数进行调用、计算和处理。
数组作为一个变量,也需要赋值,每个元素赋值的方式和前面的变量赋值形式一致:- #定义一个空的数组【array_1】
- array_1 = []
- #定义一个值为【1,2,3,4,5】的数组【array_2】
- array_2 = [1,2,3,4,5]
- # 在数组中加入一个变量
- array_2.append(6)
- # 此时数组【array_2】就会变成 [1,2,3,4,5,6]
- # 判断数组中存在某个变量
- if 1 in array_2:
- # 判断数组【array_2】是否有1,该句会返回 True
- if 7 in array_2:
- # 判断数组【array_2】是否有7,该句会返回 False
- # 使用直接指定的方式调用一个数组
- value = array_2[0]
- # 该句会提取出数组【array_2】中的第一个元素,即1
- # 注意调用时的序号是从0开始的,所以第一个元素的编号为0,第二个元素的编号为1,以此类推
- # 序号可以使用负数,意即从倒数往前的第几个元素,如[-1]即输出最后一个元素
- # 使用遍历的方式调用一个数组
- for number in array_2:
- # 此句会将数组【array_2】中的每一个值存成number,循环调用,直到数组中的所有元素都调用过一次
复制代码 和【if】语句一样,记得语句判定后面要加【:】(冒号),以及要注意相应的缩进哦。
字典(Dict)比较复杂,是一套键(key)与值(value)一一对应的元素序列,每一套键值之间用【,】(逗号)隔开,键和值之间用【:】(冒号)进行赋值,最外面再用【{}】(方括号)进行包裹。
其形式为:字典 = {键1 : 键1的值, 键2 : 键2的值, 键3 : 键3的值} 以此类推
字典的调用和赋值比较复杂,这里只需要掌握如何建立字典和调取数据就好:- #定义一个空的字典【dictionary】
- dictionary = {}
- # 定义一个有数值的字典
- dictionary = {"name": "名字", "number": 1}
- # 为字典中添加一个键值对
- dictionary["value"] = "值"
- # 此时字典就会变成 {"name": "名字", "number": 1, "value": "值"}
- # 注意 键名 和 值 都是可以用中文的
- # 搜索字典中是否存在某个键名
- if "name" in dictionary:
- # 判断此字典中有没有“name”这个键名,该句为 True
- if "job" in dictionary:
- # 判断此字典中有没有“job”这个键名,该句为 False
- # 输出字典中的特定键值对内容
- print(dictionary["name"])
- # 将会输出上面给“name”赋值的“名字”一词
- # 遍历字典的全部键值对
- for key,value in dictionary.items():
- # 将字典中的每一个键名存为key,将这个key对应的值存为value,每一次遍历一组键值对,直到所有键值对都调用过一次
复制代码 数组的元素、字典的值,不一定是一个单纯的变量,而是可以装另一个数组或者另一个字典的。
到这里大家应该可以看出来,我们前文提到的分词结果便是一个个字符串,可以使用变量和数组来储存,如- 词汇变量 = "寝室"
- 词汇数组 = ["寝室", "赛琳娜", "维姬"]
复制代码 而每个词组的数量就可以通过字典来储存,如- 词汇数量 = {"寝室": 4, "赛琳娜": 2, "维姬": 2}
- 获得寝室的出现次数 = 词汇数量["寝室"]
复制代码 人物关系则可以通过字典嵌套的方式进行储存,如- 人物关系 = {"赛琳娜": {"维姬": 2}}
- 获取赛琳娜和维姬的关系 = 人物关系["赛琳娜"]["维姬"]
复制代码 我们现在有了变量也可以对起进行运算,但你会发现点击运行时虽然不报错,但输出栏并没有任何表示,不知道自己的代码运行到了什么地步,这时候,我们就需要使用打印输出代码了——
打印输出
打印输出的代码为看了上面的安装教程,相信你们已经学会使用这个代码了。
这里需要提一点的就是,print不仅可以输出单纯的字符串,还可以配合前面提到的变量一起使用。
举个例子,我想把上面定义的变量都输出出来,就可以直接这样写:- #输出变量【number】的值
- print(number)
- #输出变量【text】的值
- print(text)
- #输出变量【boolean】的值
- print(boolean)
复制代码 按下运行键后你就可以在输出栏中看到每一个变量值都按照顺序输出了:
数组和字典也是可以print出来的:
print()函数在实际编写代码时非常有用,可以在运行中实时输出每一个变量的情况,帮助我们查看变量运算是否出现问题,以及找到BUG,请爱用print() (X)
5. 函数与Python库的调用
现在,相信你已经掌握了Python的基本语法,那么让我们开始编写代码吧!
这一章不讲具体的代码使用方式,主要是教两个很重要的概念:
函数
emmm,到底是应该叫函数还是叫方法,我也不是很清楚啦,反正没有关系,我们不需要掌握这个内容(X)
函数,你可以理解成是代码文件中的一个独立的模块,只有当调用这个方法的时候它才会运行,并且只在你想调用的地方运行,不调用的时候它就像不存在一样。
函数的定义方式如下: - def 函数(传输给这个方法的变量):
- 该函数的运行代码
复制代码然后直接在你希望使用这个函数的地方,输入这个函数的名字以及传输的变量即可。需要注意的就是函数模块需要写在调用语句的前面,否则会报错,说没有定义该函数。
老生常谈,注意语法后面的冒号,以及缩进哦。
具体操作我们可以看一个实例:
函数还有个【return】语法,含义为将return后面的内容作为函数的输出值,返还给调用的语句(也可以不在return后面加内容,直接return,意味着直接结束这个函数)
当然,我们这个是基础教程,在实际使用过程中,我们一般是不需要自己建立函数的,就算建立也只是一些和上面一样简单的函数。绝大多数时候,我们需要直接调用Python和Python库中的内建函数。
Python库的安装与调用
接下来就结合实例来说明吧,我们现在想要实现的中文文本分析处理功能,需要使用到一个名叫jieba(结巴分词)的Python库,介绍在此:https://github.com/fxsjy/jieba
有了这个库,我们就可以直接调用 cut(字符串) 或者 lcut(字符串) 这条代码,把我们的文本分割成一个个的词组,然后再对词组进行处理。
还记得我之前在基础知识里说的吗?想要使用库中的代码,需要先安装这个库(DLC)
打开Windows的命令窗口,输入如果看到进度条和相应的安装提示,那就表示正在安装这个库。如果没有安装成功,可以试试把上面的【pip】换成【pip3】再试试。
安装完成后,回到Python编辑器,在整个文档的最开始,输入调用Python库的代码:这样一来,我们就可以在这个文档里自由地使用这个Python库中的函数了,让我们来试验一下。
注意在调用Python库的函数时,需要先写这个库的名字,然后再写库中的函数名称,中间用【.】(句号)隔开。含义为调用【ku】里面的函数【func()】
看到下面的输出栏了吗?我们输入的一句话已经被拆成好几个词组了。
到这一步,恭喜你,你已经学会使用文本处理的核心代码了!
如果报错,不要慌,检查一下代码,尤其是看看【import】的部分有没有错误,如果有,请回到前面重新安装Python库即可。
6. 开始编写你的文本分析代码
用Python编辑器打开一个船新的文档,将其保存为.py文件,然后,我们终于可以开始编写我们的文本处理代码了!
这里我就开始疯狂地贴我的代码了,经过上面的讲解,我相信你们现在应该已经能够看懂它们了,应该……
第一步
导入需要使用的Python库,可以根据自己的需求进行取舍 - # 规定当前Python文档为UTF-8编码格式,以免内容中的中文变成乱码
- # -*- coding: utf-8 -*-
- # 一些含有常用函数的库
- import codecs
- from collections import defaultdict
- from pandas import DataFrame
- import pandas as pd
- # 结巴分词的库
- import jieba
- # 生成图像的库
- import matplotlib.pyplot as plt
- # 生成词云图的库
- import wordcloud
- # 生成柱状图的库
- import numpy as np
- # 生成人际关系图的库
- import networkx as nx
复制代码如果在运行时报错说没有找到相应的库,例如报错【ModuleNotFoundError: No module named 'matplotlib'】,那么就按照第4章讲的,打开Windows的命令窗口,输入【pip install matplotlib】安装这个Python库就好,一般情况下库叫啥名字就安装啥名字。
第二步
添加上路径代码,统一定义我们之后会使用到的文件的路径,养成归类管理的好习惯,可以让你的编程事半功倍(?)
这里有一个知识点,Python中的文件路径可以使用绝对路径和相对路径,绝对路径即你打开资源管理器后从地址栏直接复制粘贴得到的路径,相对路径则代表文件和该Python文档之间所对应的路径。
举个例子,我的教程所使用的的文档目录结构如下:
那么我想要用那个【文本分析_教程.py】文件取得【词典】文件夹下的【人名.txt】,其绝对路径为【C:/Users/dell/Desktop/文本分析/教程/词典/人名.txt】,相对路径为【./词典/人名.txt】
建议多使用相对路径,转移工程会很方便(?)- # 存放需要处理的文本的目录,使用txt格式
- TEXT_PATH = './文章/文本.txt'
- # 各类词典的路径
- DICT_PATH = './词典/人名.txt' # 用于保存所有的人物名称
- DICT_PATH_NZ = './词典/专有名词.txt' # 用于保存非人名的专有名词
- DICT_PATH_RELE = './词典/人物关系.txt' # 用于保存人物之间的关系
- DICT_PATH_NO = './词典/停用词.txt' # 用于保存停用词
- SYNONYMOUS_DICT_PATH = './词典/同义词.txt' # 同义词的词库
- # 输出成果的路径
- SAVE_NODE_PATH = './结果/节点.txt' # 保存登场人物和登场次数
- SAVE_EDGE_PATH = './结果/关联.txt' # 保存人物之间的关系权重
- SAVE_CLOUD_PATH = './结果/词云图.png' # 生成文本中所用的词汇的频率词云图
- SAVE_TIMES_PATH = './结果/人物登场次数图.png' # 生成人物登场次数的柱状图
- SAVE_RELET_PATH = './结果/人物关系图.png' # 生成文本中登场的人物
复制代码 这里我使用《暗月到来的那一天》作为测试,人员比较多,主题比较明确,做出来后大家对代码的运行方式可以有一个很直观的了解(?)
第三步
路径调整好后,就可以开始创建词典了。结巴分词并不是你肚子里的蛔虫,无法知道你的文章中哪些词组是人名、哪些词组是专有名词,并且有可能会将你不想分割的人名和词组给分开,因此我们需要定义我们自己专用的词典。所有的词典均为txt格式,并需要保存为“UTF-8”编码格式。
首先是人名词典,格式为一行一个人名,每个人名后面写【 10 nr】用于后续处理,因为是学校篇,所以直接放上小赛和她的小伙伴们:
然后是人物关系,格式为【第一个人名,第二个人名,关系】,注意逗号是半角的:
接下来可以先做同义词词典,这里就涉及到各位自己的写作习惯了,每个角色是否有绰号?是否喜欢使用同义词?有的话就统统加上吧!
同义词词典的写法为多个同义词列于同一行,多个词之间用空格隔开,第一个词为需要导出的词(即代码运行后,同一行后面的词都会被替换为第一个词)
奇迪:你礼貌吗???
最后就是专有名词词典了,格式为一行一个词。直接把同义词词典复制进去吧,然后把同义词的部分切成一行一个做成词典,以避免分词的时候分不出来那些同义词,自然也就没办法把同义词相互绑定起来。人名可以不放在专有词典里,因为人名词典那地方已经加载人名了。
停用词词典可以先建个空文档,等数据处理完毕再看哪些词组是不需要的,到时候再添加。
第四步
继续编写代码,加载完词典后,我们就该把词典调用上了- # 让结巴分词加载自定义词典
- jieba.load_userdict(DICT_PATH)
- jieba.load_userdict(DICT_PATH_NZ)
- # 创建同义词表,每行是一系列同义词,用空格分割,第一个词为导出词
- COMBINE_LIST = {}
- for line in open(SYNONYMOUS_DICT_PATH, "r", encoding='utf-8'):
- seperate_word = line.strip().split(" ")
- num = len(seperate_word)
- for i in range(1, num):
- COMBINE_LIST[seperate_word[i]] = seperate_word[0]
- # 创建停用词列表
- STOP_LIST = [line.strip() for line in open(DICT_PATH_NO, encoding='UTF-8').readlines()]
- # 创建人名和关系列表
- NAME_LIST = {} # 储存人物登场次数
- RELATIONSHIP_LIST = {} # 储存人物关系
- NAME_LINE = [] # 按照段落储存每段内的人物关系
- with open(DICT_PATH, "r", encoding="utf8") as f:
- # 将角色姓名存入列表nameList
- nameList = f.read().replace("\n","").split(' 10 nr')
复制代码 这些都是建立词典的基础代码,自行领会意思吧(X)
第五步
这一段就是结巴分词的核心代码了,使用一段一段遍历文本的形式,对文章的内容进行分词处理,以及处理分词之后的内容,并存入相应的变量中- # 同义词和停用词处理
- def text_editor(word):
- # 同义词处理
- if word in COMBINE_LIST:
- return COMBINE_LIST[word]
- # 停用词处理 and 去除单字
- if word not in STOP_LIST and (len(word) > 1 or word in nameList):
- return word
- return ""
- # 名字处理
- def text_name(word):
- # 当分词不在姓名列表nameList时认为该词不是人名
- if word not in nameList:
- return
- # 处理名字
- NAME_LINE[-1].append(word) # 为当前段的环境增加一个人物
- if NAME_LIST.get(word) is None: # 如果该人名在姓名字典中对应的权值为空(还没有这个键值对)
- NAME_LIST[word] = 0 # 则创建该键值对,参考实例test1.py
- RELATIONSHIP_LIST[word] = {}
- NAME_LIST[word] += 1 # 该人物出现次数加 1
- # 创建词云列表
- CLOUD_LIST = ""
- # 打开文章文本
- with codecs.open(TEXT_PATH, "r", "utf8") as f:
- # 按行处理文本
- for line in f.readlines():
- # 为新读入的一段添加该段的人物名称列表
- NAME_LINE.append([])
- # 对当行进行分词
- for word in jieba.lcut(line):
- # 返回同义词替换和去掉停用词后的句子
- word = text_editor(word)
- # 若词语处理后为空则略过
- if word == "" or word == " ":
- continue
- # 名字处理
- text_name(word)
- # 词云增加
- CLOUD_LIST += word + " "
- # 生成人物关系列表
- # 按行处理
- for line in NAME_LINE:
- # 获取每段中的任意两个人
- for name1 in line:
- for name2 in line:
- if name1 == name2:
- continue
- if RELATIONSHIP_LIST[name1].get(name2) is None:
- # 若两人尚未同时出现则新建项
- RELATIONSHIP_LIST[name1][name2] = 1
- else:
- # 两人共同出现次数加 1
- RELATIONSHIP_LIST[name1][name2] = RELATIONSHIP_LIST[name1][name2] + 1
- # 存储人物节点文件(node.txt)
- with codecs.open(SAVE_NODE_PATH, "w", "utf8") as f:
- f.write("Id,Label,Weight\r\n")
- for name, times in NAME_LIST.items():
- f.write(name + "," + name + "," + str(times) + "\r\n")
- # 存储人物关系文件(edge.txt)
- with codecs.open(SAVE_EDGE_PATH, "w", "utf-8") as f:
- f.write("Source,Target,Weight\r\n")
- for name, edges in RELATIONSHIP_LIST.items():
- for v, w in edges.items():
- f.write(name + "," + v + "," + str(w) + "\r\n")
复制代码 到这一步结束,我们打开【结果】文件夹(或者你储存结果的位置)中的“节点.txt”和“关联.txt”,就已经可以看到计算结果了。
第六步
进行可视化处理,Python可以直接把这些数据导出成图片。
在进行可视化之前,记得给plt(也就是我们前面加载的生成图像的库)添一段设置字体的代码,要设成一个支持中文的字体,这样图上的中文字才可以显示出来:- # 避免中文乱码
- plt.rcParams['font.sans-serif'] = ['SimHei']
复制代码 首先是人物登场次数图的生成,这个很简单- # 人物登场频次柱状图生成
- # 人物按照出现频次从高到低排序
- NAME_LIST = sorted(NAME_LIST.items(), key=lambda d: d[1], reverse=True)
- # 获取X轴和Y轴的数据
- x = []
- y = []
- for name in NAME_LIST:
- x.append(name[0])
- y.append(name[1])
- fig, ax = plt.subplots(figsize=(15, 5))
- ax.bar(
- x=x, # Matplotlib自动将非数值变量转化为x轴坐标
- height=y, # 柱子高度,y轴坐标
- width=0.6, # 柱子宽度,默认0.8,两根柱子中心的距离默认为1.0
- align="center", # 柱子的对齐方式,'center' or 'edge'
- color="grey", # 柱子颜色
- edgecolor="red", # 柱子边框的颜色
- linewidth=2.0 # 柱子边框线的大小
- )
- ax.set_title("人物登场次数柱状图", fontsize=15)
- # 一个常见的场景是:每根柱子上方添加数值标签
- # 步骤:
- # 1. 准备要添加的标签和坐标
- # 2. 调用ax.annotate()将文本添加到图表
- # 3. 调整样式,例如标签大小,颜色和对齐方式
- xticks = ax.get_xticks()
- for i in range(len(y)):
- xy = (xticks[i], y[i] * 1.03)
- s = str(y[i])
- ax.annotate(
- s=s, # 要添加的文本
- xy=xy, # 将文本添加到哪个位置
- fontsize=10, # 标签大小
- color="blue", # 标签颜色
- ha="center", # 水平对齐
- va="baseline" # 垂直对齐
- )
- # 坐标轴名称
- plt.xlabel("人物")
- plt.ylabel("登场次数")
- # 保存图片文件
- plt.savefig(SAVE_TIMES_PATH)
- print("人物登场次数柱状图生成完毕")
复制代码 还记得在前面核心处理时保存了人物名称和登场次数的变量吗?这时候直接把它调用出来就好了。
emmmm,小赛是主角,这非常好(X)
词云图的生成- # 词云图生成
- # 参数都可以注释掉,但必须设置font_path
- wc = wordcloud.WordCloud(
- width=1000,
- height=800,
- background_color="#ffffff", # 设置背景颜色
- max_words=500, # 词的最大数(默认为200)
- max_font_size=300, # 最大字体尺寸
- min_font_size=10, # 最小字体尺寸(默认为4)
- colormap='bone', # string or matplotlib colormap, default="viridis"
- random_state=20, # 设置有多少种随机生成状态,即有多少种配色方案
- # mask=plt.imread("C:/1.bmp"), # 读取遮罩图片!!
- font_path='C:/Windows/Fonts/simhei.ttf'
- )
- my_wordcloud = wc.generate(CLOUD_LIST)
- plt.imshow(my_wordcloud)
- plt.axis("off")
- # 保存图片文件
- wc.to_file(SAVE_CLOUD_PATH)
- print("词云图生成完毕")
复制代码 加载了词云图生成库,再生成词云就非常简单了,注意这里词云获取的是第五布中的【CLOUD_LIST】这个变量,你对【CLOUD_LIST】所做的操作都可以体现在词云库上面。
目前我们生成的词云库长这样:
是不是已经很酷了?不要着急,目前的词云图上还有一些奇奇怪怪的东西,什么【一只】啊【不是】啊【一样】啊这些没什么代表性的词很多,要怎么处理呢?
这时候,打开我们刚才建立的【停用词.txt】文件,将这些不想显示的词全部放进去即可,格式依然是一个词一行。
另外,人名之外的同义词,也可以放到这时候再来统一处理,比如说图上既有【黑岩市】又有【黑岩】那就可以都统一用同义词词典处理成黑岩市啦。
稍加优化后词云图就变成了这样:
可以一边运行词云图一边调整词典,词云图也还有一些参数可以自行设置,祝玩得愉快 (X)
人物关系图的生成- # 关系图生成
- # 生成画布
- plt.figure(figsize=(10, 7))
- # G:图表,一个networkx图
- # networkx提供四种图形结构:
- # G = nx.Graph() 无多重边无向图
- # G = nx.DiGraph() 无多重边有向图
- # G = nx.MultiGraph() 有多重边无向图
- # G = nx.MultiDiGraph() 有多重边有向图
- # 一般只用第一种无向图就好,如果使用有向图,还需要考虑每一条边的方向
- G = nx.Graph()
- # 读取人物节点的原始数据
- GET_Names = []
- with open(SAVE_NODE_PATH, "r", encoding="utf8") as f:
- # 按行处理文本
- for line in f.readlines():
- GET_Names.append(line.replace("\n",""))
- # 读取人物关系的原始数据
- GET_Relationships = []
- with open(DICT_PATH_RELE, "r", encoding="utf8") as f:
- # 按行处理文本
- for line in f.readlines():
- GET_Relationships.append(line.replace("\n",""))
- # 添加人物名称(节点)
- i = 0
- for name in GET_Names:
- # 跳过第一个表头
- if i == 0:
- i += 1
- continue
- G.add_node(name.split(",")[0], weight=int(name.split(",")[2]))
- # 添加人物关系(边)
- i = 0
- with open(SAVE_EDGE_PATH, "r", encoding="utf8") as f:
- # 按行处理文本
- for line in f.readlines():
- # 跳过第一个表头
- if i == 0:
- i += 1
- continue
- # 初始化关系属性
- relation_name = ""
- # 获取相应的人物关系属性
- for relation in GET_Relationships:
- if (relation.split(",")[0] == line.split(",")[0] and relation.split(",")[1] == line.split(",")[1]) or (relation.split(",")[1] == line.split(",")[0] and relation.split(",")[0] == line.split(",")[1]):
- relation_name = relation.split(",")[2]
- # 添加边
- G.add_edge(line.split(",")[0], line.split(",")[1], weight=int(line.split(",")[2]), relationship=relation_name)
- # 图的布局
- # 建立布局,对图进行布局美化,networkx 提供的布局方式有:
- #- circular_layout:节点在一个圆环上均匀分布
- #- random_layout:节点随机分布
- #- shell_layout:节点在同心圆上分布
- #- spring_layout: 用Fruchterman-Reingold算法排列节点(样子类似多中心放射状)
- #- spectral_layout:根据图的拉普拉斯特征向量排列节
- # 这里使用Kamada Kawai布局的最优距离参数,并将非连接组件之间的距离设置为图中的最大距离
- df = pd.DataFrame(index=G.nodes(), columns=G.nodes())
- for row, data in nx.shortest_path_length(G):
- for col, dist in data.items():
- df.loc[row,col] = dist
- df = df.fillna(df.max().max())
- pos = nx.kamada_kawai_layout(G, dist=df.to_dict())
- # 点
- # 获取点的尺寸
- nodeSize_0 = []
- nodeSize = []
- nodeColor = []
- for weights in nx.get_node_attributes(G, "weight").values():
- # 开方以让数据更均匀一些
- nodeSize_0.append(int(weights) ** 0.5)
- # 设置点位最细20px,最粗500px
- min_weight = 100
- max_weight = 300
- # 斜率
- k = (max_weight-min_weight)/(max(nodeSize_0)-min(nodeSize_0))
- # 计算线条的粗细和颜色
- for weights in nodeSize_0:
- nodeSize.append(1+k*(weights-min(nodeSize_0)))
- # 权重超过一半则为蓝色
- if weights > 0.5 * max(nodeSize_0):
- nodeColor.append('dodgerblue')
- else:
- nodeColor.append('lightgreen')
- # 绘制节点
- nx.draw_networkx_nodes(G, pos, alpha=1, node_size=nodeSize,node_shape='o',node_color=nodeColor)
- # 边
- # 获取边的权重列表
- edgeWidth_0 = []
- edgeWidth = []
- edgeColor = []
- for weights in nx.get_edge_attributes(G,'weight').values():
- # 开方以让数据更均匀一些
- edgeWidth_0.append(int(weights) ** 0.5)
- # 设置线条最细1px,最粗10px
- min_weight = 1
- max_weight = 10
- # 斜率
- k = (max_weight-min_weight)/(max(edgeWidth_0)-min(edgeWidth_0))
- # 计算线条的粗细和颜色
- for weights in edgeWidth_0:
- edgeWidth.append(1+k*(weights-min(edgeWidth_0)))
- # 权重超过一半则为红色
- if weights > 0.5 * max(edgeWidth_0):
- edgeColor.append('lightcoral')
- else:
- edgeColor.append('lightgrey')
- #pos:字典类型,节点作为键、位置作为值。位置是长度为2的序列
- #edgelist:边缘元组的集合,只绘制指定的边,默认值为G.edges()
- #width边的宽度,默认值为1.0
- #alpha透明度,默认值为1.0(不透明),0为完全透明
- #edge_color边的颜色,默认值为黑色
- #style边的样式,默认值为实线。
- nx.draw_networkx_edges(G, pos, width=edgeWidth, edge_color=edgeColor)
- # 标签
- # 线条上标注人物关系属性
- labels = nx.get_edge_attributes(G,'relationship')
- nx.draw_networkx_edge_labels(G,pos,edge_labels=labels, font_color='grey', font_size=8)
- #font_size节点标签字体大小,默认值为12
- nx.draw_networkx_labels(G, pos)
- # 生成结果
- plt.axis('off')
- plt.title('人物关系图')
- plt.rcParams['font.size'] = 10
- # 保存图片文件
- plt.savefig(SAVE_RELET_PATH)
- print("人物关系图生成完毕")
复制代码 人物关系图可以说是这里面最复杂的一段代码了,查阅了不少教程,也啃了好久源码,最后把大量教程整合出了一个看起来比较舒服的布局,大家可以直接调用。
导出的结果如下,好看吧!
networkx提供了很多布局模式(还包括和云上写作相似的环形构图),大家可以自行测试,颜色啥的也可以调整,我都把相应的注释写在上面了。
最后,在文档的最末尾加上一句让Python在运行时就把图片弹出来方便查看。
至此,自己只需要把字典写好,这些所有的分析计算都是全自动的哦,超爽der!
我再直接放一个教程文档在这里,如果前面复制粘贴时遇到问题,就用这个吧:
文本分析_教程.py (12.54 KB)
第七步
进阶!哼哼哼,我前面教了大家Python的基础语法和代码编写方式,那么你应该可以在我上面提供代码的基础上自行编写自己需要的统计内容啦!
比如我需要统计文章中所有出现过的物种和出场次数,看看谁比较受到偏爱(?),大部分代码直接复制人名统计的代码即可,然后把和人物关系的部分删掉
那我就在词典区域添加物种的词典和储存位置- # 物种词典
- DICT_PATH_NA = './词典/物种名.txt'
- # 储存位置
- SAVE_SPE_PATH = './结果/物种' + story + '.txt'
复制代码 在创建词库时获取物种的词库- # 加载自定义词典
- jieba.load_userdict(DICT_PATH_NA)
- # 创建物种名称列表
- SPECIES_LIST = {}
- with open(DICT_PATH_NA, "r", encoding="utf8") as f:
- # 将物种名存入列表nameList
- speciesList = f.read().split("\n")
复制代码 在核心代码中添加物种处理方式- # 物种处理
- def text_species(word):
- # 当分词不在姓名列表nameList时认为该词不是人名
- if word not in speciesList:
- return
- # 处理物种名称
- if SPECIES_LIST.get(word) is None: # 如果该人名在姓名字典中对应的权值为空(还没有这个键值对)
- SPECIES_LIST[word] = 0 # 则创建该键值对,参考实例test1.py
- SPECIES_LIST[word] += 1 # 该人物出现次数加 1
- # 打开文章文本
- with codecs.open(TEXT_PATH, "r", "utf8") as f:
- # 按行处理文本
- for line in f.readlines():
- # 为新读入的一段添加该段的人物名称列表
- NAME_LINE.append([])
- # 对当行进行分词
- for word in jieba.lcut(line):
- # 返回同义词替换和去掉停用词后的句子
- word = text_editor(word)
- # 若词语处理后为空则略过
- if word == "" or word == " ":
- continue
- # 名字处理
- text_name(word)
- # 物种处理
- text_species(word)
- # 词云增加
- CLOUD_LIST += word + " "
复制代码 最后在柱状图生成时,把人物列表改成物种列表- # 物种柱状图生成
- import numpy as np
- # 物种按照出现频次从高到低排序
- SPECIES_LIST = sorted(SPECIES_LIST.items(), key=lambda d: d[1], reverse=True)
- # 获取X轴和Y轴的数据
- x = []
- y = []
- i = 0
- for species in SPECIES_LIST:
- x.append(species[0])
- y.append(species[1])
- # 只显示最高的二十个
- i += 1
- if i > 20:
- break
复制代码 这样就可以生成一个物种列表啦!
但……为什么是原人啊!(炸)WWWWWWWWWWW
7. 文本的处理
有了代码后,相信你现在已经跃跃欲试想要用这些代码来处理自己的文章了吧!
但我们的代码只能处理txt格式的文档,因此我们还需要对小说进行处理,把所有小说都转存成txt格式。
我这里提供一个把word(包括.dox和.docx)转存成txt格式并且再把这些txt合并的代码
需要注意的是win32com库的安装不是直接输入这个名字,而是在命令窗口输入: - python -m pip install pypiwin32
复制代码将一个文件夹内的所有word逐个转存成txt的代码: - import jieba
- import os
- import os.path
- from win32com import client as wc
- from wordcloud import WordCloud, ImageColorGenerator, STOPWORDS
- import matplotlib.pyplot as plt
- import numpy as np
- from PIL import Image
- # doc转txt
- c = []
- # word文档的储存路径,注意这个路径必须为绝对路径,不能用相对路径
- rootdir = ["D:/MINE/创作统计/文本分析/doc"]
- def txt(j, c):
- if os.path.exists(c[j]):
- word = wc.Dispatch('Word.Application')
- doc = word.Documents.Open(c[j])
- if c[j][-4:] == ".doc":
- newname = c[j][:-4]
- elif c[j][-5:] == ".docx":
- newname = c[j][:-5]
- doc.SaveAs(newname, 4)
- doc.Close()
- word.Quit()
- print("----格式转换完成")
-
- # 定义函数,进行筛选
- def wordt(c):
- for j in range(0, len(c)):
- if c[j][-4:] == ".doc" or c[j][-5:] == ".docx": # 寻找docx文件
- txt(j, c)
- else:
- pass
- # 定义函数,查找所有文件
- for i in rootdir:
- for parent, dirnames, filenames in os.walk(i):
- for filename in filenames:
- c.append(os.path.join(parent, filename))
- wordt(c)
复制代码将一个文件夹内的所有txt合并为一个txt的代码: - import os
- import os.path
- from win32com import client as wc
- from wordcloud import WordCloud, ImageColorGenerator, STOPWORDS
- import matplotlib.pyplot as plt
- import numpy as np
- from PIL import Image
- import codecs
- import chardet
- import io
- import sys
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8') #改变标准输出的默认编码
- # txt文档的储存路径,可以使用相对路径
- txt_dir = "./原始文档"
- # 导出的合并后txt文档的储存路径和名称,可以使用相对路径
- alltxt_dir = './文章/文本.txt'
- # 合并txt文件
- def join_txt():
- # 获取目标文件夹的路径
- meragefiledir = txt_dir
- # 获取当前文件夹中的文件名称列表
- filenames = os.listdir(meragefiledir)
- # 打开当前目录下的result.txt文件,如果没有则创建
- file = open(alltxt_dir, 'w')
- # 向文件中写入字符 先遍历文件名
- for filename in filenames:
- filepath = meragefiledir + '\\'
- filepath = filepath + filename
- # 遍历单个文件,读取行数
- for line in open(filepath):
- file.writelines(line)
- file.write('\n')
- file.close()
-
- join_txt()
- print("----合并完成")
复制代码注意合并之后的txt不是UTF-8编码,记得转一下编码。
8. 人物关系图的更多处理方式
最后,再介绍一款名叫“Gephi”的软件,这是一款专用于可视化原始数据的软件。官网在此: https://gephi.org/
介绍它主要是因为,如果人物实在是太多了,那么Python的networkx处理起来就会比较吃力,很有可能导致导出的成品图上面有很多重叠的节点、很多看不清关系属性的线条。
那么这时候,我们就可以用Gephi来生成一个更好看的人物关系图。不过介于我比较懒,我还是更喜欢全自动的生成方式,所以这个软件就没有怎么研究(炸)
有关这个软件的教程很多,就请自行在网上搜索吧
至此,我们的手把手教程就完全结束了。从此再也不需要依附于APP,你可以完全自定义输出想要的图表,完全自定义添加想要分析的关键词!
你,学废了吗? |
|