实战 | 用Python爬取《云南虫谷》3.6万条评论,并做数据统计可视化展示分析,好看!

2021-11-06

大家好,我是才哥。

最近鬼吹灯系列网剧《云南虫谷》上线,作为鬼吹灯系列作品,承接上部《龙岭迷窟》内容,且还是铁三角原班人马主演,网友直呼非常好看

今天,我们就用Python爬取目前全部剧集的评论(含预告片),并做数据统计与可视化展示分析,一起跟着网友看看这部剧吧!

本文将详细讲解爬虫与数据处理可视化,寓教于乐!

后台回复 210905 领取 代码+数据

目录:

  • 1. 网页分析

  • 2. 爬虫过程

    • 2.1. 引入需要的库

    • 2.2. 爬取剧集页面数据

    • 2.3. 解析剧集ID和剧集评论ID

    • 2.4. 采集全部剧集评论

    • 2.5. 保存数据到本地

  • 3. 数据统计与可视化展示

    • 3.1. 数据预览

    • 3.2. 分集评论数

    • 3.3. 分日期评论数

    • 3.4. 分时评论数

    • 3.5. 评论员VIP等级分布

    • 3.6. 评论长度

    • 3.7. 评论点赞数

    • 3.8. 评论最多的用户

    • 3.9. 评论词云


1. 网页分析

本次评论全部来自腾讯视频(毕竟独播)

打开《云南虫谷》播放页面,F12进入到开发者模式,我们下滑点击"查看更多评论",可以发现评论的真实请求地址。

开发者模式

我们找几个评论接口地址进行对比分析,找规律

https://video.coral.qq.com/varticle/7313247714/comment/v2?callback=_varticle7313247714commentv2&orinum=10&oriorder=o&pageflag=1&cursor=6838089132036599025&scorecursor=0&orirepnum=2&reporder=o&reppageflag=1&source=132&_=1630752996851

https://video.coral.qq.com/varticle/7313247714/comment/v2?callback=_varticle7313247714commentv2&orinum=100&oriorder=o&pageflag=1&cursor=6838127093335586287&scorecursor=0&orirepnum=2&reporder=o&reppageflag=1&source=132&_=1630752996850

https://video.coral.qq.com/varticle/7313258351/comment/v2?callback=_varticle7313258351commentv2&orinum=10&oriorder=o&pageflag=1&cursor=6838101562707822837&scorecursor=0&orirepnum=2&reporder=o&reppageflag=1&source=132&_=1630753165406

最后,我们发现这个地址可以简化成以下部分

url = f'https://video.coral.qq.com/varticle/{comment_id}/comment/v2?'
params = {
    'orinum'30,
    'cursor': cursor,
    'oriorder''t'
}  

这其中四个参数含义如下:

  • orinum 是每次请求的评论数量,默认是10个,我通过尝试发现最多支持30个,所以这里我设置值为 30
  • cursor 是每次请求初始的评论id,变化规律上可以初始值设为0,下一次请求用上一次请求得到的评论列表中最后一个评论的id即可
  • oriorder 是请求的评论排序(t是指按照时间顺序,默认是另外一个最热)
  • 除了上面三个参数外,其实还有一个comment_id,每集都有一个id用于采集对应评论的,所以我们还需要研究这个id在哪获取

刚才提到我们需要获取每集的评论id数据,很巧我们发现请求每集的页面数据就可以获取。

这里需要注意的是,我们直接用requests请求每集页面的数据和实际网页端的不太一样,但是相关数据都有,我们可以找找发现。

比如,剧集的ID列表如下:

剧集的ID列表

剧集的评论id所在位置:

剧集评论id

接着,我们就用re正则表达式进行数据解析即可获取。


2. 爬虫过程

通过网页分析和我们的采集需求,整个过程可以分为以下几部分:

  • 爬取剧集页面数据
  • 解析得到剧集ID和剧集评论ID
  • 采集全部剧集评论
  • 保存数据到本地

2.1. 引入需要的库

import requests
import re
import pandas as pd
import os

2.2. 爬取剧集页面数据

# 用于爬取剧集页面数据
def get_html(url):
    headers = {
        "User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36",
    }

    r = requests.get(url, headers=headers)
    # 乱码修正
    r.encoding = r.apparent_encoding
    text = r.text
    # 去掉非字符数据
    html = re.sub('\s''', text)

    return html

2.3. 解析剧集ID和剧集评论ID

# 传入电视剧等的id,用于爬取剧集id和评论id
def get_comment_ids(video_id):
    # 剧目地址
    url = f'https://v.qq.com/x/cover/{video_id}.html'
    html = get_html(url)
    data_list = eval(re.findall(r'"vip_ids":(\[.*?\])', html)[0])    
    data_df = pd.DataFrame(data_list)
    
    comment_ids = []
    for tid in data_df.V:
        # 每集地址
        url = f'https://v.qq.com/x/cover/{video_id}/{tid}.html'
        html = get_html(url)
        comment_id = eval(re.findall(r'"comment_id":"(\d+)"', html)[0])
        comment_ids.append(comment_id)
    
    data_df['comment_id'] = comment_ids
    data_df['剧集'] = range(1,len(comment_ids)+1)
    return data_df

2.4. 采集全部剧集评论

# 获取全部剧集评论
def get_comment_content(data_df):
    for i, comment_id in enumerate(data_df.comment_id):
        i = i+1
        # 初始 cursor
        cursor = 0
        num = 0
        while True:
            url = f'https://video.coral.qq.com/varticle/{comment_id}/comment/v2?'
            params = {
                'orinum'30,
                'cursor': cursor,
                'oriorder''t'
            }    
            r = requests.get(url, params=params)
            data = r.json()
            data = data['data']
            if len(data['oriCommList'])==0:
                break
            # 评论数据
            data_content = pd.DataFrame(data['oriCommList'])
            data_content = data_content[['id''targetid''parent''time''userid''content''up']]
            # 评论员信息
            userinfo = pd.DataFrame(data['userList']).T
            userinfo = userinfo[['userid''nick''head''gender''hwlevel']].reset_index(drop=True)
            # 合并评论信息与评论员信息
            data_content = data_content.merge(userinfo, how='left')
            data_content.time = pd.to_datetime(data_content.time, unit='s') + pd.Timedelta(days=8/24)
            data_content['剧集'] = i
            data_content.id = data_content.id.astype('string')
            save_csv(data_content)
            # 下一个 cursor
            cursor = data['last']
            num =num + 1
            pages = data['oritotal']//30 + 1
            print(f'第{i}集的第{num}/{pages}页评论已经采集!')

2.5. 保存数据到本地

# 将评论数据保存本地
def save_csv(df):
    file_name = '评论数据.csv'
    if os.path.exists(file_name):
        df.to_csv(file_name, mode='a', header=False,
          index=None, encoding='utf_8_sig')
    else:
        df.to_csv(file_name, index=None, encoding='utf_8_sig')
    print('数据保存完成!')


3. 数据统计与可视化展示

本次的数据统计与可视化展示方法可以参考此前推文《》和《》等

3.1. 数据预览

抽样5条看看

df.sample(5)

看看数据信息

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 35758 entries, 0 to 35757
Data columns (total 12 columns):
 #   Column    Non-Null Count  Dtype 
---  ------    --------------  ----- 
 0   id        35758 non-null  int64 
 1   targetid  35758 non-null  int64 
 2   parent    35758 non-null  int64 
 3   time      35758 non-null  object
 4   userid    35758 non-null  int64 
 5   content   35735 non-null  object
 6   up        35758 non-null  int64 
 7   nick      35758 non-null  object
 8   head      35758 non-null  object
 9   gender    35758 non-null  int64 
 10  hwlevel   35758 non-null  int64 
 11  剧集        35758 non-null  int64 
dtypes: int64(8), object(4)
memory usage: 3.3+ MB

才哥也进行了评论,我们看看是不是采集到了

才哥的评论

