python+nlp+Gephi 分析电视剧【人民的名义】

最近和舍友看完了去年大火的电视剧【人民的名义】,看完觉得里面的人物关系很有意思,决定对其分析分析,也顺便测试一下早前使用过的一些模型(例如word2vec)效果是否能达到预期。

1.获取数据

进行分析之前首先需要获取剧情的文本,因为没有看过小说,为了尽量接近自己和大部分人所熟悉的剧情,这里爬取从百度百科上的每一集的剧情,针对已更新的剧情文本进行分析。利用python的urllib2(python3.3后改为urllib.request)和BeautifulSoup包可以很快的爬下剧情文本,保存为rmdmy.txt文档,顺便将出现的人物名字也一起爬下来,后面进行预处理和分析中涉及到的分词、实体属性对齐和社交网络分析等都将会用到。

# -*- coding: utf-8 -*-
"""
@author: wangyao
"""
#改变默认工作路径
import os 
os.chdir(r"C:\Users\wangyao\Desktop\人民的名义")

##爬取百度百科剧情
import urllib.request
from bs4 import BeautifulSoup
import re
import pandas as pd
url = "https://baike.baidu.com/item/%E4%BA%BA%E6%B0%91%E7%9A%84%E5%90%8D%E4%B9%89/17545218"

import sys
import importlib
importlib.reload(sys)

response = urllib.request.urlopen(url)
con = response.read()
#使用beautifulsoup中的html解析器
cont = BeautifulSoup(con,"html.parser")
content = cont.find_all('ul',{'id':'dramaSerialList'})
content = str(content)
##去掉HTML标签
content1 = re.sub(r'<[^>]+>','',content) 
f = open('rmdmy.txt','w',encoding= 'utf-8') #直接用open打开会报错,需要指定编码方式
f.write(content1)
f.close()

#爬取名字
f = open('rmdmy_name.txt','a',encoding= 'utf-8')
name_content = cont.find_all("dl",attrs={"class": "info"})
for i in name_content:
    name_d = i.get_text().strip().split(u'\n')[0]
    name = name_d.split(u'\xa0')[2]
    #加decode()byte和str才能相加
    f.write(name.encode('utf-8').decode()+'\n')

f.close()

文本文件如下所示:

《python+nlp+Gephi 分析电视剧【人民的名义】》 rmdmy.txt
《python+nlp+Gephi 分析电视剧【人民的名义】》 rmdmy_name.txt

2.文本预处理

将剧情爬下来后需要对文本进行预处理,主要包括分句、分词、去掉一些特殊符号和停用词、实体对齐和属性对齐等。如果一个人可能在剧中有不同的名字,这时候就需要进行统一。为了尽量正确的切分一些固定名称,需要导入自定义词典,主要包含一些人名、地名和组织名称等(例如这里需要加入侯亮平,汉东省,汉东大学,山水集团,大风厂等等)。此外,在提取文本特征时需要去掉一些停用词,以提高分析的准确度。经过一系列处理后得到比较干净的文本分词结果,然后就可以在此基础上进行深入的分析。

#文本预处理
import jieba
jieba.load_userdict('rmdmy_dict.txt')#自定义词典
stopword = [line.strip() for line in open('StopwordsCN.txt',encoding= 'utf-8').readlines()] #简体中文停用词

fr = open('rmdmy.txt','r',encoding= 'utf-8')
con = [fr.readlines()]
'''
分词,并去掉特殊字符、词语
'''
fw = open('rmdmy_content.txt','w',encoding= 'utf-8')
for i in con[0]:
    #if len(i.decode('utf-8'))<=10:
    if len(i)<=10:
        pass
    else:
        w1 = i.split("。")#按句号分句
        for j in w1:
            w2 = re.sub(r',|。|?|:|“|”|!','',j.strip())#去掉特殊字符
            #w1 = re.sub(name1,name2,w1) #实体对齐
            w3 = list(jieba.cut(w2))#分词
            w4 = [w for w in w3 if w not in stopword]#去掉停用词
            outstr = ''
            for word in w4:
                outstr +=word
                outstr +=' '
            fw.write(outstr.strip().encode('utf-8').decode())
            fw.write('\n')
fw.close()

预处理结果:

《python+nlp+Gephi 分析电视剧【人民的名义】》 rmdmy_content.txt

3.人物出现频次和社交网络关系

先看一下剧中出场次数较多的关键人物有哪些,根据之前爬下来的名字列表,统计其在文本中出现的次数,通过matplotlib包画出出现次数最多的10个关键人物如图所示,可以发现侯亮平出现次数最多,共483次,其次李达康出现了226次,再次是高育良出现了211次,祁同伟202次。整部剧穿插的几大事件都在围绕着这四个人展开,而沙瑞金虽然也是贯穿全剧的重要人物,出场次数也很多(148次),但是大部分事件里都是出来打酱油的,所以次数还低于大风厂事件的蔡成功。

