也就是在1月24日该剧迎来了大结局,我们爬取腾讯视频全23集共31.79万条弹幕,看看大家都在聊什么!
《山海情》讲述了二十世纪九十年代以来,西海固的人民和干部们响应国家扶贫政策的号召,完成异地搬迁,在福建的对口帮扶下,通过辛勤劳动和不懈探索,将风沙走石的“干沙滩”建设成寸土寸金的“金沙滩”的故事。
目录:
爬虫部分代码较多,我们放在最后哈。完整代码及弹幕数据文件大家亦可 在公众号 后台 回复 山海情
获取。
预览数据中,相关字段说明如下:
commentid :弹幕唯一id content :弹幕内容 upcount :点赞数 timepoint :所属集对应弹幕发送时间(s) opername :弹幕 用户昵称 uservip_degree :弹幕用户vip等级 集 :所属集数
In [1]: df.head()
Out[1]:
commentid content upcount ... opername uservip_degree 集
0 6754755585141912011 我来这么早?没人么 25 ... NaN 5 1
1 6754755922809562416 第一 0 ... NaN 4 1
2 6754758012762783182 来了来了 10 ... NaN 3 1
3 6754758030261780084 打卡了 11 ... NaN 5 1
4 6754758276425465450 宁夏人来啦! 17 ... 寒 0 1
[5 rows x 7 columns]
In [2]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 317927 entries, 0 to 317926
Data columns (total 7 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 commentid 317927 non-null int64
1 content 317927 non-null object
2 upcount 317927 non-null int64
3 timepoint 317927 non-null int64
4 opername 126947 non-null object
5 uservip_degree 317927 non-null int64
6 集 317927 non-null int64
dtypes: int64(5), object(2)
memory usage: 17.0+ MB
In [3]: df[['upcount','uservip_degree']].describe()
Out[3]:
upcount uservip_degree
count 317927.000000 317927.00000
mean 1.893925 0.77157
std 3.460646 1.69589
min 0.000000 0.00000
25% 0.000000 0.00000
50% 1.000000 0.00000
75% 2.000000 0.00000
max 102.000000 8.00000
全剧一共23
集,共有317,927
条弹幕。
In [4]: danmu = df.groupby('集')['commentid'].count().to_frame('弹幕数').reset_index()
山海情
绘图代码:
# 引入需要用到的库
from pyecharts.globals import CurrentConfig, NotebookType
CurrentConfig.NOTEBOOK_TYPE = NotebookType.JUPYTER_LAB
from pyecharts import options as opts
from pyecharts.commons.utils import JsCode
from pyecharts.charts import *
# 绘制租房类型分布
bar = (
Bar(init_opts=opts.InitOpts(theme='dark', width='1000px'))
.add_xaxis(danmu.集.to_list())
.add_yaxis('弹幕数', danmu['弹幕数'].to_list())
.set_global_opts(legend_opts=opts.LegendOpts(is_show=True),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)),
title_opts=opts.TitleOpts(title="《山海情》各集弹幕数",
subtitle='数据采集日期:2021年1月'),
)
)
bar.load_javascript()
bar.render_notebook()
本部分我们用jieba
进行分词 和 stylecloud
进行词云绘制,由于stylecloud在制作词云时的背景只能用它指定的哪些,我们需要进行相关调整后使其能自定义背景图。
我们用全部的弹幕分词后绘制词云(以福建省行政区域为背景)如下图所示,咱们可以发现,在本剧中最受用户热议的是得宝、水花、老师和麦苗这四个角色。
全部弹幕
我们去掉人名(将剧中的人名添加到停用词)的弹幕分词后绘制词云(以宁夏省行政区域为背景)如下图所示,可以发现 大家都觉得该剧特别的真实。评价基本全是正向的,表示 喜欢的,夸好看的,会让人想起自己的小时候,以及剧中也充满着让人哈哈哈的桥段(从郭京飞饰演的刘金山登场开始,那一口胡建普通话简直太逗了
)。
去掉人名
马得宝,马得福的弟弟,作为弹幕最爱角色,是在他同龄的几个小伙伴中的领导者,年轻气盛有勇有谋,从策划逃村时给小伙伴们清晰地分工。挨着父亲的鞭子仍喊着“想要走出去”,他与麦苗一家在移民新区重逢时有条不紊地忙前忙后,再到带着丢了工作的小伙伴进程谋职时的坚定果断,他身上都透露出超越年龄的担当与智慧,在闽宁镇建设中起到了先锋带头作用。
马得福,本剧的主角,奋斗在第一线的基层干部,从农校毕业的第一件事就是追回家乡涌泉村里“逃跑”的吊庄户。从苦口婆心地劝返吊庄户、帮助村民完成“吊庄移民”工作,到软磨硬泡给移民村通电,再到之后东西协作扶贫政策出台后,带领村民们共同走上致富的康庄大道。
李水花,从小与马得福青梅竹马,情投意合。父亲因为一头驴、两只羊、两笼鸡的彩礼就把她“卖”到邻村。她内心坚定乐观,永远笑对生活和未知,不愿意认命的她选择坐火车出逃反抗命运。她在福建教授凌一农的帮助下,在自家庭院里种起了双孢菇。在闽宁镇建设中浇灌希望,追寻光芒。
白麦苗,白校长的女儿,和得宝的爱情非常纯。因为母亲早逝,她对父亲心存隔阂,从小脾气有些古怪,跟得宝、水旺、尕娃一起长大。她天资聪颖,直言快语,内心好强不服输,敢于走出去,愿意尝试新的生活方式,想要靠自己勤劳的双手开创新世界。她积极响应国家政策,到福建莆田打工,她在工作中帮助同乡姐妹们,且细心努力,成为厂里女工中的先进典范。
白校长,教书育人的白校长,憨厚朴实,热情、用心良苦、无奈和不被理解的痛苦,但他一生为教育事业做贡献,坚守着自己教书育人的事业。他是一位来到宁夏西海固地区支教的老师,他已经扎根于涌泉村了,和西海固的人口音虽然略有不同,但早已把那里当成了自己的家,并且在那里娶妻生子,似乎成为地地道道的涌泉村人了。
关于弹幕情感分析,我们这里采用的是腾讯云-自然语言处理
中的篇章分析相关接口。
由于弹幕总量较多,这里仅进行两类文本取样分析:①抽样;②每集点赞第1的弹幕。
腾讯情感分析接口代码如下:
前提需要将API Python SDK 安装到您的环境中
pip install --upgrade tencentcloud-sdk-python
import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.nlp.v20190408 import nlp_client, models
def get_sentiment(text):
try:
cred = credential.Credential("你的key", "你的secret")
httpProfile = HttpProfile()
httpProfile.endpoint = "nlp.tencentcloudapi.com"
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
client = nlp_client.NlpClient(cred, "ap-guangzhou", clientProfile)
req = models.SentimentAnalysisRequest()
params = {
"Text": text,
"Mode": "3class"
}
req.from_json_string(json.dumps(params))
resp = client.SentimentAnalysis(req)
j = json.loads(resp.to_json_string())
return j['Sentiment']
except TencentCloudSDKException as err:
print(err)
举例:
In [4]: get_sentiment(text = '你吃过了吗')
Out[4]: 'neutral'
In [5]: get_sentiment(text = '你好')
Out[5]: 'positive'
In [6]: get_sentiment(text = '你很烦')
Out[6]: 'negative'
抽样10条弹幕,做简单的情感分析,不过好像并不是很准,哈哈。
In [7]: sample = df.sample(10)
In [8]: sample
Out[8]:
commentid content ... uservip_degree 集
20206 6755378371346595774 演的太好了 ... 0 2
174127 6757131757407377261 这样的县长应该提起来重用 ... 1 13
15589 6755432423347440299 给了点路费 ... 0 1
314161 6759132523517160753 得花这过分了 ... 2 23
19362 6758001303567706465 96年路过 ... 0 2
147508 6756875386320615867 现在上海15左右一斤 ... 0 11
247891 6758228650422150608 我要早点遇上这老师该多好 ... 0 18
95010 6757574373873484314 南方有蟑螂正常 ... 0 6
34339 6755052757532740869 九十年代有这么穷吗?七十年代吧 ... 4 2
4332 6755503063420138319 都是西部演员啊 ... 0 1
[10 rows x 7 columns]
In [9]: data = sample[['content']]
In [10]: data['情感倾向'] = data['content'].apply(get_sentiment)
In [11]: data
Out[11]:
content 情感倾向
20206 演的太好了 positive
174127 这样的县长应该提起来重用 neutral
15589 给了点路费 positive
314161 得花这过分了 negative
19362 96年路过 neutral
147508 现在上海15左右一斤 neutral
247891 我要早点遇上这老师该多好 positive
95010 南方有蟑螂正常 negative
34339 九十年代有这么穷吗?七十年代吧 neutral
4332 都是西部演员啊 neutral
通过排序然后分组取第一个值,即可获得每集点赞最多的弹幕。
获取每集点赞最多的弹幕
In [12]: data = df.sort_values('upcount', ascending=False).groupby('集', as_index=False).first()
...: data['情感倾向'] = data['content'].apply(get_sentiment)
弹幕情感
对于单集弹幕的爬虫比较简单,直接获取真实的弹幕url地址即可。
对于全部剧集的弹幕爬虫,我们需要观察单集的弹幕url地址规律然后构建全部剧集的弹幕url地址列表。这其中比较关键的点在 target_id的获取
,具体大家阅读代码练习研究吧。这里我就先不做详细说明了。
import pandas as pd
import requests
from bs4 import BeautifulSoup
import re
import time
def get_suf(url):
# 获取target后缀列表
url_suf = url
r_suf = requests.get(url_suf)
# 乱码修正
r_suf.encoding = r_suf.apparent_encoding
soup = BeautifulSoup(r_suf.text, 'lxml')
# 获取全部剧集 所在的节点 class="mod_episode"
sellList = soup.find(class_="mod_episode")
# 获取全部剧集节点列表 _stat="videolist:click"
lis = sellList.find_all('span',_stat="videolist:click")
# 选取第一个target_id 后缀
# div = lis[0].get('id')
sufs = []
for li in lis:
suf = li.get('id')
sufs.append(suf)
return sufs
def get_pre(sufs):
# 获取target_id列表
url_pre = 'https://access.video.qq.com/danmu_manage/regist?'
# 参数
parames = {
'vappid': 97767206,
'vsecret': 'c0bdcbae120669fff425d0ef853674614aa659c605a613a4',
'raw': 1,
}
# payload
target_ids = []
for suf in sufs:
payload = {
"wRegistType":2,
"vecIdList":[suf],
"wSpeSource":0,
"bIsGetUserCfg":1,
"mapExtData":{
suf:{
"strCid":"mzc0020009e7qbj",
"strLid":""
}
}
}
r_pre = requests.post(url_pre, params = parames, json = payload)
text = r_pre.text
target_id = re.findall(r'targetid=(.*?)"',text)[0]
target_ids.append(target_id)
return target_ids
def get_danmu(target_ids):
# 获取弹幕信息
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",
}
url = 'https://mfm.video.qq.com/danmu?'
df_list = []
for i,target_id in enumerate(target_ids):
time.sleep(1)
print(f'正在爬取第 {i+1} 集数据')
comments = []
for page in range(1,100):
# page = 1
parames = {
'otype': 'json',
# 'callback': 'jQuery1910568921143306111_1611479965774',
'target_id': target_id,
# 'session_key': '30279,0,1611479967',
'timestamp': 15+30*(page-1),
}
try:
r = requests.get(url,headers=headers,params=parames)
except:
r = requests.get(url,headers=headers,params=parames,verify=False)
j = r.json()
comment = j['comments']
# if len(comment) == 0:
# break
# else:
comments.extend(comment)
print(f'第 {i+1} 集 已爬取 {len(comments)} 条弹幕')
df = pd.DataFrame(comments)
df['集'] = i+1
df_list.append(df)
data = pd.concat(df_list)
data = data[['commentid', 'content', 'upcount',
'timepoint', 'opername', 'uservip_degree', '集']]
return data
if __name__ == '__main__':
# 忽略警告信息
requests.packages.urllib3.disable_warnings()
url_suf = 'https://v.qq.com/x/cover/mzc0020009e7qbj.html'
# 获取后缀
sufs = get_suf(url_suf)
# 获取target_ids
target_ids = get_pre(sufs)
# 获取全部数据
df = get_danmu(target_ids)