才哥的userid1296690233,我们查询一下,发现才哥VIP等级居然6级

df.query('userid==1296690233')

head字段是头像,我们看看是不是才哥头像

from skimage import io
# 显示头像
img_url = df.query('userid==1296690233')['head'].iloc[0]
image = io.imread(img_url)
io.imshow(image)

对上了,对上了!!

3.2. 分集评论数

绘图参考《》,所以我们这里是Pandas绘制带交互的可视化图,引入环境:

import pandas as pd
import pandas_bokeh

pandas_bokeh.output_notebook()
pd.set_option('plotting.backend''pandas_bokeh')

接下来,正式的数据统计与可视化展示开始

from bokeh.transform import linear_cmap
from bokeh.palettes import Spectral
from bokeh.io import curdoc
# curdoc().theme = 'caliber'

episode_comment_num = df.groupby('剧集')['id'].nunique().to_frame('评论数')
y = episode_comment_num['评论数']
mapper = linear_cmap(field_name='评论数', palette=Spectral[11] ,low=min(y) ,high=max(y))
episode_bar = episode_comment_num.plot_bokeh.bar(
    ylabel="评论数量"
    title="分集评论数"
    color=mapper,
    alpha=0.8,
    legend=False    
)

我们可以看到,第一集评论数最高,高达1.7万,占了全部评论的一半;其次是第7集的评论数,主要是本周播到了第7集哈!

3.3. 分日期评论数

df['日期'] = pd.to_datetime(df.time).dt.date
date_comment_num = df.groupby('日期')['id'].nunique().to_frame('评论数')
date_comment_num.index = date_comment_num.index.astype('string')

y = date_comment_num['评论数']
mapper = linear_cmap(field_name='评论数', palette=Spectral[11] ,low=min(y) ,high=max(y))
date_bar = date_comment_num.plot_bokeh.bar(
    ylabel="评论数量"
    title="分日期评论数"
    color=mapper,
    alpha=0.8,
    legend=False    
)

从8月30日开播,首播当天会员可看5集,作为会员的我一口气就看完了。我们发现开播后前2天评论数较多,由于每周1-3更新, 所以这几天的评论数整体也较高。

3.4. 分时评论数

df['时间'] = pd.to_datetime(df.time).dt.hour
date_comment_num = pd.pivot_table(df,
                                  values='id',
                                  index=['时间'],
                                  columns=['日期'],
                                  aggfunc='count'
                                 )
time_line = date_comment_num.plot_bokeh(kind="line",
                            legend="top_left",
                            title="分时评论数"
                           )
分时评论数

通过分时评论数曲线,我们发现在首播当日8点的小时评论数冲击到最高,此后比较符合电视剧观看行为:中午、晚上及午夜较高。

3.5. 评论员VIP等级分布

vip_comment_num = df.groupby('hwlevel').agg(用户数=('userid','nunique'),
                                            评论数=('id','nunique')
                                           )
vip_comment_num['人均评论数'] = round(vip_comment_num['评论数']/vip_comment_num['用户数'],2)
usernum_pie = vip_comment_num.plot_bokeh.pie(
    y="用户数",
    colormap=Spectral[9],
    title="评论员VIP等级分布",
    )

不得不说,评论的人大部分都是VIP用户,难怪腾讯视频要搞什么超前点播弄所谓VIP上的VIP。。。

不同VIP用户人均评论数会有不同吗?

y = vip_comment_num['人均评论数']
mapper = linear_cmap(field_name='人均评论数', palette=Spectral[11] ,low=min(y) ,high=max(y))
vipmean_bar = vip_comment_num.plot_bokeh.bar(
    y = '人均评论数',
    ylabel="人均评论数"
    title="不同VIP用户人均评论数"
    color=mapper,
    alpha=0.8,
    legend=False    
)

基本呈现一个VIP等级越高 评价意愿越高!但是为什么呢?

3.6. 评论长度

写评论的网友大部分都是666,好看之类的词汇,比如才哥就是等更新三个字,那么一般都会评价多少个字符呢?

