原标题:长文干货 | 基于豆瓣影评数据的完整文本分析!
作者:沂水寒城,CSDN博客专家,个人研究方向:机器学习、深度学习、NLP、CV
Blog: http://yishuihancheng.blog.csdn.net
文本分析中很多的工作都是基于评论数据来进行的,比如:滴滴出行的评价数据、租房的评价数据、电影的评论数据等等,从这些预料数据中能够挖掘出来客户群体对于某种事物或者事情的看法,较为常见的工作有:舆情分析、热点挖掘和情感分析。
在之前的工作经历中,我对微博数据和电影评论数据进行文本分析工作较多,今天的文章主要就是想以影评数据为切入点介绍一些自己文本分析的流程和方法,本文简单的实现流程如下图所示:
主要分为:数据采集、数据清洗存储、主题挖掘、分词与词频统计、词云展示几个部分。
一、影评数据采集
影评数据有很多网站可以去获取,比如最常用的猫眼电影、豆瓣电影等等,本文是基于豆瓣电影完成的数据采集工作,这个具体的采集项目网上都有很多详细的讲解与实现,这里我就不再对采集过程进行过多的介绍,直接看代码实现。
首选需要实现对于给定电影名称获取到其对应的id功能,因为在数据中电影数据项都是以id作为标识的,这里的代码实现很简单,主要是就是需要找到具体请求的API即可:
defgetIMDBIdByName(name='勇敢的心',save_path='id_title.txt'):
'''''
基于剧名查询获取 id
'''
url="https://movie.douban.com/j/subject_suggest?q="+urllib.quote(name)
data=getJsonData(url)
ifdata:
id_list=[one['id'] forone indata]
title_list=[one['title'] forone indata]
withopen(save_path,'a') asf:
fori inrange(len(id_list)):
f.write(','.join([id_list[i],title_list[i]]).strip+'n')
获取到的电影id会存储在我们默认的文件id_title.txt中,之后影评数据的获取会从这里读取数据来构建映射字典。
完成上述功能后我们就可以对给定名称的电影获取其对应的影评数据了,这里给出来样例爬虫代码的具体实现:
defdemoSpider(movie_id='12345689',offset=0):
'''''
爬虫数据展示
'''
res_list=[]
url="https://movie.douban.com/subject/{0}/comments?start={1}&limit=20&sort=new_score&status=P".format(movie_id,str(offset))
header,proxy=buildProxy
res=getPageHtml(url,header,proxy,flag=True,num_retries=3)
soup=BeautifulSoup(res.content,'html5lib')
div_comment=soup.find_all('div',class_='comment-item')
forone indiv_comment:
username=one.find('div',class_='avatar').a['title']
comment_time=one.find('span',class_='comment-time')['title']
votes=one.find('span',class_='votes').get_text
comment=one.p.get_text
one_list=[username.strip,comment_time.strip,votes.strip,comment.strip]
printone_list
res_list.append(one_list)
offset+=20
time.sleep(random.randint(3,8))
url="https://movie.douban.com/subject/{0}/comments?start={1}&limit=20&sort=new_score&status=P".format(movie_id,str(offset))
header,proxy=buildProxy
res=getPageHtml(url,header,proxy,flag=True,num_retries=3)
soup=BeautifulSoup(res.content,'html5lib')
div_comment=soup.find_all('div',class_='comment-item')
forone indiv_comment:
username=one.find('div',class_='avatar').a['title']
comment_time=one.find('span',class_='comment-time')['title']
votes=one.find('span',class_='votes').get_text
comment=one.p.get_text
one_list=[username.strip,comment_time.strip,votes.strip,comment.strip]
res_list.append(one_list)
returnres_list
仔细看上述代码会发现,这里的实现是非常简单的,我们的翻页操作是基于网页的offset偏移量来间接完成的。上述代码实现了指定电影评论数据的采集工作。
如果在实际应用中出现大量数据采集工作的话需要考虑一些网站的反爬虫机制,这里我较为常用的三种反反爬虫机制主要包括:随机休眠机制、随机User-Agent伪装机制和动态IP代理池构建机制,如果对这方面感兴趣的话可以阅读我的头条号系列文章,搜索《反反爬虫机制三重奏》即可,这里我基于代码实现了随机UA和代理池的功能,具体不多解释了,直接看代码实现即可:
defbuildProxy:
'''''
构建代理信息
'''
header_list=generateRandomUA(num=500)
header={'User-Agent':random.choice(header_list)}
ip_proxy=random.choice(ip_list)
one_type,one_ip,one_port=ip_proxy[0],ip_proxy[1],ip_proxy[2]
proxy={one_type:one_type+'://'+one_ip+':'+one_port}
returnheader,proxy
二、影评数据清洗存储
完成第一部分的工作后,我们就采集到了所需的评论数据,但是这里的评论数据难以直接用于分析工作,我们需要对其进行解析处理后,对所需的评论文本数据进行清洗后才能够使用,这里简单对相关的工作进行说明。
我们随便打开一部电影《红海行动》的影评数据文件,找出前3条评论数据样例如下所示:
评论人:梦梦梦梦
评论时间:2018-02-1600:05:42
支持人数:14673
评论内容:本来对这类电影不感兴趣,陪着男朋友去看的,很意外,还不错,一部很燃的片子,俩个多小时的电影,至少一个半小时的高潮,全程无尿点,据说是根据真实事件改编的,海陆空作战,超级帅。算是春节档电影的一股清流,大家真的要感受一下中国军人的风采,只想说威武!!佟莉炸飞机还有狙击手对战那段太帅了
评论人:乌鸦火堂
评论时间:2018-02-1315:35:16
支持人数:10557
评论内容:春节档最好!最好不是战狼而是战争,有点类似黑鹰坠落,主旋律色彩下,真实又残酷的战争渲染。故事性不强,文戏不超20分钟,从头打到尾,林超贤场面调度极佳,巷战、偷袭、突击有条不紊,军械武器展示效果不错。尺度超大,钢锯岭式血肉横飞,还给你看特写!敌人如丧尸一般打不完,双方的狙击手都是亮点
评论人:sylvia晓霄小小
评论时间:2018-02-1100:11:02
支持人数:7839
评论内容:超前点映场。场面真实,剧情紧凑。中间其实很想上厕所,但是愣是没有找到任何尿点…作为战争片,已超额完成任务,在真实度还原上,达到了国产影片从未有过的高度。细节处理也很妙,剥糖纸的那一段看的揪心。被海清和蒋璐霞的演技圈粉…看到最后,感觉自己整个人都在燃烧。准备春节的时候带着爸妈二刷。
评论人:华盛顿樱桃树
评论时间:2018-02-0600:06:34
支持人数:10171
评论内容:国产类型片的里程碑,2个多小时节奏全程紧绷清晰,真热血真刺激。叙事,人物,情感,动作,制作都几乎无可挑剔。该有的都有,演员群像都比想象中出色,但最出色的还是导演。这个格局,超越某狼N倍。
分析发现:对于单条影评数据主要包含:评论人、评论时间、支持人数和评论内容四部分,我们需要的是评论内容,这里需要对原始获取到数据进行解析,具体实现如下:
defsingeCommentParse(data='comments/1291546.txt',save_path='handle/1291546.json'):
'''''
单个影评数据的解析处理
数据样式:
评论人:phoebe
评论时间:2007-11-21 20:38:33
支持人数:22660
评论内容:陈凯歌可以靠它吃两辈子饭了,现在看来江郎才尽也情有可原
'''
withopen(data) asf:
data_list=[one.strip forone inf.readlines ifone]
comment_list=cutList(data_list,c=4)
res_list=[]
fori inrange(len(comment_list)):
one_list=comment_list[i]
try:
one_dict={}
one_dict['person'],one_dict['timestamp'],one_dict['number'],one_dict['content']='N','N','N','N'
forone inone_list:
ifone.startswith('评论人'):
one_dict['person']=one.split(':')[-1].strip
elifone.startswith('评论时间'):
one_dict['timestamp']=one.split(':')[-1].strip
elifone.startswith('支持人数'):
one_dict['number']=one.split(':')[-1].strip
elifone.startswith('评论内容'):
one_dict['content']=one.split(':')[-1].strip
res_list.append(one_dict)
except:
pass
withopen(save_path,'wb') asf:
f.write(json.dumps(res_list))
defallCommentsHandle(dataDir='comments/',saveDir='handle/'):
'''''
对整个目录下的影评数据全部处理
'''
ifnotos.path.exists(saveDir):
os.makedirs(saveDir)
txt_list=os.listdir(dataDir)
forone_txt intxt_list:
one_name=one_txt.split('.')[0].strip
one_txt_path=dataDir+one_txt
one_json_path=saveDir+one_name+'.json'
singeCommentParse(data=one_txt_path,save_path=one_json_path)
上述代码实现了对所有电源原始评论数据的解析处理,执行后就得到了所需的评论内容数据。
完成解析处理后需要对影评内容数据进行清洗,这里的清洗我主要是去除评论数据中的特殊字符等信息,具体实现如下:
defdataClean(one_line):
'''''
去脏、去无效数据
'''
withopen('stopwords.txt') asf:
stopwords_list=[one.strip forone inf.readlines ifone]
sigmod_list=[',','。','(',')','-','——','n','“','”','*','#','《','》','、','[',']','(',')','-',
'.','/','】','【','……','!','!',':',':','…','@','~@','~','「一」','「','」',
'?','"','?','~','_',' ',';','◆','①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩',
'⑾','⑿','⒀','⒁','⒂','"',' ','/','·','…','!!!','】','!',',',
'。','[',']','【','、','?','/^/^','/^','”',')','(','~','》','《','。。。',
'=','⑻','⑴','⑵','⑶','⑷','⑸','⑹','⑺','…','']
forone_sigmod insigmod_list:
one_line=one_line.replace(one_sigmod,'')
returnone_line
到这里文本数据的预处理工作就结束了,之后需要对数据进行存储,这里我是直接将数据存储到了MySQL数据库中。
首先需要创建对应的表,具体实现如下:
defcreateTableMySQL(tablename='mytable'):
'''''
创建表
'''
conn=pymysql.connect(**mysql)
cur=conn.cursor
try:
drop_sql="drop table if exists %s"% tablename
cur.execute
exceptException,e:
print'Drop Exception: ',e
try:
create_sql="""CREATE TABLE %s (
movieId VARCHAR(50) NOT NULL,
movieName VARCHAR(50) NOT NULL,
personName VARCHAR(50) NOT NULL,
supportNum VARCHAR(50),
content VARCHAR(255),
timePoint VARCHAR(50) NOT NULL,
"""%(table)
cur.execute(create_sql)
conn.commit
cur.close
conn.close
exceptException,e:
print'createTableMySQL Exception: ',e
数据入库操作实现如下:
def write2Table(data_list,tablename='mytable'):
'''''''
写入数据表
data_list:待写入的数据列表,列表中每个元素都是一条记录形成的列表
'''
try:
connect=pymysql.connect(**mysql)
cursor=connect.cursor
TS=datetime.datetime.now.strftime("%Y-%m-%d %H:%M:%S") #获取当前时间戳
forone in data_list:
insert_sql="INSERT INTO %s (movieId,movieName,personName,supportNum,content,timePoint)
VALUES ('%s','%s','%s','%s','%s','%s')"%
(tablename,one[0],one[1],one[2],one[3],one[4],one[5])
cursor.execute(insert_sql)
connect.commit
print'myTable InsertOperation Finished!!!'
cursor.close
connect.close
except Exception,e:
print"myTable InsertOperation Exception: ",e
到此就完成了影评数据的清洗与存储工作!
三、LDA主题挖掘分析
这一部分主要是基于LDA主题挖掘模型来对处理好的影评数据进行主题倾向性的分析挖掘工作。具体实现如下所示:
deftextTopicModel(n_topics=8,n_top=10,data_path='1291546.txt'):
'''
主题挖掘模型
'''
withopen(data_path) asf:
corpus=[one.strip.split('/') forone inf.readlines ifone]
tf_vectorizer=CountVectorizer(max_df=0.95,min_df=1,max_features=1000,stop_words='english')
corpus=[' '.join(one) forone incorpus]
tf=tf_vectorizer.fit_transform(corpus)
lda=LatentDirichletAllocation(n_topics=n_topics,learning_offset=50.,random_state=0)
docres=lda.fit_transform(tf)
print'================================docres================================'
printdocres
print'================================components_================================'
printlda.components_
结果输出如下:
{"蝶衣": 1, "记得": 1, "故事": 1, "哥哥": 6, "背景": 1, "作品": 2, "时代": 1, "真的": 1, "疯魔": 1, "女娇": 1, "霸王": 1, "说好": 1, "生活": 1, "角色": 1, "陈凯歌": 3, "电影": 6, "人生": 2, "风华绝代": 1, "成活": 1, "中国": 4, "喜欢": 1, "一年": 1, "人物": 1, "这部": 3, "一辈子": 2, "历史": 2, "时辰": 1, "经典": 3, "看过": 1, "感情": 1, "导演": 1, "程蝶衣": 6, "真虞姬": 1, "一部": 2, "霸王别姬": 2, "不算": 1, "关注": 1, "虞姬": 3, "一个月": 1, "永远": 1, "张国荣": 5, "一出": 1, "巅峰": 1, "之作": 1}
上述代码中我们数据的数据是《霸王别姬》的评论数据,输出的是对应的10个主题中主题词的词频数据。基于词云对其可视化结果如下所示:
接下来我们借助于主题可视化分析工具对其各个主题进行展示如下所示:
Topic0:
Topic1:
Topic2:
Topic3:
这里仅展示出前4个主题的分布情况,借助于可视化工具来呈现主题还是一种不错的方式。
四、分词与词频统计
这部分的工作主要是对原始的影评数据进行分词和词频统计,相对来说较为简单,就不多说明了,直接看代码实现即可:
defsingleCommentCut(data='1291546.json',word_path='1291546.txt',fre_path='1291546.json'):
'''
对单个影评数据清洗、分词处理
'''
withopen(data) asf:
data_list=json.load(f)
content=[]
fre_dict={}
forone_dict indata_list:
one_clean=dataClean(one_dict['content'])
one_cut=seg(one_clean)
one_line='/'.join(one_cut)
content.append(one_line)
forone inone_cut:
ifone infre_dict:
fre_dict[one]+=1
else:
fre_dict[one]=1
withopen(word_path,'w') asf:
forone_line incontent:
f.write(one_line.strip+'n')
withopen(fre_path,'w') asf:
f.write(json.dumps(fre_dict))
上述代码实现了对输入影评数据的分词与词频统计,并存储到本地文件中。
五、词云可视化分析
这里是本文的最后一个部分,主要是对前面几章中计算处理得到的文本数据进行可视化展示分析,对于文本数据的可视化我用的最多的也就是词云了,简单看下具体的效果吧。
原始影评数据词云可视化结果如下:
《我不是药神》
《流浪地球》
《肖申克的救赎》
《红海行动》
主题挖掘词云可视化分析结果如下:
《我不是药神》
《流浪地球》
《肖申克的救赎》
《红海行动》
对于完整影评数据的可视化来说更全面地展现出来了评论数据的信息,对于主题挖掘的可视化来说,突出了在广大评论数据中的主题倾向性。
到这里本文的工作就结束了,很高兴在自己温习回顾知识的同时能写下点分享的东西出来,如果说您觉得我的内容还可以或者是对您有所启发、帮助,还希望得到您的鼓励支持,谢谢!
全新打卡学习模式
每天30分钟
30天学会Python编程
世界正在奖励坚持学习的人!返回搜狐,查看更多
责任编辑: