首先要知道我们要做什么,要是茫无目的那就说明都做不了,我们这次的数据分析
步骤:
① 获取视频url
② 获取视频cid
③ 获取视频弹幕
第一步:获取视频url
解析我在弹幕里直接写了,可以自己看一看,我就不在博客里写了,不好排版
Get_Url代码部分
from bs4 import BeautifulSoup
from selenium import webdriver
url = 'https://space.bilibili.com/517327498/channel/seriesdetail?sid=60119'
print("------------开始罗翔说评论地址------------") # 2021-11-6 edg VS DK
# chrome驱动,需要放在Python安装的目录下
driver = webdriver.Chrome(r"C:\Users\12430\AppData\Local\Programs\Python\Python39\chromedriver.exe")
driver.get(url)
data = driver.page_source # 获取到页面信息
# print(data)
soup = BeautifulSoup(data, 'lxml') # 我们可以利用他解析HTML代码,并且在解析HTML代码的时候,如果
# HTML代码不规范或者不完整,lxml解析器会自动修复或补全代码,从而提高效率
# print(soup) # 不信你查看一下
count = 1
res = []
all = soup.find_all('li', attrs={'class': 'small-item fakeDanmu-item'}) # 找到这个专辑里的视频,只要是
# B站的专辑都在这个class里
print("all: ", end="")
print(all)
# print(all) # 不信你看一下
for li in all:
if count <= 15: # 罗翔的这个专辑视频就15个,所以爬15次
a = li.find('a', attrs={'class': 'cover cover-normal'})
print("这是a: ", end="")
print(a)
res.append('https:' + a.get("href")) # href的格式是//www.bilibili.com/video/BV1k44y1g7Dp这样
count += 1
else:
break
print(res)
with open('Urls/链接.txt', 'w') as f: # 将结果放入这个文件里
for link in res:
f.write(link + '\n')
print("已将全部链接放入到链接.txt文件中")
第二步:获取视频cid
① 首先我们要定位到这个cid到第放在了什么鬼地方,发现在下面这个位置
在script中,我们发现cid好出现在了这个window.__playinfo__中,所以我们要想办法获取他它,这边有一个办法就是startswith()这个方法
② 知道cid位置以后就想这么获取它就行了
首先用startswith()获取链接,不知道为什么罗翔老师的视频不是//cn开头而是//upos(图一),
通过正则表达式的方法获取到cid的部分(图二)。
然后通过一系列的处理获取前9个数字,至此获取cid成功
Get_Cid 代码部分
from bs4 import BeautifulSoup
from selenium import webdriver
import re
import time
cids = []
Urls = []
cid_start = 15
# sets_end = len(open('Urls/Link.txt', 'r').readlines()) + sets_start - 1
anime_name = "《罗翔读评论》"
with open('Urls/Link.txt', 'r') as f:
for line in f.readlines():
Urls.append(line.strip())
print("开始爬取动漫" + anime_name + "所有视频的Cid")
# chrome驱动,需要放在Python安装的目录下
driver = webdriver.Chrome(r"C:\Users\12430\AppData\Local\Programs\Python\Python39\chromedriver.exe")
link = '' # 需要先定义,后面查找url的时候可能会应为找不到url而导致link未定义
cid = ''
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.__playinfo__"): # 必须这样否则找不到
res = a
# print(res)
links = re.split(r':', str(res))
# print("-----------------------")
# print(links)
for url in links:
# 这个链接前面是域名,中国的都是以cn开头,但是现在好像不是了
if url.startswith("//"): # 不知道为什么有些视频不是//cn开头的,所以用//来实现
print(url)
print("-----------------------")
link = url
break
cid = re.findall(".*/(.*)-1-.*", link)
# 获取到视频的cid,存进数组然后一起存进Cid.txt文件中
cid = cid[0]
# 处理特殊情况下,长度不符合cid,去除尾部部分
# 需要根据Cid进行特殊处理
if len(cid) > 9:
length = len(cid)
a = length - 9
cid = cid[:-a]
# print(cid)
cids.append(cid)
# 每抓完一个网页休眠5秒
time.sleep(5)
print(anime_name + "第" + str(cid_start) + "集Cid爬取完毕")
cid_start -= 1
print(cid)
with open('Urls/Cid.txt', 'w') as f:
for id in cids:
f.write(id + '\n')
print("已将全部视频的Cid放入到Cid.txt文件中")
第三步:获取视频的弹幕
① 首先我们需要一个headers 获取 用户代理 和 Cookie,这个自己用F12找找就行,
然后开始寻找视频的时间,首先我们发现罗翔老师读评论的第一期和最后一期好像是2020/7/14和
2022/4/29来着,那么我们就设置2020/7/13到2022/4/30来寻找视频日期
② 开始获取弹幕数据,这边就不用图片了,代码太长了
第一步,首先要知道<d></d>这个标签里的这么多数字是什么
第一个参数 17.57900 记作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颜色的十进制为准
第五个参数 1653783652 记作DM_realTime,是发送弹幕的时间戳
第六个参数 0 记作DM_pool,是弹幕池 0普通池 1字幕池 2特殊池(高级弹幕)
第七个参数 def8eb39记作DM_userID,是发送者的ID,用于“屏蔽此弹幕的发送者”功能
第八个参数 1062823650710860544 记作DM_id,是弹幕在弹幕数据库中rowID,也就是这条弹幕是历史总弹幕的第几条
第九个参数 11 这个玩意好像没什么用?
弹幕本体 记作DM_text
Get_BulletChat部分
import re
import jieba
from bs4 import BeautifulSoup
import time
import pandas as pd
import requests
import datetime
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/102.0.5005.63 Safari/537.36",
"Connection": "keep-alive",
# 这个cookie的获取方法在文档中已说明
"Cookie": "buvid3=F2278A54-EB90-05CA-6AC4-25606AB151A009225infoc; CURRENT_FNVAL=4048; "
"b_lsid=10C7E85F4_1812E57B9D8; _uuid=1CCF3FED-9CA9-4996-69104-410F55A4243FE10121infoc; "
"buvid4=AD4080D0-B5E7-8064-1B21-3F1AFE743B2510754-022060418-h0Kdr2RwgtcfH98SEPYQ1Q%3D%3D; "
"buvid_fp=16eaee4e45e32b194417c0880035c419; CURRENT_BLACKGAP=0; blackside_state=0; sid=7hhsqlnl; "
"rpdid=|(J|)Yllu|k~0J'uYlRJJmmuY; "
"b_timer=%7B%22ffp%22%3A%7B%22333.788.fp.risk_F2278A54%22%3A%221812E57C17E%22%2C%22333.999.fp"
".risk_F2278A54%22%3A%221812E5BBF35%22%7D%7D "
}
sets = 15 # 最新一期的数字
anime_name = "罗翔读评论"
dates = [] # 日期数组,用于填充url
# 遍历日期 包括begin和end的日期 生成类似2020-04-29的格式的日期
begin = datetime.date(2020, 7, 13)
end = datetime.date(2022, 4, 30)
# 需要一天一天找,所以比较慢
d = begin
delta = datetime.timedelta(days=1) # 一天一天找
while d <= end:
dates.append(str(d.strftime("%Y-%m-%d")))
d += delta
# print(dates)
#
Cids = [] # Cid数组,用于填充url
with open('Urls/Cid.txt', 'r') as f:
for line in f.readlines():
Cids.append(line.strip())
print(Cids) # 15--1
for cid in Cids:
print("正在爬取第" + str(sets) + "期的" + anime_name + "弹幕...")
# 每次都要重置这些数据
dm_data = [] # 弹幕数据
dm_text = [] # 弹幕本体
# 弹幕的八个参数和弹幕本体
DM_time = [] # 弹幕时间
DM_mode = [] # 弹幕模式
DM_font = [] # 弹幕类型
DM_color = [] # 弹幕颜色
DM_realTime = [] # 弹幕时间戳
DM_pool = [] # 弹幕池
DM_userID = [] # 弹幕发送者ID
DM_id = [] # 弹幕是弹幕数据库中的第几条
DM_text = []
print("正在爬取第" + str(sets) + "期的弹幕...")
for date in dates:
url = 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid
response = requests.get(url=url, headers=headers) # 返回文本信息
response.encoding = 'utf-8'
soup = BeautifulSoup(response.text, 'lxml') # 建立soup对象
all = soup.find_all("d") # d是弹幕标签
# print(all)
for d in all:
# 弹幕数据
dm_data.append(str(d.get("p")).split(",")) # p是标签
# 弹幕本体
dm_text.append(d.get_text())
print("第" + str(sets) + "集" + " " + str(date) + "数据爬取完毕!")
# 分别把数据存进这几个数组
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)
# 利用pandas进行csv文件的写入
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') # 存储弹幕信息
print("已将弹幕放入到Danmu-" + str(sets) + ".csv文件中")
sets -= 1
# 每抓完一个网页休眠7秒
print("缓冲中...")
time.sleep(5)
print("已将罗翔读评论的第①期到第①⑤期的弹幕爬取完毕")
第四步,开始作图
主要功能:
import matplotlib.pyplot as plt
import matplotlib
import pandas as pd
import os
from wordcloud import WordCloud
import jieba
anime_name = "罗翔读评论"
file_dir = "./Danmu/"
# 获取文件名
files = [files for root, dirs, files in os.walk(file_dir)]
print(files)
# 去重
def duplicate(files):
for file in files:
print(file)
data_df = pd.DataFrame()
df = pd.read_csv(file_dir + file, encoding="utf-8-sig", index_col=0, engine='python')
df = pd.concat([data_df, df])
# 开始去重
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 = ['1', '2', '3', '4',
'5', '6', '7', '8',
'9', '10', '11', '12',
'13', '14', '15']
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, "m", ':')
plt.ylabel("弹幕数量")
plt.xlabel("《罗翔读评论》期数")
plt.title("每一期弹幕总数的变化图")
plt.savefig('./Analysis/弹幕分析图片/弹幕总数变化图', dpi=800)
plt.show()
print("绘制完毕")
# 发弹幕总数TOP10的用户柱状图
def danmuUserTopBarh(files):
print("弹幕TOP20用户图绘制中...")
datas = []
for file in files:
datas.append(pd.read_csv(file_dir + file, encoding="utf-8", 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 < 20:
label.append(item[0])
width.append(item[1])
i += 1
else:
break
matplotlib.rcParams["font.family"] = "SimHei"
y = [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 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("发弹幕总数TOP20的用户柱状图")
plt.subplots_adjust(left=0.22) # 控制图片左边的间隔 避免显示不全
plt.savefig('./Analysis/弹幕分析图片/TOP20柱状图', dpi=600)
print("绘制完毕")
# 每期弹幕密度变化图
def danmuDensityChange(files):
print("弹幕密度变化图绘制中...")
sets = 1
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("绘制完毕")
# 每期的弹幕词云,词云已经用jieba库进行去词了
def danmuWordCloud(files):
print("弹幕词云绘制中...")
sets = 1
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=80,
background_color='white',
min_font_size=5,
width=1920,
height=1080,
scale=10).generate(words)
plt.imshow(wd)
plt.axis("off")
wd.to_file("./Analysis/词云/第" + str(sets) + "期词云.jpg")
sets += 1
print("绘制完毕")
# 每期的弹幕词云(jieba去词以后-带图片背景)
def danmuWordCloud_Img(files):
print("弹幕词云绘制中...")
# df = pd.DataFrame()
# jieba.load_userdict("./Tools/" + anime_name + "词汇.txt")
sets = 1
sets_s = 1
for file in files:
data = pd.read_csv(file_dir + file, encoding="utf-8", index_col=0)
# 先把全部弹幕信息写成一个字符串,再调用方法
words = ''
for item in data.DM_text:
words += str(item)
# print(words)
with open('./自定义文件/stopwords.txt', 'r+', encoding='utf-8') as fp:
stopwords = fp.read().split('\n') # 将停用词词典的每一行停用词作为列表中的一个元素
word_list = [] # 用于存储过滤停用词后的分词结果'
seg_list = jieba.cut(words)
for seg in seg_list:
if seg not in stopwords:
word_list.append(seg)
words = " ".join(word_list)
mask = plt.imread(
'./Analysis/maskImages/' + anime_name + '.jpg') # 读取图片作为词云图轮廓
# if sets == 15:
# sets = 1
# 这个scale参数是画布大小参数,也就是调整分辨率的,10代表是原来的10倍大小,越高分辨率越高
wd = WordCloud(font_path='simhei.ttf',
max_words=800,
background_color='white',
min_font_size=1,
# width=1920,
# height=1080,
mask=mask,
scale=5
).generate(words)
plt.imshow(wd)
plt.axis("off")
wd.to_file("./Analysis/带形状词云/" + anime_name + "第" + str(sets_s) + "集词云_Image.jpg")
# sets += 1
sets_s += 1
print("绘制完毕")
# 弹幕颜色直方图
def DM_color(files):
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
for file in files:
data_df = pd.DataFrame()
data = pd.read_csv(file_dir + file) # 读取数据文件
data = pd.concat([data_df, data]) # 拼接数据
data_color = data['DM_color'].value_counts() # 统计用户发送的弹幕使用同一种颜色的数量
data_color = data_color.head(7) # 选出前七种颜色
favorite_color = [] # 弹幕颜色的十六进制颜色码
# 将爬取到的十进制颜色码转换为十六进制颜色码
for a_color in data_color.index:
temp = hex(a_color)
temp = '#' + temp[2:].upper()
while len(temp) < 7:
temp = temp[0] + '0' + temp[1:]
favorite_color.append(temp)
fig, ax = plt.subplots()
# 画柱状图,颜色为使用较多的弹幕颜色
plt.bar([1, 2, 3, 4, 5, 6, 7], data_color.values, color=favorite_color)
plt.title(anime_name + '弹幕颜色使用数量前七名')
plt.xlabel('排名')
plt.ylabel('使用该颜色的弹幕数量')
# plt.show()
fig.savefig(r'./Analysis/弹幕分析图片/' + anime_name + 'Color.png', transparent=True) # 保存
if __name__ == '__main__':
# # 去重
duplicate(files[0])
# 每一期弹幕总数的变化折线图
danmuSumPlot(files[0])
# 发弹幕总数TOP20的用户柱状图
danmuUserTopBarh(files[0])
# 弹幕颜色直方图
DM_color(files[0])
# 每期弹幕密度变化图
danmuDensityChange(files[0])
# 每期的弹幕词云
danmuWordCloud(files[0])
# 每期的弹幕词云(jieba去词以后-带图片背景)
danmuWordCloud_Img(files[0])
这边就贴一张结果图片当实例吧
最后鸣谢这篇文章对我的帮助:(1条消息) python大作业——B站弹幕数据爬取与分析_lkx_icy的博客-CSDN博客_python爬取b站弹幕并进行数据分析