#人物出场次数统计
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt

import matplotlib.font_manager as fm
font_yahei_consolas = fm.FontProperties(fname = 'simsun.ttc') 
#引入字体,否则文字显示不出来

#python 字符串转列表 list 出现\ufeff的解决方法(网上)
##文件内容 lisi
#lock = open("lock_info.txt", "r+",encoding="utf-8")
#lock_line = lock.readline()
#lock_list = lock_line.split(",")
#print(lock_list)
# 
#y = lock_line.encode('utf-8').decode('utf-8-sig')
#print(y)
# 
##打印结果如下
#['\ufefflisi']
#lisi
#自己测试,在notepad++上把编码从UTF-8编码模式改成UTF-8无BOM编码模式,ufeeff就会消失


with open('rmdmy_dict.txt',encoding= 'utf-8') as f1:
    data1 = f1.readlines()
with open('rmdmy_content.txt',encoding= 'utf-8') as f2:
    data2 = f2.read()
    
#匹配词典里的名字和剧本内容里的该名字出现的次数
count = []
for name in data1:
    count.append([name.strip(),data2.count(name.strip())])
count1 = []
for i in count:
    if i not in count1:
        count1.append(i)
count = count1

count.sort(key = lambda x:x[1])
ay,ax = plt.subplots()
numbers = [x[1] for x in count[-10:]]
names = [x[0] for x in count[-10:]]
ax.barh(range(10),numbers,color=['peru','coral'],align = 'center')
ax.set_title('人物出场次数',fontsize = 14,fontproperties = font_yahei_consolas)
ax.set_yticks(range(10))
ax.set_yticklabels(names,fontsize = 14,fontproperties = font_yahei_consolas)
plt.show()

人物出场频次条形图:

《python+nlp+Gephi 分析电视剧【人民的名义】》 人物出场次数

再来看看剧中人物的社交关系情况,采用以句为单位来进行分析(有时候也以段落为单位来识别人物关系,但采集的文本每集只有一个段落,所以不适用),即如果两个人同时出现在一句话中,那说明他们之间肯定有某种联系。因此可以得到他们的社交网络关系。通过求得的共现矩阵,使用Gephi画出下面的社交网络关系图,图中边的粗细代表关系的密切程度,边越粗表示两人的关系越密切,而名字的大小可以表示为该人的社交人脉强弱情况。

#匹配词典里的名字和剧本内容里的该名字出现的次数
count = []
for name in data1:
    count.append([name.strip(),data2.count(name.strip())])
count1 = []
for i in count:
    if i not in count1:
        count1.append(i)
count = count1

count.sort(key = lambda x:x[1])
ay,ax = plt.subplots()
numbers = [x[1] for x in count[-10:]]
names = [x[0] for x in count[-10:]]
ax.barh(range(10),numbers,color=['peru','coral'],align = 'center')
ax.set_title('人物出场次数',fontsize = 14,fontproperties = font_yahei_consolas)
ax.set_yticks(range(10))
ax.set_yticklabels(names,fontsize = 14,fontproperties = font_yahei_consolas)


#社交网络关系(共现矩阵)
f2 = open('rmdmy_content.txt','r',encoding= 'utf-8')
word = f2.readlines()
name = data1
#name = data1[1:]
#总人数
wordcount = len(name) 

#初始化128*128值全为0的共现矩阵
cormatrix = [[0 for col in range(wordcount)] for row in range(wordcount)] 
#遍历矩阵行和列  
for colindex in range(wordcount):
    for rowindex in range(wordcount):
        cornum = 0
        #如果两个人名字在同一句话里出现,那么共现矩阵中两个人对应的值加1
        for originline in word:
            if name[colindex].strip() in originline and name[rowindex].strip() in originline:
                cornum += 1
        cormatrix[colindex][rowindex] = cornum

cor_matrix = np.matrix(cormatrix)
for i in range(len(name)):
    cor_matrix[i,i] = 0
social_cor_matrix = pd.DataFrame(cor_matrix, index = name,columns = name)
#把共现矩阵存进excel
social_cor_matrix.to_csv('social_cor_matrix.csv')

social_contact = pd.DataFrame(columns = ['name1','name2','frequency'])
#共现频率
for i in range(0,len(name)):
    for j in range(0,len(name)):
        if i<j and cormatrix[i][j] > 0:
            social_contact.loc[len(social_contact),'name1'] = name[i]
            social_contact.loc[len(social_contact)-1,'name2'] = name[j]
            social_contact.loc[len(social_contact)-1,'frequency'] = cormatrix[i][j]

social_contact.to_excel('social_contact.xlsx',index = False)

社交情况:

《python+nlp+Gephi 分析电视剧【人民的名义】》 社交网络

4.走进‘大风厂’事件

接下来重点探索一下我比较感兴趣的“大风厂”事件,先通过关键字抓取出相关剧情,然后使用python的wordcloud包生成词云,wordcloud可以导入图片自定义词云的形状,非常方便,但是需要注意中文编码和字体的问题,否则生成的词云会显示成乱码。

#大风厂(感兴趣的关键字,可能需要加入到词典中)
text = []
#遍历每句话
for line in word:
    if '大风厂' in line:
        text.append(line)

#词频统计
dict_dz = {}
for i in text:
    dz1 = i.split(' ')
    for w in dz1:
        w1 = w.strip()
        if dict_dz.__contains__(w1) :
            dict_dz[w1] += 1
        else:
            dict_dz[w1] = 1     
                            
#生成text
text1 = ''
for i in text:
    dz2 = i.split(' ')
    for w in dz2:
        text1 =text1 +' '+ w
                
#生成词云图
from wordcloud import WordCloud,STOPWORDS,ImageColorGenerator
#读取背景图片信息保存为array
background_Image = plt.imread('c1.jpg')
font = 'msyh.TTF'  

#设置字体格式路径,不然显示不了中文,
#可以改成r'C:\Windows\Fonts\xxxx.ttf'来切换其他字体,这边我们把文件放在默认文件夹中。
#选择已经有的字体,根据词频否则生成图片的时候会报错:OSError: cannot open resource

wc = WordCloud(background_color = 'white',mask = background_Image,
               max_words = 2000,stopwords = STOPWORDS,font_path = font,
               max_font_size = 80,random_state = 42,scale = 1.5).generate(text1)

#这种方法和上面直接generate()的结果相同,但是传入的数据格式不同,
#这个函数传入的是要给字典,key(键)为词,value(值)为出现频率。
#wc.generate_from_frequencies(dict_dz) 

#根据图片生成词云颜色,这里选择显眼的颜色
#如果需要黑白灰的词云颜色就把'#'删除
#image_colors = ImageColorGenerator(background_Image)
#wc.recolor(color_func = image_colors)
plt.imshow(wc)
plt.axis('off')
plt.show()
wc.to_file('c2.jpg') #保存图片

生成的词云图:

《python+nlp+Gephi 分析电视剧【人民的名义】》 wordcloud

词云中词语越大代表和大风厂这个词相关度越高,可以看出和大风厂关系最紧密的有:蔡成功,陈岩石,郑西坡,李达康,职工股权,侯亮平,高小琴,山水集团等

4.1.基于TF-IDF提取关键词

上面的词云图表示了大风厂同时出现频数多的词,但出现频数多的词并不能代表是文本中的关键词,故使用TF-IDF进行关键词提取。TF-IDF权重通过计算词频和逆向文件频率来计算,这里直接利用jieba分词工具进行提取,列出20个关键词如下表所示:

【蔡成功,李达康,郑西坡,陈岩石,侯亮平,赵东来,沙瑞金,陈清泉,职工,高小琴,山水集团,尤瑞星,郑乾,欧阳菁,股权,打电话,常成虎,京州,陈海,郑胜利】

可以发现与大风厂相关的主要人物都列出来了,此外,还有一些特别词,如山水集团,京州,职工,股权等,通过这些关键词我们可以比较清楚的知道有关大风厂的大部分信息。我们可以推测出:大风厂事件很可能和山水集团还有职工股权纠纷有关,其事件主要涉及到的人是山水集团、京州公务员、大风厂职工,各部分人在事件中扮演不同的角色。如果想看到每个词具体的TF-IDF权重也可以利用scikit-learn包进行计算,然后再根据权重的大小进行重要性排序。

#基于tf-idf提取关键词
from jieba import analyse
tfidf = analyse.extract_tags
#analyse.set_stop_words('stop_words.txt') #使用自定义停用词集合

text_dz = ''
for l in text:
    text_dz += l
    text_dz += ' '
keywords = tfidf(text_dz,topK=20)
print (keywords)

4.2.运用word2vec挖掘语义相似关系

由于文本分析中也经常会用到word2vec将文本转化为词向量的形式后来挖掘词语语义上的相似关系。这里我们使用word2vec来测试一下模型效果。导入gensim库后,将文本转化为模型输入的形式后进行训练,然后就可以得到每个词的向量形式。

4.2.1 先简单介绍一下word2vec的基本使用:

训练模型的定义:

from gensim.models import Word2Vec  
model = Word2Vec(sentences, sg=1, size=100,  window=5,  min_count=5,  negative=3, sample=0.001, hs=1, workers=4)  
#参数解释:
#其中的sentences是句子列表,而每个句子又是词语的列表,即list[list]类型。
#1.sg=1是skip-gram算法,对低频词敏感;默认sg=0为CBOW算法。

