分析哔哩哔哩生活搞笑区的热度视频信息,分析月度视频的热词,三联等数据对视频播放量的影响。
数据爬取
确定目标
因为想要一个量大的数据集,因此没有考虑热榜排名,因为所有区加起来也才一千左右。全部视频信息的话技术不行,然后就盯上了分区榜。
从这个榜单可以选择时间段,可以根据每个月的视频热度排名等信息,来分析月度热点,哪些视频更加容易火,以及各种因素对视频播放量的影响。虽然只是一个小分区月度热度排名,并不包含全部视频,但是数据量也是极大的。下图可以看到接近有23万条数据。
网站分析
这里存在一个难点,就是虽然浏览器上是可以查看网页源码,并且包含了视频的相关信息,但是用requests请求之后的网页源码却并没有相关的信息。因此前两个版本,我采用了selenium库的方法来获取信息,但是这个方法有一个缺点,速度慢(因为要跟浏览器一样加载整个页面信息)、信息少(只有标题、作者、视频简介、以及视频页和个人主页网址),很麻烦。于是这次我换成了API调用的方法。
我们选择一个具体的数字来查找,可以发现搜索出来一个search的接口。
点进去之后,可以发现里面的result共有20条数据,刚好对应着每页20个视频。
可以看到里面包含了作者、标题、标签、播放等一系列数据。
接口为https://s.search.bilibili.com/cate/search?callback=main_ver=v3&search_type=video&view_type=hot_rank&order=click©_right=-1&cate_id=138&page=1&pagesize=20&jsonp=jsonp&time_from=20200801&time_to=20200831
,view_type为排行类型,page为页面数,pagesize为页面最大的视频数,上限好像是100。最后面就是时间了。
但是我还需要三连数据以及UP主的粉丝量。同理分析
得到三连的API接口:https://api.bilibili.com/x/web-interface/archive/stat?aid=371876135
其中aid由BV转换。
粉丝数为https://api.bilibili.com/x/relation/stat?vmid=32172331
mid可以在第一个接口处获取。
IP池
这时候虽然已经可以开始爬取了,但是如果数据量稍微有一点大,访问稍微有点频繁,就会导致IP被屏蔽。
这时候我们就需要用到代理IP,免费的代理IP虽然也有,而且GITHUB上也有专门的项目来建立代理IP池。但是免费的终究很麻烦,于是我选择了日租独享的IP。http://www.xdaili.cn/
代码
# coding: utf-8
# Author:南岛鹋
# Blog: www.ndmiao.cn
# Date :2020/8/25 10:29
# Tool :PyCharm
import requests
import csv
import json
import random
import time
class video_data:
def __init__(self):
self.url = 'https://s.search.bilibili.com/cate/search?main_ver=v3&search_type=video&view_type=hot_rank&order=click©_right=-1&cate_id=138&page={}&pagesize=20&jsonp=jsonp&time_from=20200801&time_to=20200831'
self.page = 11507
self.alphabet = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'
def dec(self, x): # BV号转换成AV号
r = 0
for i, v in enumerate([11, 10, 3, 8, 4, 6]):
r += self.alphabet.find(x[v]) * 58 ** i
return (r - 0x2_0840_07c0) ^ 0x0a93_b324
def random_headers(self, path): # 随机读取一个头信息
with open(path, 'r') as f:
data = f.readlines()
f.close()
reg = []
for i in data:
k = eval(i) # 将字符串转化为字典形式
reg.append(k)
header = random.choice(reg)
return header
def get_ip(self): # 代理IP获取
print('切换IP中.......')
url = '代理IP的地址'
ip = requests.get(url).text
if ip in ['{"ERRORCODE":"10055","RESULT":"提取太频繁,请按规定频率提取!"}', '{"ERRORCODE":"10098","RESULT":"可用机器数量不足"}']: # 出现频繁或者机器不足,睡眠14秒
time.sleep(14)
ip = requests.get(url).text
print(ip)
else:
print(ip)
proxies = {
'https': 'http://' + ip,
'http': 'http://' + ip
} # 设置https和http可以按需选择
return proxies
def get_requests(self, url, proxy): # 请求的函数
headers = self.random_headers('headers.txt')
# 将头信息和IP写入,用try来减少意外对程序的影响
try:
response = requests.get(url, timeout=3, headers=headers, proxies=proxy)
except requests.exceptions.RequestException as e:
print(e)
proxy = self.get_ip()
try:
response = requests.get(url, timeout=3, headers=headers, proxies=proxy)
except requests.exceptions.RequestException as e:
print(e)
print('原始IP')
response = requests.get(url, timeout=3, headers=headers)
return response, proxy
def get_follower(self, mid, proxy): # 获取粉丝数
url = 'https://api.bilibili.com/x/relation/stat?vmid=' + str(mid)
r, proxy = self.get_requests(url, proxy)
result = json.loads(r.text) # 用json来解析文本
# 按照需求获取需要的数据,因为粉丝数是必定存在的,所以失败了需要多次尝试获取。
try:
follower = result['data']['follower']
except:
follower,proxy = self.get_follower(mid, proxy)
return follower, proxy
def get_view(self, BV, proxy): # 获取三连和播放
aid = self.dec(BV)
url = 'https://api.bilibili.com/x/web-interface/archive/stat?aid=' + str(aid)
r, proxy = self.get_requests(url, proxy)
result = json.loads(r.text)
view = {}# 因为视频虽然在排行榜,但是很可能已经删除,所以没有数据为None
try:
view['view'] = result['data']['view']
view['danmu'] = result['data']['danmaku']
view['reply'] = result['data']['reply']
view['like'] = result['data']['like']
view['coin'] = result['data']['coin']
view['favorite'] = result['data']['favorite']
view['share'] = result['data']['share']
view['rank'] = result['data']['his_rank']
except:
view['view'] = 'None'
view['danmu'] = 'None'
view['reply'] = 'None'
view['like'] = 'None'
view['coin'] = 'None'
view['favorite'] = 'None'
view['share'] = 'None'
view['rank'] = 'None'
return view, proxy
def get_parse(self, result, proxy): # 整合数据
content = []
items = result['result']
for item in items:
pubdate = item['pubdate']
title = item['title']
author = item['author']
bvid = item['bvid']
mid = item['mid']
follower, proxy = self.get_follower(mid, proxy)
video_view, proxy = self.get_view(bvid, proxy)
view = video_view['view']
danmu = video_view['danmu']
reply = video_view['reply']
like = video_view['like']
coin = video_view['coin']
favorite = video_view['favorite']
share = video_view['share']
rank = video_view['rank']
tag = item['tag']
con = [pubdate, title, author, bvid, mid, follower,view,danmu,reply,like,coin,favorite,share,rank, tag]
content.append(con)
print(con)
print(content)
self.save(content)
return proxy
def write_header(self):
header = ['日期', '标题', '作者', 'BV', 'mid', '粉丝', '播放', '弹幕', '评论', '点赞','硬币','收藏','转发','排名','标签']
with open('fun_video.csv', 'a', encoding='gb18030', newline='')as f:
write = csv.writer(f)
write.writerow(header)
def save(self,content):# 存入csv
with open('fun_video.csv', 'a', encoding='gb18030', newline='')as file:
write = csv.writer(file)
write.writerows(content)
def run(self):
#self.write_header()
proxy = self.get_ip()
for i in range(168, self.page):
url = self.url.format(i)
response, proxy = self.get_requests(url, proxy)
result = json.loads(response.text)
proxy = self.get_parse(result, proxy)
print('第{}页爬取完毕'.format(i))
if __name__ == '__main__':
video = video_data()
video.run()
数据分析
以下代码在Notebook上运行
首先我们需要导入自己需要用到的库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from wordcloud import WordCloud, STOPWORDS #导入模块worldcloud
from PIL import Image #导入模块PIL(Python Imaging Library)图像处理库
import numpy as np #导入模块numpy,多维数组
import matplotlib
import jieba
数据预处理
读取数据
data=open(r'D:\video_data.csv',encoding='utf-8')
video_data = pd.read_csv(data)
查看数据前五行
video_data.head()#查看前五行
浏览数据的大概信息
video_data.info()#视频数据的信息
对数据进行预处理,将None值换成0,将数字数据的格式换成int
video_data['播放'].replace('None', 0,inplace = True)#将数据中标记为None的数据替换成0,方便数据处理
video_data['弹幕'].replace('None', 0,inplace = True)
video_data['评论'].replace('None', 0,inplace = True)
video_data['点赞'].replace('None', 0,inplace = True)
video_data['硬币'].replace('None', 0,inplace = True)
video_data['收藏'].replace('None', 0,inplace = True)
video_data['转发'].replace('None', 0,inplace = True)
video_data['排名'].replace('None', 0,inplace = True)
video_data['播放'] = video_data['播放'].astype("int64")#将数字的格式转换成int格式,用于数据处理
video_data['弹幕'] = video_data['弹幕'].astype("int")
video_data['评论'] = video_data['评论'].astype("int")
video_data['点赞'] = video_data['点赞'].astype("int")
video_data['硬币'] = video_data['硬币'].astype("int")
video_data['收藏'] = video_data['收藏'].astype("int")
video_data['转发'] = video_data['转发'].astype("int")
video_data['排名'] = video_data['排名'].astype("int")
查看预处理之后的数据格式
video_data.info()
对标题进行预处理,只保留中文字符
video_data['标题'] = video_data['标题'].str.replace(r'[^\u4e00-\u9fa5]','')#只保留中文
将标题分割成一个个短词
video_data['标题'].fillna(' ',inplace=True) #将空值替换成空格
video_data['标题'] = video_data['标题'].apply(lambda x:' '.join(jieba.cut(x)))#将句子分割成一个个词语
video_data['标题'].head()
同理处理标签
#同理处理标签
video_data['标签'] = video_data['标签'].str.replace(',','')
video_data['标签'].fillna(' ',inplace=True)
video_data['标签'] = video_data['标签'].apply(lambda x:' '.join(jieba.cut(x)))
video_data['标签'].head()
将时间信息转化成标准格式
video_data.日期 = pd.to_datetime(video_data.日期.str.findall(r'\d{4}.+').str.get(0)) #将时间进行解析,转化为标准格式
video_data['weekday'] = video_data.日期.dt.weekday #获取星期几
video_data['hour'] = video_data.日期.dt.hour #获取小时
设置一个四舍五入代码
#用于计算三连、弹幕、评论率
def new_round(_float, _len):
if isinstance(_float, float):
if str(_float)[::-1].find('.') <= _len:
return(_float)
if str(_float)[-1] == '5':
return(round(float(str(_float)[:-1]+'6'), _len))
else:
return(round(_float, _len))
else:
return(round(_float, _len))
计算三连等比率
video_data['点赞率']=new_round(video_data.点赞/video_data.播放*100,0)
video_data['硬币率']=new_round(video_data.硬币/video_data.播放*100,0)
video_data['收藏率']=new_round(video_data.收藏/video_data.播放*100,0)
video_data['转发率']=new_round(video_data.转发/video_data.播放*100,0)
video_data['弹幕率']=new_round(video_data.弹幕/video_data.播放*100,0)
video_data['评论率']=new_round(video_data.评论/video_data.播放*100,1)
查看处理后的数据
video_data.head()
数据分析
查看一共有几位UP主
print('共有{}位UP,分别是'.format(len(video_data['UP'].unique())))#unique是将重复的去除
video_data['UP'].unique()
统计每个播放量区间的视频数量
# 计算每个播放量区间的视频数量
data_length = len(video_data)
data_length_rate0 = len(video_data[video_data['播放']<10000])
data_length_rate1 = len(video_data[(video_data['播放']>=10000) & (video_data['播放']<100000)])
data_length_rate2 = len(video_data[(video_data['播放']>=100000) & (video_data['播放']<500000)])
data_length_rate3 = len(video_data[(video_data['播放']>=500000) & (video_data['播放']<1000000)])
data_length_rate4 = len(video_data[video_data['播放']>1000000])
结果展示
video_rate = [data_length_rate0,data_length_rate1,data_length_rate2,data_length_rate3,data_length_rate4]
data_view = ['[0,9999]','[10000,99999]','[100000,499999]','[500000,999999]','[1000000,....]']
video_rate
画图展示
# 画出饼图
plt.rcParams['font.sans-serif']=['SimHei'] # 中文不乱码
plt.rcParams['axes.unicode_minus'] = False
fig = plt.figure(figsize=(10,15))
plt.pie(video_rate,autopct='%1.2f%%') #画饼图(数据,数据对应的标签,百分数保留两位小数点)
plt.legend(
data_view,
fontsize=12,
title="区间",
loc="center left",
bbox_to_anchor=(1, 0.9))
plt.title("播放量占比")
plt.show()
只展示一万播放量以上的内容
video_rate = [data_length_rate1,data_length_rate2,data_length_rate3,data_length_rate4]
data_view = ['[10000,99999]','[100000,499999]','[500000,999999]','[1000000,....]']
fig = plt.figure(figsize=(10,15))
plt.pie(video_rate,autopct='%1.2f%%') #画饼图(数据,数据对应的标签,百分数保留两位小数点)
plt.legend(
data_view,
fontsize=12,
title="区间",
loc="center left",
bbox_to_anchor=(1, 0.9))
plt.title("播放量占比")
plt.show()
统计展示播放量排名前二十的UP主
#统计播放量排名前20的UP
top_20=video_data.sort_values(by=['播放'],ascending=False)[:20]
top_20['UP'].value_counts()
前20的具体数据
# 前20的具体数据
top_20[['UP','播放','粉丝']]
根据UP主分组,对每个UP八月的总播放量进行排序
#根据UP主分组,对每个UP八月的总播放量进行排序
print(video_data.groupby('UP')['播放'].sum().sort_values(ascending=False)[:20])
top_1 = video_data[video_data['UP']=='大霓奈']
print(top_1['UP'].value_counts())
top_1 = video_data[video_data['UP']=='陈师姬']
print(top_1['UP'].value_counts())
top_1 = video_data[video_data['UP']=='17岁反派里的持枪Boy']
print(top_1['UP'].value_counts())
对每个UP主的弹幕数综合进行排序
# 对每个UP主的弹幕数综合进行排序
video_data.groupby('UP')['弹幕'].sum().sort_values(ascending=False)[:20]
评论数展示
# 对每个UP主的评论数综合进行排序
video_data.groupby('UP')['评论'].sum().sort_values(ascending=False)[:20]
视频数量展示
# 对八月份每个UP主发的视频数量进行统计
video_data['UP'].value_counts()[:20]
对每周不同时间段发布的视频数量进行统计
# 对每周不同时间段发布的视频数量进行统计
fig1, ax1=plt.subplots(figsize=(14,4))
df=video_data.groupby(['hour', 'weekday']).count()['mid'].unstack()
df.plot(ax=ax1, style='-.')
plt.show()
# 对每周不同时间的视频播放量进行统计
fig2,ax2=plt.subplots(figsize=(14,4))
df=video_data.groupby(['hour','weekday']).sum()['播放'].unstack()
df.plot(ax=ax2,style='-.')
plt.show()
# 对每周不同时间段发布的视频播放量大于10000的视频数量进行汇总
view_1 = video_data[video_data['播放']>10000]
fig2,ax2=plt.subplots(figsize=(14,4))
df=view_1.groupby(['hour','weekday']).sum()['mid'].unstack()
df.plot(ax=ax2,style='-.')
plt.show()
view_2 = video_data[video_data['播放']>100000]
view_3 = video_data[video_data['播放']>1000000]
用词云显示热词
matplotlib.rcParams['font.sans-serif'] = ['KaiTi']#作图的中文
matplotlib.rcParams['font.serif'] = ['KaiTi']#作图的中文
infile = open("D:/stopwords.txt",encoding='utf-8')
stopwords_lst = infile.readlines()
STOPWORDS = [x.strip() for x in stopwords_lst] #去除头尾字符
stopwords = set(STOPWORDS) #设置停用词
def ciyun(texts,mid='all'): #支持指定UP主
if mid == 'all':
text = ' '.join(texts)
else:
text = ' '.join(texts[video_data['mid']==mid])
wc = WordCloud(font_path="msyh.ttc", background_color='white', max_words=100, stopwords=stopwords, max_font_size=80, random_state=42, margin=3) #配置词云参数
wc.generate(text) #生成词云
plt.imshow(wc,interpolation="bilinear")#作图
plt.axis("off") #不显示坐标轴
ciyun(video_data['标题'])
大于一万视频的热词
ciyun(view_1['标题'])
大于10万播放视频的热词
ciyun(view_2['标题'])
大于100万播放视频的热词
ciyun(view_3['标题'])
同理查看标签的热词
ciyun(video_data['标签'])
ciyun(view_1['标签'])
ciyun(view_2['标签'])
ciyun(view_3['标签'])
统计标题中包含老师的视频数和播放量综合
# 统计标题中包含老师的视频数和播放量综合
video_teacher = video_data[video_data['标题'].str.contains("老师")]
teacher = [len(video_teacher),video_teacher['播放'].sum()]
teacher
[3022, 16610756]
video_bro = video_data[video_data['标题'].str.contains("兄弟")]
brother = [len(video_bro),video_bro['播放'].sum()]
brother
[1897, 25270292]
video_girlfriend = video_data[video_data['标题'].str.contains("女朋友")]
girlfriend = [len(video_girlfriend),video_girlfriend['播放'].sum()]
girlfriend
[830, 28265224]
# 包含女朋友的标题中包含兄弟的视频信息
fun = video_girlfriend[video_girlfriend['标题'].str.contains("兄弟")].drop_duplicates()
print(len(fun))
print(fun)
video_yidan = video_data[video_data['标题'].str.contains("一旦")]
yidan = [len(video_yidan),video_yidan['播放'].sum()]
yidan
[89, 28302099]
video_wubei = video_data[video_data['标题'].str.contains("吾辈")]
wubei = [len(video_wubei),video_wubei['播放'].sum()]
wubei
[318, 35563900]
video_waizui = video_data[video_data['标题'].str.contains("歪嘴")]
waizui = [len(video_waizui),video_waizui['播放'].sum()]
waizui
[1810, 70787655]
查看带有这几个热词标题视频的个数饼状图
video_rate = [teacher[0],brother[0],girlfriend[0],yidan[0],wubei[0],waizui[0]]
data_view = ['老师','兄弟','女朋友','一旦','吾辈','歪嘴']
fig = plt.figure(figsize=(10,15))
plt.pie(video_rate,autopct='%1.2f%%') #画饼图(数据,数据对应的标签,百分数保留两位小数点)
plt.legend(
data_view,
fontsize=12,
title="区间",
loc="center left",
bbox_to_anchor=(1, 0.9))
plt.title("数量占比")
plt.show()
带有热刺标题的视频播放量饼图
video_rate = [teacher[1],brother[1],girlfriend[1],yidan[1],wubei[1],waizui[1]]
data_view = ['老师','兄弟','女朋友','一旦','吾辈','歪嘴']
fig = plt.figure(figsize=(10,15))
plt.pie(video_rate,autopct='%1.2f%%') #画饼图(数据,数据对应的标签,百分数保留两位小数点)
plt.legend(
data_view,
fontsize=12,
title="区间",
loc="center left",
bbox_to_anchor=(1, 0.9))
plt.title("播放量占比")
plt.show()
同理处理标签的
video_bijian = video_data[video_data['标签'].str.contains("必剪")]
bijian = [len(video_bijian),video_bijian['播放'].sum()]
video_fun = video_data[video_data['标签'].str.contains("恶作剧")]
fun = [len(video_fun),video_fun['播放'].sum()]
video_tc = video_data[video_data['标签'].str.contains("吐槽")]
tc = [len(video_tc),video_tc['播放'].sum()]
video_beauty = video_data[video_data['标签'].str.contains("美女")]
beauty = [len(video_beauty),video_beauty['播放'].sum()]
video_wezy = video_data[video_data['标签'].str.contains("万恶之源")]
wezy = [len(video_wezy),video_wezy['播放'].sum()]
video_show = video_data[video_data['标签'].str.contains("表演")]
show = [len(video_show),video_show['播放'].sum()]
video_tuwei = video_data[video_data['标签'].str.contains("土味")]
tuwei = [len(video_tuwei),video_tuwei['播放'].sum()]
video_rate = [bijian[0],fun[0],tc[0],beauty[0],wezy[0],show[0],tuwei[0]]
data_view = ['必剪','恶作剧','吐槽','美女','万恶之源','表演','土味']
fig = plt.figure(figsize=(10,15))
plt.pie(video_rate,autopct='%1.2f%%') #画饼图(数据,数据对应的标签,百分数保留两位小数点)
plt.legend(
data_view,
fontsize=12,
title="区间",
loc="center left",
bbox_to_anchor=(1, 0.9))
plt.title("数量占比")
plt.show()
video_rate = [bijian[1],fun[1],tc[1],beauty[1],wezy[1],show[1],tuwei[1]]
data_view = ['必剪','恶作剧','吐槽','美女','万恶之源','表演','土味']
fig = plt.figure(figsize=(10,15))
plt.pie(video_rate,autopct='%1.2f%%') #画饼图(数据,数据对应的标签,百分数保留两位小数点)
plt.legend(
data_view,
fontsize=12,
title="区间",
loc="center left",
bbox_to_anchor=(1, 0.9))
plt.title("数量占比")
plt.show()
对大于1万播放量的视频三连率等进行排序
# 对大于1万播放量的视频三连率等进行排序
video_rate = video_data[video_data['播放']>10000]
like_20=video_rate.sort_values(by=['点赞率'],ascending=False)[:20]
coin_20=video_rate.sort_values(by=['硬币率'],ascending=False)[:20]
sc_20=video_rate.sort_values(by=['收藏率'],ascending=False)[:20]
share_20=video_rate.sort_values(by=['转发率'],ascending=False)[:20]
danmu_20=video_rate.sort_values(by=['弹幕率'],ascending=False)[:20]
command_20=video_rate.sort_values(by=['评论率'],ascending=False)[:20]
like_20[['标题','播放','UP','点赞','点赞率']]
coin_20[['标题','播放','UP','硬币','硬币率']]
sc_20[['标题','播放','UP','收藏','收藏率']]
share_20[['标题','播放','UP','转发','转发率']]
danmu_20[['标题','播放','UP','弹幕','弹幕率']]
command_20[['标题','播放','UP','评论','评论率']]
结论
- 生活搞笑区的视频中,大部分视频的播放量都集中在10000以下,占了93.86%。
- 要想获得高播放,则有三条途径:粉丝数、视频质量、视频数量。
- 每个月大量上传视频获取高播放完全有可能。播放总和最高的两位UP,一个投了154个视频,一个投了528个。
- 弹幕和评论则是粉丝数多的UP占优势,粉丝黏性高。
- 八月投放视频最多的UP是老年人诱捕大队队长,一共投放了6932个视频。
- 视频主要集中在10:00-24:00投放,这个区间的播放总和也是最高。
- 八月热词主要是龙王,节日相关,哔哩哔哩活动以及相关的UP主。
- 哔哩哔哩相关活动热词视频播放量普遍较低,UP相关的和月度梗相关的播放量收益最好。
- 三连率、弹幕率、转发率、评论率对视频播放量的影响不大。