import numpy as np

df['评论长度'] = df['content'].str.len()
df['评论长度'] = df['评论长度'].fillna(0).astype('int')

contentlen_hist = df.plot_bokeh.hist(
    y='评论长度',
    ylabel="评论数"
    bins=np.linspace(010026),
    vertical_xlabel=True,
    hovertool=False,
    title="评论点赞数直方图",
    color='red',
    line_color="white",
    legend=False,
#     normed=100,
    )

我们找几个评论内容老长的看看

(df.sort_values(by='评论长度',ascending=False)
 [['剧集','content','评论长度','nick','hwlevel']].head(3)
 .style.hide_index()
)

我想说,这评论是在那抄袭的,还是真有才啊?

3.7. 评论点赞数

咱们就看看被点赞最多的几条吧

# pd.set_option('display.max_colwidth',1000)
(df.sort_values(by='up',ascending=False)
 [['剧集','content','up','nick','hwlevel']].head()
 .style.hide_index()
)

看地图 别迷路!是有什么梗吗?超8000的点赞~~

3.8. 评论最多的用户

user_comment_num = df.groupby('userid').agg(评论数=('id','nunique'),
                                            vip等级=('hwlevel','max')
                                           ).reset_index()
user_comment_num.sort_values(by='评论数',ascending=False).head()
userid评论数vip等级
640014751335
1368145091241
1214181910183
1305770517172
1015445833145

评价33条,这用户也是很牛!!我们看看他都评啥了:

df.query('userid==640014751')[['nick','剧集','time','content']].sort_values(by='time')

有点无聊,是来刷好评的吧!!我们还是看看评价第二多的小伙伴吧!

df.query('userid==1368145091')[['nick','剧集','time','content']].sort_values(by='time')

不得不说,看着正常一些。。不过,感觉有点话唠,哈哈!

这两位都是啥头像,感兴趣的看看:

from skimage import io
# 显示头像
img_url = df.query('userid==640014751')['head'].iloc[0]
image = io.imread(img_url)
io.imshow(image)
评论最多额哥们头像
from skimage import io
# 显示头像
img_url = df.query('userid==1368145091')['head'].iloc[0]
image = io.imread(img_url)
io.imshow(image)
评论第二多额哥们头像

咳咳,我就不做评价了,毕竟我的头像和昵称也都很...

3.9. 评论词云

这部分参考《》,我们将从整体词云和主角词云几部分展开

先看看咱们三个主角提及次数

df.fillna('',inplace=True)

hu = ['老胡','胡八一','潘粤明','胡','潘']
yang = ['张雨绮','Shirley','杨']
wang = ['姜超','胖子']

df_hu = df[df['content'].str.contains('|'.join(hu))]
df_yang = df[df['content'].str.contains('|'.join(yang))]
df_wang = df[df['content'].str.contains('|'.join(wang))]

df_star = pd.DataFrame({'角色':['胡八一','Shirley杨','王胖子'],
                        '权重':[len(df_hu),len(df_yang),len(df_wang)]
                       })

y = df_star['权重']
mapper = linear_cmap(field_name='权重', palette=Spectral[11] ,low=min(y) ,high=max(y))
df_star_bar = df_star.plot_bokeh.bar(
    x = '角色',
    y = '权重',
    ylabel="提及权重"
    title="主要角色提及权重"
    color=mapper,
    alpha=0.8,
    legend=False    
)

王胖子是笑点担当,不得不说出镜次数真高!!!

整体词云

整体词云

胡八一词云

胡八一

Shirley杨词云

张雨绮

王胖子词云

王胖子

词云核心代码

import os   
import stylecloud
from PIL import Image
import jieba
import jieba.analyse
import pandas as pd
from wordcloud import STOPWORDS