#2.size是输出词向量的维数,值太小会导致词映射因为冲突而影响结果,值太大则会耗内存并使算法计算变慢,一般值取为100到200之间。

#3.window是句子中当前词与目标词之间的最大距离,3表示在目标词前看3-b个词,后面看b个词(b在0-3之间随机)。

#4.min_count是对词进行过滤,频率小于min-count的单词则会被忽视,默认值为5。

#5.negative和sample可根据训练结果进行微调,sample表示更高频率的词被随机下采样到所设置的阈值,默认值为1e-3。

#6.hs=1表示层级softmax将会被使用,默认hs=0且negative不为0,则负采样将会被选择使用。

#7.workers控制训练的并行,此参数只有在安装了Cython后才有效,否则只能使用单核,anaconda会自带Cython。

训练后的模型保存与加载:

model.save(fname)  
model = Word2Vec.load(fname)  

模型使用

model.most_similar(positive=['woman', 'king'], negative=['man'])  
#woman+king-man的词向量结果:输出[('queen', 0.50882536), ...]  
  
model.doesnt_match("breakfast cereal dinner lunch".split())  
#输出'cereal' ,分出不是一类的词 
  
model.similarity('woman', 'man')  
#两个词的相似度,输出0.73723527  
  
model['computer']  # raw numpy vector of a word  
#某个词的特征,输出array([-0.00449447, -0.00310097,  0.02421786, ...], dtype=float32)  
4.2.2 分析与‘大风厂’相似的词语关系
#word2vec
import gensim
sentences = []
for line in word:
    if line.strip() != '':
        sentences.append(line.strip().split(' '))

model = gensim.models.Word2Vec(sentences,size = 100,window = 5,min_count = 5,workers = 4)        

例如打印出“大风厂”的向量如输出结果所示。

print (model['大风厂'])

《python+nlp+Gephi 分析电视剧【人民的名义】》 向量图

我们也可以输出通过word2vec生成向量之后,和‘大风厂’最相似的向量

#这里我们输出相似度最高的18个(本来取了20个,最后两个词无特别含义,这里我们topn取18)
for k,s in model.most_similar(positive = ['大风厂'],topn=18):
    print (k,s)

《python+nlp+Gephi 分析电视剧【人民的名义】》 关联词向量

最后,我们测试一下word2vec的效果,由于篇幅有限这里就不介绍word2vec原理了,具体可以去看peghoty大神的word2vec系列博客。https://blog.csdn.net/itplus/article/details/37969635
需要注意的是:如果使用word2vec建议不要去除停用词,因为word2vec用于发现上下文的关系,如果去除了停用词,生成的词向量可能会受影响,最后生成的词向量差异过小没有区分度,这里我们为了方便还是使用去除停用词的语料,这导致了出现的关联词向量相似度两极分化的现象。另外在深度学习中也是不需要去除停用词的,但是去除了模型结果会有略微提升

word2vec模型的优点在于不仅考虑了语境信息还压缩了数据规模。了解word2vec的人都知道,他的强大之处在于可以利用基本代数公式来发现单词之间的关系(比如,“国王”-“男人”+“女人”=“王后”)。所以word2vec生成的词向量很明显可以应用于:代替词袋用来预测未知数据的情感状况。
现在这些词向量已经捕捉到上下文的信息,我们来看一下效果:
计算一个词,该词的词向量和词向量(山水集团+大风厂-高小琴)最接近。
由于高小琴是山水集团的boss,而蔡成功是大风厂的boss,那么按照word2vec的原理我们可以自己推理出,这个词应该是’蔡成功’。

r = model.most_similar(positive = ['山水集团','大风厂'],negative=['高小琴'],topn=1)
print (r)

结果为:

《python+nlp+Gephi 分析电视剧【人民的名义】》 word2vec运算结果.png

[(‘蔡成功’, 0.9983329772949219)]
和预期结果一样,这就是word2vec的强大之处。

另:如果要直观的看词向量的效果,在词向量数量不是特别多的情况下,可以使用pca对词向量降维,降到两维度或者三维就可以进行可视化。

References:

图片和文字内容爬取自百度百科
[1]http://blog.csdn.net/AlanConstantineLau/article/details/72146213
[2]http://blog.csdn.net/xiemanr/article/details/72796739
[3]https://www.zhihu.com/question/21268129
[4]http://blog.csdn.net/u010309756/article/details/67637930
[5]https://www.cnblogs.com/mjiang2017/p/8431977.html
[6]https://blog.csdn.net/itplus/article/details/37969635
[7]https://radimrehurek.com/gensim/models/word2vec.html

    原文作者:wong小尧
    原文地址: https://www.jianshu.com/p/46c39eb1e775
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