2020年受疫情影响影视行业受冲击较大,也没什么新的电影上映,要说20年我最期待的电影肯定是准备在20年上映的花木兰了,对花木兰的影评来一波分析先上图
接下来就是代码块了
主函数 movie_comment_analysis_main.py
from film.data_analysis import data_analysis
from film.gen_analy_result import gen_analy_result
from film.pre_process_data import pre_process_data
from film.prepare_datat import prepare_data
movie_id = 1210778
movie_name = "花木兰"
df = None
# 下载数据
def down_data():
prepare_data(movie_id, movie_name)
# 数据清洗
def pre_data():
global df
df = pre_process_data(movie_name)
# 数据分析
def movie_analysis():
# 各项数据分析
df_result = data_analysis(df)
# 可视化与生成分析结果
gen_analy_result(df_result, df, movie_name)
if __name__ == '__main__':
down_data()
pre_data()
movie_analysis()
采集模块 prepare_datat.py
import time
import requests
import json
import pandas as pd
import numpy as np
def prepare_data(movie_id, movie_name):
print("===================获取影评数据======================")
# 模拟浏览器,需要提供header头部信息
headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1'
}
comment_pd = pd.DataFrame()
startTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
for i in range(0, 10000, 15):
print("爬取第{0}页".format(int((i+15)/15)))
# i%1000是因为爬猫眼时偏移量最大只能1000
url = "http://m.maoyan.com/mmdb/comments/movie/"+str(movie_id)+".json?_v_=yes&offset="+str(i%1000)+"&startTime="+startTime
html = requests.get(url, headers=headers)
time.sleep(3)
res_data = html.content # 提取content内容
res_data = res_data.decode("utf-8") # 对内容解码
# json.loads():反序列化成标准的Json格式
json_data = json.loads(res_data)
# 提取Json数据
# 先判断是否有数据
if "cmts" in json_data:
# 提取cmts评论
cmts = json_data["cmts"]
# 选取数据
for cmt in cmts:
# 昵称,性别,评论星级,点赞数,回复数,城市,日期,评论内容
cmt_dict = {}
# 昵称
if "nick" in cmt.keys():
cmt_dict["nick"] = cmt["nick"]
else:
cmt_dict["nick"] = np.nan
# 性别
if "gender" in cmt.keys():
cmt_dict["gender"] = cmt["gender"]
else:
cmt_dict["gender"] = np.nan
# 评分
if "score" in cmt.keys():
cmt_dict["score"] = cmt["score"]
else:
cmt_dict["score"] = np.nan
# 点赞数
if "approve" in cmt.keys():
cmt_dict["approve"] = cmt["approve"]
else:
cmt_dict["approve"] = np.nan
# 评论的回复数量
if "replyCount" in cmt.keys():
cmt_dict["replyCount"] = cmt["replyCount"]
else:
cmt_dict["replyCount"] = np.nan
# 城市
if "cityName" in cmt.keys():
cmt_dict["cityName"] = cmt["cityName"]
else:
cmt_dict["cityName"] = np.nan
# 日期
if "time" in cmt.keys():
cmt_dict["time"] = cmt["time"]
else:
cmt_dict["time"] = np.nan
# 评论内容
if "content" in cmt.keys():
cmt_dict["content"] = cmt["content"]
else:
cmt_dict["content"] = np.nan
comment_pd = comment_pd.append(cmt_dict, ignore_index=True)
print(comment_pd)
else:
print("No data")
comment_pd.to_excel(str(movie_name)+".xlsx")
print("===================数据获取完毕======================")
清洗模块 pre_process_data.py
import pandas as pd
def pre_process_data(movie_name):
print("===================开始数据清洗======================")
df = pd.read_excel(movie_name+".xlsx",parse_dates=["time"]) # 读取源数据,将数据解析为时间格式
df["小时"] = df["time"].map(lambda x: int(x.strftime("%H"))) # 提取小时
df = df.drop_duplicates() # 去重
print("数据去重完毕")
df = df.dropna(subset=["cityName"]) # 删除城市空值行
df = df.dropna(subset=["gender"]) # 删除性别空值行
print("去除空值完毕")
df.to_excel(movie_name+".xlsx") # 写入处理后的数据
print("===================数据清洗完毕======================")
return df
数据处理模块 data_analysis.py
def data_analysis(df_comment):
print("===================开始数据分析======================")
df_comment_list = []
df_score = df_comment.groupby(["cityName"])['score'] # 按城市分组
df_gen_score = df_comment.groupby(["gender"])['score'] # 按性别分组
df_time_score = df_comment.groupby(["time"])['time'] # 按时间分组
time_count = df_comment.groupby("小时")["nick"].agg(['count']) # 提取时间段
comment_gender_sum = df_gen_score.agg(['sum']) # 评分中男女总数
df_comment_list.append(comment_gender_sum)
comment_time_count = df_time_score.agg(['count']) # 评分中日期计数
comment_score_mean_count = df_score.agg(['mean', 'count']) # 评分中各个城市的平均分、数量
# 重新设置索引 inplace改变原来的
comment_score_mean_count.reset_index(inplace=True)
comment_score_mean_count['mean'] = round(comment_score_mean_count['mean'],2)
df_comment_list.append(comment_score_mean_count)
df_comment_list.append (comment_time_count)
df_score = df_comment.groupby(["score"])
df_score_nick = df_score["nick"]
score_count = df_score_nick.agg(['count'])
df_comment_list.append(score_count)
df_comment_list.append(time_count)
print("===================数据分析完毕======================")
return df_comment_list
分析模块 gen_analy_result.py
import collections
import os
import numpy as np
import imageio
import jieba
from wordcloud import WordCloud, ImageColorGenerator
import matplotlib.pyplot as plt
# 解决中文乱码问题
plt.rcParams["font.sans-serif"] = "SimHei"
fig = plt.figure(figsize=(8, 6))
url_city = None
url_gen = None
url_score = None
url_scatter = None
url_stack = None
def gen_analy_result(df_result, df_source, movie_name):
print("===================开始生成结果======================")
# 生成词云
gen_wordcloud(df_source, movie_name)
# 生成折线图、柱状图、网页的函数
draw_plot_bar(df_result, movie_name)
draw_pie(df_result, movie_name)
drwa_bar(df_result, movie_name)
draw_scatter(df_result, movie_name)
draw_stackplot(df_result, movie_name)
print("===================结果生成完毕======================")
# 气泡图
def draw_scatter(df_result, movie_name):
global url_scatter
city_result = df_result[1]
city_main = city_result.sort_values("count", ascending=False)[0:10]
# 建立坐标系
fig = plt.figure()
ax1 = fig.add_subplot(111)
# 指明x和y的值
x = np.array(city_main['count'].tolist()) # 城市评论数做x轴
y = np.array(city_main['mean'].tolist()) # 城市平均分做y轴
# 绘图
colors = y * 10 # 根据y值的大小生成不同的颜色
area = y * 100 # 据y值的大小生成大小不同的形状
ax1.scatter(x, y, marker="o", s=area, c=colors)
# 设置标题
ax1.set_title("城市评论数量与评分关系图", loc="center")
# 添加数据标签 ha水平方向 va垂直方向
for a, b in zip(x, y):
plt.text(a, b, b, ha="center", va="center", fontsize=12, color="white")
# 设置x轴和y轴
ax1.set_xlabel('评论数量')
ax1.set_ylabel('平均分')
# 设置网格线
plt.grid(False)
url_scatter = movie_name + "scatter.jpg"
fig.savefig(url_scatter)
# 词云函数
def gen_wordcloud(df_source, movie_name):
# 把所有评论串在一起,用空格分隔
full_comment = " ".join(df_source["content"])
# 分词
word_list = []
# cut_for_search搜索引擎模式:在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词
words_gen = jieba.cut_for_search(full_comment) # 返回的是生成器
for w in words_gen:
word_list.append(w)
word_list = [k for k in word_list if len(k) > 1] # 过滤只有一个词的评论
print(word_list)
# imageio导入照片功能比较好用,可以导入很多格式类型的图片
bg_color = imageio.imread("地球图片.jpg")
# WordCloud可以将文本中词语出现的频率作为一个参数绘制词云,而词云的大小、颜色、形状等都是可以设定的
# 设置对象
wc = WordCloud(background_color='white', # 背景颜色
max_words=200, # 最大词数
mask=bg_color, # 设置词云形状 以该参数值作图绘制词云,这个参数不为空时,width和height会被忽略
max_font_size=300, # 显示字体的最大值
font_path="simfang", # 指定字体路径,系统字体路径:C:\Windows\Fonts
random_state=42, # 为每个词返回一个PIL颜色
)
# 统计列表元素出现次数,返回的数据中键对应词,值对应出现次数,例如:Counter({'blue': 3, 'red': 2, 'green': 1})
counter = collections.Counter(word_list)
# wc对象的生成器的词频
wc.generate_from_frequencies(counter) # frequencies频率
print("================词云已生成================")
# 从图片中取色
image_colors = ImageColorGenerator(bg_color) # 从图片中取色
plt.figure() # 创建画布
plt.imshow(wc.recolor(color_func=image_colors)) # 绘制图像
plt.axis("off") # 关闭坐标轴
wc.to_file(os.path.join(movie_name + "词云.jpg")) # 生成词云图片
print("================词云图片已生成================")
# 评分统计
def drwa_bar(df_result, movie_name):
global url_score
# 建立坐标系
fig = plt.figure()
ax1 = fig.add_subplot(111)
# 指明x和y的值
score_result = df_result[3]
x = np.array(score_result.index)
y = np.array(score_result['count'].tolist()) # 评论平均分作为折线图的Y轴
# 绘制柱状图
ax1.bar(x, y, color="r", label="评分数量")
# 设置标题
ax1.set_title(movie_name + "评分统计", loc="center")
# 添加数据标签
for a, b in zip(x, y): # zip()需是np.array()
ax1.text(a, b, b, ha="center", va="bottom", fontsize=11)
# 设置x轴和y轴的名称
ax1.set_xlabel('星级')
ax1.set_ylabel('数量')
# 设置x轴和y轴的刻度
ax1.set_xticks(np.arange(0, 6, 1))
ax1.set_yticks(np.arange(100, 600, 100))
# 显示图例
ax1.legend()
# 保存图表到本地
url_score = movie_name + "bar.jpg"
fig.savefig(url_score)
# 城市分析
def draw_plot_bar(df_result, movie_name):
print("=====================开始绘制城市分析图====================")
global url_city
# 建立坐标系
fig = plt.figure()
ax1 = fig.add_subplot(111)
# 指明x和y的值
city_result = df_result[1]
city_main = city_result.sort_values("count", ascending=False)[0:10] # 取前10
x = city_main['cityName'].tolist()
y1 = city_main['mean'].tolist() # 评论平均分作为折线图的Y轴
y2 = city_main['count'].tolist() # 评论数量作为柱状图的Y轴
# 直接绘制折线图和柱形图
ax1.plot(x, y1, color="r", linestyle="solid", linewidth=1, marker="o", markersize=3, label="平均评分")
ax1.bar(x, y2, color="b", label="评论数量")
# 设置标题
ax1.set_title(movie_name+"-TOP10城市评论数量与平均评分", loc="center")
# 添加数据标签
for a, b in zip(x, y1):
ax1.text(a, b, b, ha="center", va="bottom", fontsize=11)
for a, b in zip(x, y2):
ax1.text(a, b, b, ha="center", va="bottom", fontsize=11)
# 设置x轴和y轴的名称
ax1.set_xlabel('城市')
ax1.set_ylabel('评论数量')
# 设置x轴和y轴的刻度
ax1.set_xticks(np.arange(0,10,1))
ax1.set_yticks(np.arange(10, 60, 10))
# 显示图例
ax1.legend()
# 保存图表到本地
url_city = movie_name + "bar_plot.jpg"
fig.savefig(url_city)
print("=====================绘制城市分析图完毕====================")
# 性别分析
def draw_pie(df_result, movie_name):
global url_gen
genter_result = df_result[0]
# 建立坐标系
fig = plt.figure()
ax1 = fig.add_subplot(111)
# 指明x值
x = np.array(genter_result['sum'])
lab = genter_result.index
if lab[0] == 1:
gen1 = "男"
else:
gen1 = "女"
if lab[1] == 2:
gen2 = "女"
else:
gen2 = "男"
labels = [gen1, gen2]
explode = [0, 0]
labeldistance = 1.1 # 标签距离
# autopct百分比格式 shadow是否有阴影 radius半径
ax1.pie(x, labels=labels, autopct="%.0f%%", explode=explode, radius=1.0, labeldistance=labeldistance)
# 设置标题
ax1.set_title(movie_name+"评分男女占比", loc="center")
# 保存图表到本地
url_gen = movie_name + "pie.jpg"
fig.savefig(url_gen)
# 面积图
def draw_stackplot(df_result, movie_name):
global url_stack
time_result = df_result[4]
print(time_result)
print(type(time_result))
# 建立坐标系
fig = plt.figure()
ax1 = fig.add_subplot(111)
# 指明x和y的值
print("00")
print(time_result.index)
x = np.array(time_result.index)
y = np.array(time_result['count'].tolist())
# 绘图
print("11")
ax1.stackplot(x, y, labels="评论数量")
# 设置标题
ax1.set_title("评论数量与时间的关系图", loc="center")
# 设置x轴和y轴名称
ax1.set_xlabel('时间/小时')
ax1.set_ylabel('评论数量')
# 设置网格线
plt.grid(False)
# 显示图例
ax1.legend()
url_stack = movie_name + "stack.jpg"
fig.savefig(url_stack)