数据分析通常是以图表的形式展示,为人们带来更加直观的感受。本文将详细介绍基于Python语言开发的一个影评分析项目,对猫眼电影的影视作品进行影评分析。
一、需求分析
首先需要做的就是需求分析,通常影评分析应该包含以下几个功能:
(1)可以选择电影;
(2)可以通过数据分析电影;
(3)查看显示城市评论数和平均分;
(4)查看显示热力图;
(5)查看显示词云图。
二、系统设计
1. 系统功能结构
猫眼电影影视作品分析主要分为3个部分:选择电影点击分析、数据抓取及保存、查看分析结果。其中,数据抓取及保存又包括爬取数据保存到本地文件,再根据文件内容生成图表;查看分析结果包括主要城市评论分数及平均分(柱状图 + 折线图)、热力图、词云图。猫眼电影影视作品分析的详细功能结构图如下图所示:
注意:数据抓取及保存为本系统的核心功能。
2. 系统业务流程
在开发项目前,需要了解软件的业务流程。根据项目的需求分析及功能结构,设计出系统的业务流程图如下图所示:
三、系统开发环境
本系统的软件开发及运行环境具体如下:
(1)操作系统:Windows Enterprise 10;
(2)Python版本:Python 3.6;
(3)开发工具:PyCharm Professional 2018.3;
(4)Python内置模块:os、sys、json、requests、collections、random;
(5)第三方模块:pandas、scipy、jieba、wordcloud、matplotlib、imageio、PyQt5、pyqt5-tools、pyecharts、echarts-china-counties-pypkg、echarts-china-provinces-pypkg、echarts-china-cities-pypkg。
版本要求:PyQt5 <= 5.10.1, pyecharts <= 0.5.11
四、主窗口设计
1. 主窗口的实现
在实现主窗口之前,需要理清初始化主窗口的业务流程和实现技术。本文使用的是PyQt5模块来实现主窗口的功能,设计了如下所示的主窗口业务流程图:
主窗口初始化的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 主方法入口
if __name__ == '__main__':
# 实例化QApplication类
app = QtWidgets.QApplication(sys.argv)
main_win = QtWidgets.QMainWindow()
# 显示热力图,主要城市评论数及平均分窗口
hot_win = HotMapWindows()
# 显示词云图窗口
word_win = WordCloudWindows()
# 初始化主窗口
ui = UI_Form()
ui.init_ui(main_win, hot_win, word_win)
# 显示主窗口
main_win.show()
sys.exit(app.exec_())
2. 查看内容的隐藏和显示
查看内容的隐藏和显示比较简单,代码如下所示:1
2setVisible(True) # 显示查看内容
setVisible(False) # 隐藏查看内容
在下拉列表中选择一些新的电影名称后,先判断是否存在该电影的分析数据,然后通过创建 hide() 方法和 show() 方法来隐藏或看是查询内容的文本标签和按钮,效果展示如下图所示:
关于 hide() 方法和 show() 方法的实现代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21def hide(self):
"""
隐藏查看内容
"""
self.wordCloud.setVisible(False)
self.viewWordCloud.setVisible(False)
self.hotMap.setVisible(False)
self.viewHotMap.setVisible(False)
self.commentNum.setVisible(False)
self.viewCommentNum.setVisible(False)
def show(self):
"""
显示查看内容
"""
self.wordCloud.setVisible(True)
self.viewWordCloud.setVisible(True)
self.hotMap.setVisible(True)
self.viewHotMap.setVisible(True)
self.commentNum.setVisible(True)
self.viewCommentNum.setVisible(True)
3. 下拉列表处理
在下拉列表里选择要分析的电影后,需要先判断是否分析过该电影,分析过的电影则直接显示查看内容,否则隐藏查看内容。下拉列表的业务流程图如下所示:
下拉列表中进行电影选择后,首先要判断该电影是否被分析过,如若被分析过则显示查看内容,否则隐藏查看内容,因此需要定义电影选择的处理方法,实现的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 电影选择处理方法
def item_change(self, text):
for movie in movie_list:
# 定位下拉菜单选择的电影
if text.__eq__(movie.get('name')):
# 判断是否被分析过
if not os.path.exists(self.default_dir + text):
self.pushButton.setText('分析')
self.hide()
else:
self.pushButton.setText('重新分析')
self.movie_name = movie.get('name')
self.movie_id = movie.get('id')
self.show()
self.button_click()
五、数据分析与处理
1. 获取猫眼电影影评数据
首先通过猫眼电影的接口获取影评数据,接口URL是http://m.maoyan.com/mmdb/comments/movie/movie_id.json
,其中 movie_id 就是猫眼电影为每部电影生成的唯一ID,怎么获得其实很简单(浏览器中打开猫眼电影首页-电影,选择任意一部电影的名称,右键审查元素就能够看到它的 movie_id)。下面的代码的作用就是调用该URL接口,并将返回数据解析为dict格式,需要注意的是一定要设置headers,否则爬虫程序无法获取到数据。1
2
3
4
5
6
7
8
9
10
11
12def get_json_data(url):
"""
get response data of url
:param url: str
:return: dict data
"""
try:
headers = {'User-Agent': random.choice(agent)}
json_data = requests.get(url, headers=headers).json()
return json_data
except ValueError:
return None
调用 get_json_data() 方法得到影评的数据之后,需要进行预处理:第一是要筛选出我们需要保存的信息,去掉重复后保存到 csv 文件之中;第二是基于pandas的DataFrame按照城市名称进行分组,统计每个城市的评论数和平均分,用于后面生成全国热力图和主要城市评论数及平均分分析的HTML页面。完整的实现代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38def crawl_data(self):
"""
爬取影评数据, 并进行预处理
"""
# 创建影评分析结果存放目录
save_dir = self.default_dir + self.movie_name + '/'
if not os.path.exists(save_dir):
os.mkdir(save_dir)
# 需要存储的信息列
col_list = ['time', 'score', 'cityName', 'content', 'nickName']
frame = pd.DataFrame(columns=col_list)
# 通过URL获取影评数据
url = 'http://m.maoyan.com/mmdb/comments/movie/' + self.movie_id + '.json'
json_data = get_json_data(url) # keys: 'cmts', 'hcmts', 'total'
comments = json_data.get('cmts')
high_comments = json_data.get('hcmts')
# 筛选需要的信息
list1 = list(map(lambda x: dict(filter(lambda it: it[0] in col_list, x.items())), comments))
list2 = list(map(lambda x: dict(filter(lambda it: it[0] in col_list, x.items())), high_comments))
list1.extend(list2) # 合并信息
frame = frame.append(list1, ignore_index=True)
# 去重复数据,并重新建立索引
frame = frame.drop_duplicates().reset_index()
# 影评保存 csv 文件
frame.to_csv(save_dir + self.movie_name + '.csv', index=True, header=True, encoding='utf_8_sig')
frame_group = frame.groupby('cityName')
# 通过聚合函数求平均分,以及评论总数
frame_agg = frame_group['score'].agg(['mean', 'count'])
# 重新建立索引,因为 group by 之后结构已经变了
frame_agg.reset_index(inplace=True)
# 平均分保留2位小数
frame_agg['mean'] = frame_agg['mean'].apply(lambda x: round(x, 2))
# 生成全国热力图和主要城市评论数及平均分
self.gen_hot_map(save_dir, frame_agg)
# 生成词云图
self.gen_word_cloud(save_dir, ' '.join(frame['content']))
2. 生成全国热力图和主要城市评论数及平均分文件
首先,生成全国热力图主要是根据各个城市的评论数得到各个省份的评论总数,然后借助 pyecharts 中的 Geo 制作地理热度图,并保存为 HTML 文件,用于全国热力图窗口进行展示。同样,这里选取了评论数最多的30个城市,并根据每条评论的分数来计算每个城市的平均分,主要城市评论数以折线图的方式展示,平均分以柱状图的形式展示,将这两个图叠加在同一个图中,最后保存为HTML文件。下面则是完整的实现代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42def gen_hot_map(self, save_dir, frame):
"""
生成全国热力图和主要城市评论数及平均分
:param save_dir: 结果的保存目录
:param frame: 用来操作的数据, DataFrame
"""
geo_map = Geo('《' + self.movie_name + '》 全国热力图',
title_color="#fff",
title_pos="center", width=1200,
height=600, background_color='#404a59')
while True:
try:
geo_map.add("", attr=frame['cityName'], value=frame['count'], type="heatmap",
visual_range=[0, 50], visual_text_color="#fff",
symbol_size=15, is_visualmap=True, is_roam=False)
break
except ValueError as e:
# 去除不支持的城市名
city_name = str(e).split("No coordinate is specified for ")[1]
frame = frame[frame['cityName'] != city_name]
# 生成全国热力图html文件
geo_map.render(save_dir + self.movie_name + '全国热力图.html')
# 取前30个主要城市
main_city = frame.sort_values('count', ascending=False)[:30]
# 以折线图画主要城市评分
line = Line("主要城市评分")
line.add("城市", x_axis=main_city['cityName'], y_axis=main_city['mean'], is_stack=True,
xaxis_rotate=30, yaxis_min=0, mark_point=['min', 'max'], xaxis_interval=0,
line_color='lightblue', line_width=4, mark_point_textcolor='black',
mark_point_color='lightblue', is_splitline_show=False)
# 以柱状图画主要城市评分
bar = Bar("主要城市评论数")
bar.add("城市", x_axis=main_city['cityName'], y_axis=main_city['count'], is_stack=True,
xaxis_rotate=30, yaxis_min=0, xaxis_interval=0, is_splitline_show=False)
# 叠加画在同一个图上
overlap = Overlap()
overlap.add(bar)
overlap.add(line, yaxis_index=1, is_add_yaxis=True)
# 生成主要城市评论数及平均分html文件
overlap.render(save_dir + self.movie_name + '主要城市评论数及平均分.html')
下方是对主要城市评论数及平均分的窗口展示,红色的柱状图表示的是每个城市的评论数(对应的是左边的刻度),折线图则表示每个城市的平均分(对应的是右边的刻度):
3. 生成词云图
生成词云图使用的是 jieba 和 wordcloud 工具,首先将所有的评论内容拼接为一个字符串,接着使用 jieba 进行分词得到主要的关键词,然后根据选定的背景图使用 wordcloud 生成词云图,并保存为 PNG 文件,下面则是完整的实现代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29def gen_word_cloud(self, save_dir, contents):
"""
先使用jieba分词, 再使用wordcloud生成词云图
:param save_dir: 词云图保存目录
:param contents: 要分析的文本
"""
words_generator = jieba.cut_for_search(contents)
words_list = list(filter(lambda x: len(x) > 1, words_generator))
# 解析词云背景图片
back_color = imageio.imread('../resource/background.jpg')
word_cloud = WordCloud(
background_color='white', # 背景颜色
max_words=200, # 最大词数
mask=back_color, # 以该参数值作图绘制词云,这个参数不为空时,width和height会被忽略
max_font_size=300, # 显示字体的最大值
font_path="../resource/STFANGSO.ttf", # 字体
random_state=42, # 为每个词返回一个PIL颜色
)
words_count = collections.Counter(words_list)
word_cloud.generate_from_frequencies(words_count)
# 基于彩色图像生成相应彩色
image_colors = ImageColorGenerator(back_color)
# 绘制词云
plt.figure()
plt.imshow(word_cloud.recolor(color_func=image_colors))
# 去掉坐标轴
plt.axis('off')
# 保存词云图片
word_cloud.to_file(save_dir + self.movie_name + '词云.png')
下方则是词云图的效果展示图,从中可以看出用户的重要关注点、以及热门的话题词(理论上应该去除停用词,这样结果会更准确):
六、点击查看显示内容
1. 创建显示HTML页面窗口
显示HTML页面,需要创建新的窗口,然后根据选定的电影,显示对应的全国热力图和主要城市评论数及平均分,下面则是完整的实现代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class HotMapWindows(QMainWindow):
"""
热力图和主要城市评论数及平均分 主窗口类
"""
def __init__(self):
super(QMainWindow, self).__init__()
self.setGeometry(200, 200, 1250, 650)
self.browser = QWebEngineView()
# 所有分析结果存放目录
self.default_dir = '../result/'
def show_hot_map(self, movie_name, index):
title = ['主要城市评论数及平均分', '全国热力图']
self.setWindowTitle(movie_name + title[index])
html = self.default_dir + movie_name + '/' + movie_name + title[index] + '.html'
# windows下注意要替换文件路径中的 '\' 为 '/'
html = 'file:///' + os.path.abspath(html).replace('\\', '/')
self.browser.load(QUrl(html))
self.setCentralWidget(self.browser)
2. 创建显示图片窗口
同样,显示图片也需要创建新的窗口,然后根据选定的电影,显示对应的词云图,下面是完整的实现代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class WordCloudWindows(QMainWindow):
"""
词云图 主窗口类
"""
def __init__(self):
super(QMainWindow, self).__init__()
self.setGeometry(200, 200, 650, 650)
self.browser = QLabel()
self.default_dir = '../result/'
def show_word_cloud(self, movie_name):
title = '词云'
self.setWindowTitle(movie_name + title)
png = self.default_dir + movie_name + '/' + movie_name + title + '.png'
# 理由pixmap解析图片
pixmap = QPixmap(png)
# 等比例缩放图片
scaredPixmap = pixmap.scaled(QSize(600, 600), aspectRatioMode=Qt.KeepAspectRatio)
# 设置图片
self.browser.setPixmap(scaredPixmap)
# 判断选择的类型 根据类型做相应的图片处理
self.browser.show()
self.setCentralWidget(self.browser)
3. 点击查看按钮触发事件
点击查看按钮,会触发不同的事件,下面的代码实现的就是对不同的查看按钮进行操作,会跳出相应的新窗口,并显示对应的查看内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28def button_click(self):
"""
操作不同的查看按钮
"""
self.viewCommentNum.clicked.connect(self.view_main_city)
self.viewHotMap.clicked.connect(self.view_hot_map)
self.viewWordCloud.clicked.connect(self.view_word_cloud)
def view_main_city(self):
"""
主要城市评论数及平均分查看按钮触发事件
"""
self.hot_win.show_hot_map(self.movie_name, index=0)
self.hot_win.show()
def view_hot_map(self):
"""
全国热力图查看按钮触发事件
"""
self.hot_win.show_hot_map(self.movie_name, index=1)
self.hot_win.show()
def view_word_cloud(self):
"""
词云查看按钮触发事件
"""
self.word_win.show_word_cloud(self.movie_name)
self.word_win.show()
七、总结
整个影评分析项目已经介绍完了,重点在于如何获取影评数据、数据预处理分析、如何生成想要的可视化图表。其次,用户的交互窗口是通过QT来实现,因此要求对QT要有一定的基础。最后,完整的github代码可点击此处前往下载。(声明:所有代码仅供学习参考使用)

...
...