Gitee 仓库地址:gitee.com/ccuni/pymon…
一、运行环境
- Windows10
- python 3.9
- Anaconda3 + jupyter
- mongodb 5.0.6
- PyMongo 3.5.1
- wordcloud-1.8.1
anaconda 安装 wordcloud的命令
conda install -c conda-forge wordcloud
二、实战介绍
数据来源基于Python的第三方库,即requests库,bs4库,re库爬取豆瓣网TOP10的电影信息,以及它们的部分影评信息(100个左右)。
将爬取的信息进行预处理,封装成dict字典,借助 pymongo库 连接本机的 MongoDB,向数据库插入之前爬取的真实数据,然后分别使用MongoDB提供的map_reduce机制以及agreegate机制来聚合、分组、汇总计算数据,以MongoDB为基础,存储影视信息和评论信息,同时分析电影的综合价值。
三、获取数据
这一部分可参考Gitee仓库:gitee.com/ccuni/pymon…
四、PyMongo 实战
4.1 连接MongoDB、创建集合
from pymongo import MongoClient
from random import randint
client = MongoClient('localhost', 27017)
db = client.mv
# 创建电影信息集合
ct_mv_info = db.dc_mv_info
# 创建影评集合
ct_mv_review = db.dc_mv_review
# 查看创建结果
ct_mv_review
4.2 向 MongoDB 插入文档
这里先将DataFrame的影视信息转化为dict字典格式
dc_mv = []
index = 0
for x in mv_data.values.tolist():
dict_info = {}
# 指定文档的_id为电影ID
dict_info['_id'] = mv_data['mv_id'][index]
index += 1
# i 用于循环遍历取DF列表数据
i = 0
for key, v in mv_data.items():
dict_info[key] = x[i]
i += 1
# 指定文档的
dc_mv.append(dict_info)
dc_mv
# 插入前 先清空
ct_mv_info.delete_many({})
# 插入文档
ct_mv_info.insert_many(dc_mv)
4.3 查询MongoDB的数据
ct_mv_info.find_one()
4.4 同样的操作插入影评
先处理信息,将原先的DataFrame的影评信息转化为可插入到MongoDB的dict字典
# list_mv[1] 输出结果 dict_keys(['reviews', 'star'])
# 查询保存的列表数据
# for x in list_mv[1].values.to_list():
# print(x)
'''
根据之前的存储信息获取所有电影的影评, 封装成可插入MongoDB的 dict
'''
def getAllReviews() -> list[list]:
index = 0
reviews = []
for i in range(len(list_mv)):
# 获取每一列
rv_cols = list_mv[0]['reviews'].columns
# 表示当前的评论标号
i = 0
# 记录当前电影的所有影评信息
dc_reviews = []
for k, rows in list_mv[index]['reviews'].iterrows():
# 根据电影ID和当前的评论序号定义_id
dict_info = {'_id' : mv_data['mv_id'][index] + str(i)}
i += 1
for col in rv_cols:
dict_info[col] = rows[col]
dc_reviews.append(dict_info)
index += 1
reviews.append(dc_reviews)
return reviews
# 获取Top10电影的爬取到的所有影评
dc_reviews = getAllReviews()
count = 0
for i in range(len(dc_reviews)):
count += len(dc_reviews[i])
print(f'[INFO] >> 共获取到 {len(dc_reviews)} 个电影 {count} 个的影评')
print(f'[INFO] >> 查看其中的一个影评: {dc_reviews[0][:1]}')
处理结果:
4.5 插入影评信息到MongoDB
ct_mv_review.delete_many({})
for rv in dc_reviews:
ct_mv_review.insert_many(rv)
# 查看插入结果
ct_mv_review.find_one()
五、基于 PyMongo的数据分析
5.1 计算豆瓣 Top 10 影视的平均评分
ct_mv_info.find_one()
from bson.code import Code
mapper = Code("""function(){
emit('', {count:1, mv_star: eval(this.mv_star)});
}
""")
reducer = Code("""function(k, v) {
reducedVal = {count: 0, mv_star: 0};
for (var idx = 0; idx < v.length; idx++) {
reducedVal.count += v[idx].count;
reducedVal.mv_star += v[idx].mv_star;
}
return reducedVal;
};
""")
finalizer = Code("""
reducedVal.mv_star_avg = reducedVal.mv_star/reducedVal.count;
return reducedVal;
""")
res = ct_mv_info.map_reduce(map = mapper,reduce=reducer,out='mv_star_avg', finalize = finalizer)
res
ct_mv_star_avg = db.mv_star_avg
for x in ct_mv_star_avg.find():
print(x)
根据结果得出,10部电影里的平均评分为9.5分,还是处于相当高的水平,当然这个案例并没有实际意义,主要是为了熟悉MongoDB的MapReduce的基本使用
5.2 统计Top10电影影评的[赞同 / 不赞同]的平均比率
ct_mv_review.find_one()
from bson.code import Code
mapper = Code("""function(){
var a = eval(this.rv_action_agree);
var b = eval(this.rv_action_disagree);
var rate = 0;
if(a > 0 && b > 0){
rate = b / (a + b);
emit(this.rv_mv_id, {count: 1, rate: rate});
}
}
""")
reducer = Code("""function(k, v) {
reducedVal = {count: 0, rate: 0};
for (var i = 0; i < v.length; i++) {
reducedVal.count += v[i].count;
reducedVal.rate += v[i].rate;
}
return reducedVal;
};
""")
finalizer = Code("""
reducedVal.rate = reducedVal.rate/reducedVal.count;
return reducedVal;
""")
res = ct_mv_review.map_reduce(map = mapper,reduce=reducer,out='mv_agree_divide_disagree_rate', finalize = finalizer)
res
ct_mv_agree_divide_disagree_rate = db.mv_agree_divide_disagree_rate
# 查询mongodb文档数据并持久化
temp1 = []
for x in ct_mv_agree_divide_disagree_rate.find():
print(x)
temp1.append(x)
len(temp1)
# 准备电影ID到电影名的映射字典
dict_rv_name = {}
for mv in ct_mv_info.find():
dict_rv_name[mv['mv_id']] = mv['mv_name']
# 条件查询, 根据电影 ID 获取到对应的电影名
for i in range(len(temp1)):
id = temp1[i]['_id']
name = ''
for x in ct_mv_info.find({'_id': id}):
name = x['mv_name']
temp1[i]['mv_name'] = name
temp1
可视化
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.sans-serif'] = ['SimHei']
matplotlib.rcParams['font.family']='sans-serif'
# 柱形的宽度
bar_width = 0.6
plt.xticks(rotation=35)
x1 = [x['mv_name'] for x in temp1]
y1 = [x['value']['rate'] for x in temp1]
# 绘制柱形图
plt.bar(x=x1,
height=y1,
width=bar_width,
color=['skyblue', 'pink'],
linewidth=1.5,
)
# 26 # 为每个条形图添加数值标签
for x,y in enumerate(y1):
plt.text(x,y+0.003,'%.3f' % y,ha='center')
plt.xlabel('电影名称',fontsize=14, color='blue')
plt.ylabel('评论分歧(反对)平均占比',fontsize=14, color='red')
plt.title('豆瓣Top10影视评论分歧占比统计图',fontsize=15, color='green')
plt.show()
分析过程:
通过PyMongoDB的MapReduce过程,最终得出的豆瓣Top10部分影评分歧占比统计图如上图所示。
-
从整体来看,Top10影视的评论分歧都相对较低,处于
[6.1%, 11.1%]范围。 -
其中占比最多电影的为《盗梦空间》,分歧率为
11.1%,这意味着有100人评论,那么就有将近11人的观点不被赞同,这跟电影的题材、类型、剧情、演员等多个因素都有关。 -
占比最少的为《千与千寻》,仅为
6.1%,这说明观众们的观点大多是一致的,100人里面只有6人左右的观点不一致。
综上,对于Top10的电影,除了评分、观看数等指标,评论分歧率直观体现了影视的影响力,这意味着观众可以选择这个分歧率较小的电影作为参考,达到更好的观看体验,同时对于同行,能更放心地借鉴其中的一些高深的拍摄手法、剧情演绎方法等。
5.3 统计Top10电影从2021年到今日的评论情况
ct_mv_review.find_one()
for x in ct_mv_review.aggregate([{'$group': {'_id':'$rv_mv_id', 'counter':{'$sum':1}}}]):
print(x)
from datetime import datetime
list_rv= []
year, month, day = 2021,1,1
for x in ct_mv_review.aggregate([
{
# 转化类型
'$project':
{
'rv_time': '$rv_time',
'rv_info': '$rv_info',
'rv_name': '$rv_name',
'rv_time_stand':
{
'$convert':
{
'input':'$rv_time',
'to': 'date',
'onNull': 'missing rv_time'
}
},
},
},
{
'$match':
{
'rv_time_stand':
{
'$gte': datetime(year,month,day)
},
}
},
]):
list_rv.append(x)
dict_rv_info = {}
'''
处理 MongoDB 聚合后的结果 汇总评论
'''
for rv in list_rv:
# 前 7 位是电影的ID
mv_id = rv['_id'][:7]
dict_rv_info[dict_rv_name[mv_id]] = {}
dict_rv_info[dict_rv_name[mv_id]][rv['rv_name']] = {
'rv_time': rv['rv_time'],
'rv_info': rv['rv_info']
}
print(f'[INFO] >> 已统计完 [{len(dict_rv_info)}] 个电影在{year}年{month}月{day}后的影评')
'''
词频统计
'''
import jieba
from wordcloud import WordCloud
# 不需要统计的词汇
nope = ['电影', '没有', '一个', '之后', '这部']
for k, v in dict_rv_info.items():
dict_word_count = {}
# 遍历每个用户的评论
for review in v.values():
# 遍历每个词
for x in jieba.cut(review['rv_info']):
if(len(x) >= 2) and x not in nope:
dict_word_count.setdefault(x, 0)
dict_word_count[x] = dict_word_count[x] + 1
#生成词云 保存到本地
t = WordCloud(
width=600, height=480, # 图片大小
background_color='white', # 背景颜色
scale=10,
font_path=r'c:\windows\fonts\simfang.ttf' ).generate_from_frequencies(dict_word_count)
save_path = './count_images/' + k + '.jpg'
t.to_file(save_path)
print(f'[INFO] >> 电影[{k}] 评论的词频统计词云生成完毕, 保存位置在[{save_path}]')
# print(dict_word_count)
评论词云的结果如下图所示:
美丽人生:
辛德勒的名单
这里展示了两部电影的评论词云,而且是在21年1月份以后的评论,在MongoDB的强大支持下,检索某个日期里的文档数据十分遍历,通过这样的方式,我们能感受到电影从去年到现在的影响力。