前段时间要写一个Python大作业,选题为B站弹幕数据分析,由于是Python新手,所以参考了以下的文档,再次感谢分享技术的人
同时也因为本次是只是本人记录一次初学Python期间的一次较有意思的大作业,语法简陋勿喷。参考文档:https://blog.csdn.net/weixin_34161029/article/details/91713988
B站弹幕数据分析,首先我们需要抓取到B站视频的弹幕数据,才能进行数据分析
选取分析的对象是B站UP主 观视频工作室 的**《睡前消息》** 系列视频中的最新15期,即 110-124期视频(2020-05-03 ~ 2020-06-05) 的弹幕作为本次分析的弹幕,爬取的日期从第110期发布的日期开始到爬取的时候**(2020-05-03 ~ 2020-06-09)**
首先需要从 观视频工作室 的个人主页处找到 睡前消息 频道,爬这里就可以获取到所需要的视频链接,页面是动态页面,所以这里选择用 selenium库 爬取链接,存进Link.txt文件备用
注意这里我用的是比较偷懒的爬取方法,只能爬到最新15期,要爬其他的可以自己改进一下算法
在视频页的console处可以输入window.cid来获取cid,但在selenium模拟中往浏览器console处输入的方法,搜索苦久无果,所以想出了如此下策:在视频页打开F12,在elements处搜索cid,发现是有的,这条语句是与网关有关的,通过bs4匹配到该语句,用正则匹配出来即可
正则前缀/ 后缀-1-
要爬取B站的弹幕,有两种方法 ( 据我所知 )
5.1 第一种方法 是https://comment.bilibili.com/{cid}.xml,这种方法可以获得最新的maxlimit数量(一般这个数量是3000)的弹幕,但是没办法获得历史弹幕
5.2 第二种方法 是https://api.bilibili.com/x/v2/dm/history?type=1&oid={cid}&date={yyyy-mm-dd},这种方法可以获取从某一天开始的maxlimit数量的弹幕,把想要获得的日期遍历一遍,虽然可能会漏掉一些弹幕(比如某一天弹幕数超过了maxlimit,就获取不到超出的部分),但也算是获取了大部分弹幕,比上一种方法好,采取这种方法
而第二个方法由于是api,所以需要用到cookie,可以用以下的方法来获取cookie
接下来就是分析这个xml文件中的弹幕数据,例子为如下:
<d p="369.34300,1,25,16777215,1590515762,0,a6fb6c47,33226011652915207">脱发</d>
这个 p字段 中的参数含义为如下:(百度搜索得到的答案)
第一个参数 369.34300 记作DM_time,是弹幕在视频中出现的时间,以秒数为单位。
第二个参数 1 记作DM_mode,是弹幕的模式1…3 滚动弹幕 4底端弹幕 5顶端弹幕 6.逆向弹幕 7精准定位 8高级弹幕
第三个参数 25 记作DM_font,是字号, 12非常小,16特小,18小,25中,36大,45很大,64特别大
第四个参数 16777215 记作DM_color,是字体的颜色以HTML颜色的十进制为准
第五个参数 1590515762 记作DM_realTime,是发送弹幕的时间戳
第六个参数 0 记作DM_pool,是弹幕池 0普通池 1字幕池 2特殊池(高级弹幕)
第七个参数 a6fb6c47 记作DM_userID,是发送者的ID,用于“屏蔽此弹幕的发送者”功能
第八个参数 33226011652915207 记作DM_id,是弹幕在弹幕数据库中rowID,也就是这条弹幕是历史总弹幕的第几条
p字段以外的 弹幕本体 记作DM_text
把爬取到的弹幕分别放到多个csv文件中,每一期一个csv文件,方便后面的数据分析
至此爬取数据部分完成
在爬取并保存了所有弹幕数据之后,先进行去重,可对比还未去重时文件的大小和去重后文件的大小
1.1 未去重:(至于110-112和113-124日期不一样,是因为抓113-124的时候被封了IP,只能等到第二天早上抓110-112)
1.2 去重后:
然后进行如下的可视化分析:
2.1 每一期弹幕总数的变化 折线图
结合上图,我们发现每期的弹幕总数统计波动有些大,其中一些弹幕数,比如116期,甚至在这里统计只有不到5000条,但是在B站上可以看到:
这一期其实总弹幕是有1.4W的,统计到的弹幕数仅占1/3左右,出现这种情况是由于 在视频发出的某一天里,大多数用户发过了弹幕,而这部分弹幕超出了B站统计的maxlimit ,导致我们没办法统计到这部分弹幕。
相对而言,弹幕数多的视频就是弹幕不仅仅在某一天大量被发出,比如115期,这里我们统计到了有**9000+**的弹幕数,而B站上的数据:
统计到的弹幕大约占70%,这是由于并没有很多用户在某一个日期大量发出弹幕,导致弹幕库溢出,也从某方面说明了该视频的受众时间较广,是一个较为优质的视频。
另外,加上我去把每期的标题和弹幕数一对应,发现,涉及到 国与国之间的时评 的时候,在播放量相差不多的情况下,视频的受众时间会比较广,相对的,谈论的时事范围或格局越小,受众时间会相对应减小。当然这些都是通过一小部分数据得出的结论,不能代表完全的趋势就是这样。
2.2 统计发弹幕总数TOP10 的用户 横向柱状图
一共15期的节目,根据不完全统计,这位ID为 13631380 的用户一共发了160+条弹幕,平均每一期要发11条弹幕,是真爱粉无误了。
2.3 统计每期弹幕密度变化图 (图太多,只放了总体的缩略图)
首先从个体上观察这些弹幕密度图,发现弹幕都是较为均匀的,但从总体上观察这些弹幕密度图的时候,发现其中112-118期的弹幕密度图极其相似,119-124期的弹幕密度图也是极其相似,总的来说,112-124期都会有一个密度大且会持续一小段时间的“弹幕风云”,更巧的是,这部分都集中在视频时间700秒左右,也就是11分-13分之间会有一波弹幕的小高潮。
为此我特意去再次观看**《睡前消息》,发现在112-118期中,节目大概都在15~20分钟左右,而在11-13分钟的时候,也就是在视频时间过去大概70%**左右的时候,主持人都会作出一些毒辣的点评,从而引起弹幕的激烈讨论。
2.4 绘制出每期视频的弹幕词云 (取其中几个词云图展示)
由于我设置了最小字体为10,所以如果是弹幕种类分布得比较广泛的视频,留白处会比较多,这里我选取了较为饱满的弹幕词云图作为展示
可见弹幕里面的 同意 支持 保护 之类的字眼比较多,**仅观表象的话 **可以说明,虽然该节目主持人虽嘴巴毒辣,在视频弹幕里会造成不少的人激烈讨论,但还是有大部分人同意这些观点的。
项目目录结构
GetUrl.py
from bs4 import BeautifulSoup
from selenium import webdriver
url = 'https://space.bilibili.com/54992199/channel/detail?cid=82529'
print("开始爬取《睡前消息》第110-124期视频地址")
# chrome驱动,需要放在Python安装的目录下
driver = webdriver.Chrome(r"E:\Python\chromedriver.exe")
driver.get(url)
data = driver.page_source
soup = BeautifulSoup(data, 'lxml')
count=1
res = []
all = soup.find_all('li', attrs={'class': 'small-item fakeDanmu-item'})
for li in all:
if count<=15:
a=li.find('a',attrs={'class':'cover cover-normal'})
res.append('https:'+a.get("href"))
count+=1
else:
break
with open('Urls/Link.txt', 'w') as f:
for link in res:
f.write(link+'\n')
print("已将全部链接放入到Link.txt文件中")
GetCid.py
from bs4 import BeautifulSoup
from selenium import webdriver
import re
import time
cids=[]
Urls=[]
with open('Urls/Link.txt', 'r') as f:
for line in f.readlines():
Urls.append(line.strip())
print("开始爬取《睡前消息》第110-124期视频的Cid")
# chrome驱动,需要放在Python安装的目录下
driver = webdriver.Chrome(r"E:\Python\chromedriver.exe")
for url in Urls:
driver.get(url)
data = driver.page_source
soup = BeautifulSoup(data, 'lxml')
all = soup.find_all('script')
for a in all:
if str(a).startswith("<script>window."):
res=a
links=re.split(r'\:',str(res))
for url in links:
# 这个链接前面是域名,中国的都是以cn开头
if url.startswith("//cn"):
link=url
break
cid=re.findall(".*/(.*)-1-.*", link)
# 获取到视频的cid,存进数组然后一起存进Cid.txt文件中
cid=cid[0]
# 处理特殊情况下,长度不符合cid,去除尾部部分
if len(cid)>9:
length=len(cid)
a=length-9
cid=cid[:-a]
cids.append(cid)
# 每抓完一个网页休眠5秒
time.sleep(5)
with open('Urls/Cid.txt', 'w') as f:
for id in cids:
f.write(id + '\n')
print("已将全部视频的Cid放入到Cid.txt文件中")
GetBulletChat.py
from bs4 import BeautifulSoup
import time
import pandas as pd
import requests
import datetime
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 QIHU 360EE",
"Connection": "keep-alive",
# 这个cookie的获取方法在文档中已说明
"Cookie":""
}
sets=124 # 最新一期的数字
dates=[] # 日期数组,用于填充url
# 遍历日期 包括begin和end的日期 生成类似2020-05-03的格式的日期
begin = datetime.date(2020,5,3)
end = datetime.date(2020,6,9)
d = begin
delta = datetime.timedelta(days=1)
while d <= end:
dates.append(str(d.strftime("%Y-%m-%d")))
d += delta
Cids=[] # Cid数组,用于填充url
with open('Urls/Cid.txt', 'r') as f:
for line in f.readlines():
Cids.append(line.strip())
for cid in Cids:
# 每次都要重置这些数据
dm_data = [] # 弹幕数据
dm_text = [] # 弹幕本体
# 弹幕的八个参数和弹幕本体
DM_time = []
DM_mode = []
DM_font = []
DM_color = []
DM_realTime = []
DM_pool = []
DM_userID = []
DM_id = []
DM_text = []
print("正在爬取第" + str(sets) + "期的《睡前消息》弹幕...")
for date in dates:
url="https://api.bilibili.com/x/v2/dm/history?type=1&oid="+cid+"&date="+date
html=requests.get(url=url,headers=headers) #返回文本信息
html.encoding='utf8'
soup=BeautifulSoup(html.text,'lxml') #建立soup对象
all=soup.find_all("d")
for d in all:
# 弹幕数据
dm_data.append(str(d.get("p")).split(","))
# 弹幕本体
dm_text.append(d.get_text())
# 分别把数据存进这几个数组
for i in dm_data:
DM_time.append(i[0])
DM_mode.append(i[1])
DM_font.append(i[2])
DM_color.append(i[3])
DM_realTime.append(i[4])
DM_pool.append(i[5])
DM_userID.append(i[6])
DM_id.append(i[7])
for i in dm_text:
DM_text.append(i)
dt={"DM_time":DM_time,"DM_mode":DM_mode,"DM_font":DM_font,"DM_color":DM_color,
"DM_realTime":DM_realTime,"DM_pool":DM_pool,"DM_userID":DM_userID,"DM_id":DM_id,"DM_text":DM_text}
d=pd.DataFrame(dt)
d.to_csv('./Danmu/Danmu-'+str(sets)+'.csv',encoding='utf-8-sig') #存储弹幕信息
print("已将弹幕放入到Danmu-"+str(sets)+".csv文件中")
sets-=1
# 每抓完一个网页休眠7秒
print("缓冲中...")
time.sleep(7)
print("已将《睡前消息》第110-124期的弹幕爬取完毕")
DataAnalysis.py
import matplotlib.pyplot as plt
import matplotlib
import pandas as pd
import os
from wordcloud import WordCloud
import jieba
file_dir="./Danmu/"
# 获取文件名
files=[files for root,dirs,files in os.walk(file_dir)]
# 去重
def duplicate(files):
for file in files:
df = pd.read_csv(file_dir + file,encoding="utf-8-sig",index_col=0)
data = df.drop_duplicates(subset=['DM_id'], keep='first')
data.to_csv(file_dir + file,encoding='utf-8-sig',index=True,index_label="")
print("去重完毕")
# 每一期弹幕总数的变化折线图
def danmuSumPlot(files):
print("弹幕总数变化图绘制中...")
list1 = ['110','111','112','113','114','115','116','117','118','119','120','121','122','123','124']
data_sum=[]
for file in files:
data = pd.read_csv(file_dir + file,encoding="utf-8-sig",index_col=0)
data_sum.append(len(data))
matplotlib.rcParams["font.family"] = "SimHei"
plt.plot(list1, data_sum, "c")
plt.ylabel("弹幕数")
plt.xlabel("《睡前消息》期数")
plt.title("每一期弹幕总数的变化图")
plt.savefig('./Analysis/弹幕总数变化图', dpi=600)
plt.show()
print("绘制完毕")
# 发弹幕总数TOP10的用户柱状图
def danmuUserTopBarh(files):
print("弹幕TOP10用户图绘制中...")
datas=[]
for file in files:
datas.append(pd.read_csv(file_dir + file,encoding="utf-8-sig",index_col=0))
# 先合并全部csv文件,再进行统计
data=pd.concat(datas)
data = data.groupby('DM_userID').size().reset_index(name="count")
data = data.sort_values("count", ascending=False)
label = [] # y轴的值
width = [] # 给出具体每个直方图的数值
i = 0
for item in data.values:
if i < 10:
label.append(item[0])
width.append(item[1])
i += 1
else:
break
matplotlib.rcParams["font.family"] = "SimHei"
y = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] # 给出在y轴上的位置
plt.barh(y=y, width=width, tick_label=label) # 绘制水平直方图
plt.ylabel("用户ID")
plt.xlabel("弹幕数")
plt.title("发弹幕总数TOP10的用户柱状图")
plt.subplots_adjust(left=0.17) # 控制图片左边的间隔 避免显示不全
plt.savefig('./Analysis/TOP10', dpi=600, left=0.17)
print("绘制完毕")
# 每期弹幕密度变化图
def danmuDensityChange(files):
print("弹幕密度变化图绘制中...")
sets=110
for file in files:
data = pd.read_csv(file_dir + file, encoding="utf-8-sig", index_col=0)
data = data.sort_values("DM_time")
# 先对弹幕发送时间进行取整
data['DM_time'] = [int(item) for item in data.DM_time]
data = data.groupby('DM_time').size().reset_index(name="counted")
list2 = [item for item in data.DM_time]
data_sum = [item for item in data.counted]
matplotlib.rcParams["font.family"] = "SimHei"
plt.plot(list2, data_sum, "c")
plt.ylabel("弹幕数量")
plt.xlabel("视频时间轴/(秒)")
plt.title(str(sets)+"期弹幕密度变化图")
plt.savefig("./Analysis/弹幕密度变化/"+str(sets)+'期弹幕密度变化图', dpi=600)
sets+=1
print("绘制完毕")
# 每期的弹幕词云
def danmuWordCloud(files):
print("弹幕词云绘制中...")
sets = 110
for file in files:
data = pd.read_csv(file_dir + file, encoding="utf-8-sig", index_col=0)
# 先把全部弹幕信息写成一个字符串,再调用方法
words = ''
for item in data.DM_text:
words += item
words=" ".join(jieba.cut(words))
# 这个scale参数是画布大小参数,也就是调整分辨率的,10代表是原来的10倍大小,越高分辨率越高
wd = WordCloud(font_path='simhei.ttf', max_words=40, background_color='white',min_font_size=10,scale=10).generate(words)
plt.imshow(wd)
plt.axis("off")
wd.to_file("./Analysis/词云/第" + str(sets) + "期词云.jpg")
sets+=1
print("绘制完毕")
if __name__ == '__main__':
# 去重
duplicate(files[0])
# 每一期弹幕总数的变化折线图
danmuSumPlot(files[0])
# 发弹幕总数TOP10的用户柱状图
danmuUserTopBarh(files[0])
# 每期弹幕密度变化图
danmuDensityChange(files[0])
# 每期的弹幕词云
danmuWordCloud(files[0])
本文地址:https://blog.csdn.net/lkx_icy/article/details/107178025
如对本文有疑问, 点击进行留言回复!!
ubuntu环境下利用Wand将pdf转jpg, python代码
荐 Python可视化matplotlib13-iris鸢尾花数据集|histogram直方图
网友评论