MongoDB 实战(一)基于PyMongo的电影影评分析 | 对数据结果进行可视化展示以及分析 | 评论词云 | 分时间段分析

727 阅读3分钟

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部分影评分歧占比统计图如上图所示。

  1. 从整体来看,Top10影视的评论分歧都相对较低,处于[6.1%, 11.1%] 范围。

  2. 其中占比最多电影的为《盗梦空间》,分歧率为 11.1% ,这意味着有100人评论,那么就有将近11人的观点不被赞同,这跟电影的题材、类型、剧情、演员等多个因素都有关。

  3. 占比最少的为《千与千寻》,仅为 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的强大支持下,检索某个日期里的文档数据十分遍历,通过这样的方式,我们能感受到电影从去年到现在的影响力。