def ciYun(data,addWords,stopWords):
    print('正在作图...')
    comment_data = data
    
    for addWord in addWords:
        jieba.add_word(addWord)

    comment_after_split = jieba.cut(str(comment_data), cut_all=False)
    words = ' '.join(comment_after_split)
    
    # 词云停用词 
    stopwords = STOPWORDS.copy()
    for stopWord in stopWords:
        stopwords.add(stopWord)

    # 就下面代码,即可获取满足类型要求的参数
    stylecloud.gen_stylecloud(
                              text=words,
                              size = 800,
                              palette='tableau.BlueRed_6'# 设置配色方案
                              icon_name='fas fa-mountain',# paper-plane mountain thumbs-up male fa-cloud
                              custom_stopwords = stopwords,
                              font_path='FZZJ-YGYTKJW.TTF'
                              # bg = bg, 
                              # font_path=font_path,  # 词云图 字体(中文需要设定为本机有的中文字体)
                             )
    
    print('词云已生成~')
    pic_path = os.getcwd()
    print(f'词云图文件已保存在 {pic_path}')
    
data = df.content.to_list()
addWords = ['潘老师','云南虫谷','还原度',
            '老胡','胡八一','潘粤明',
            '张雨绮','Shirley','Shirley杨','杨参谋',
            '王胖子','胖子']
# 添加停用词
stoptxt = pd.read_table(r'stop.txt',encoding='utf-8',header=None)
stoptxt.drop_duplicates(inplace=True)
stopWords = stoptxt[0].to_list()
words = ['说','年','VIP','真','这是','没','干','好像']
stopWords.extend(words)    
    
# 运行~
ciYun(data,addWords,stopWords)

以上就是本次全部内容,如果你感兴趣,可以点赞+在看后台回复 210905 领取代码+数据哦!




推荐阅读


相关文章

详解Python 3.6.x程序打包并发布至pypi的完整过程

2021-11-06
python setup.py sdist和python setup.py bdist_wheel,创建相应的文件,如图:第四步,登录pypi.python.org网站,注册账号并登录,然...

使用Python 3.5/3.6监听本机任意窗口中的按键操作

2021-11-06
python setup.py build_ext --swig=..\swigwin-3.0.12\swig.exe,可以根据实际情况修改swig.exe的路径,另外本机最好已安装VC2008第五...

边看边用!这本 Python 3.6 的书火爆了 IT 圈!

2021-11-06
而且注意这本书是基于Python3.6来的.4.技术方向多这本书涵盖了网络编程、数据分析、网络爬虫等大量企业实用的知识,内容涵盖了...

如何在 Linux 中安装最新的 Python 3.6 版本

2021-11-06
Python 3.6在我写这篇文章的时候(2017 年三月中旬),在 CentOS 和 Debian 8 中可用的最新 Python 版本分别是 Python 3.4 和 ...

Python 3.6 Highlights

2021-11-06
//docs.python.org/3.9/whatsnew/3.6.html#whatsnew36-pep525之前yield和await没法在同一个函数里用,现在可以了,所以可以做成异...

《统计学习方法》的Python 3.6复现,已开源!

2021-11-06
Python 3.6,已经全部测试通过.项目地址:https://github.com/fengdu78/lihang-code文末附电子书及代码下载方式《统计学习方法》,...

使用异步Python 3.6和Redis编写快速应用程序

2021-11-06
英文原文:https://eng.paxos.com/write-fast-apps-using-async-python-3.6-and-redis 译者:guoziqing

Python3.6入门与进阶实战等多套视频教程

2021-11-06
以下是一些视频教程的链接与关键词,关注以下公众号二维码,回复关键词就可领取Python进阶与实战视频教程链接: https://pan....

资源 | 《统计学习方法》的Python 3.6复现,实测可用

2021-11-06
Python数据科学【读者福利】 ↓↓↓【知识星球】:Python数据科学学习社区【限时下载】:500g学习资源免费领取往期精彩推荐 ↓↓↓【1】...

刷爆抖音,4万好评!这本 Python 3.6 的书又断货了...

2021-11-06
而且注意这本书是基于Python3.6来的.4.技术方向多这本书涵盖了网络编程、数据分析、网络爬虫等大量企业实用的知识,内容涵盖了...

随机推荐