0%

按日期爬取b站弹幕二

上次的b站爬取需要手动输入日期等信息,这次直接在程序内根据时间差进行爬取,直接放上完整代码。

代码

上次的分析链接在此:b站弹幕爬取分析
直接附上完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145

import requests
from lxml import etree
import pandas as pd
from wordcloud import WordCloud
import jieba
import datetime


class BarrageSpider:
def __init__(self, bv):
# 需要一个bv号,在接下来的代码中进行替换操作
self.bv = bv
self.video_name = None
# 不需要登录的弹幕接口地址 只能爬取部分弹幕
self.barrage_url = 'https://comment.bilibili.com/{}.xml'
# 需要登陆的弹幕接口地址 根据日期进行分类 需要循环爬取 最后归总数据
self.date_url = 'https://api.bilibili.com/x/v2/dm/history?type=1&oid={}&date={}' # 2021-01-01
# 点击按钮弹出日历的数据接口,这里我们用来作索引
self.index_url = 'https://api.bilibili.com/x/v2/dm/history/index?type=1&oid={}&month={}' # 2021-01
# 在抓包工具中找的一个简洁的请求,里面有我们需要的oid或者是cid
self.bv_url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + bv + '&jsonp=jsonp'
# 视频时间获取
self.video_url = 'https://www.bilibili.com/video/{}'.format(bv)
# 不需要登录接口的伪装头
self.comment = {
'referer': 'https://www.bilibili.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66 '
}
# 需要登录的伪装头 因为需要登录 ip代理已经没有意义了 这里就不再使用IP代理
self.date_headers = {
"referer": "https://www.bilibili.com/",
"origin": "https://www.bilibili.com",
"cookie": "你的cookie 爬很久远的视频 会被封ip 后面接收到的都是空结果",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66 "
}

# 从接口返回的json中获取到我们的cid 注: cid = oid
def get_cid(self):
# 定位到数据data中下面的cid
return requests.get(url=self.bv_url, headers=self.comment).json()['data'][0]['cid']

def get_video_time(self):
time_data = requests.get(url=self.video_url, headers=self.comment).text
video_page = etree.HTML(time_data)
v_time = video_page.xpath('//div[@class="video-data"]/span[3]/text()')[0].split(' ')[0]
self.video_name = video_page.xpath('//h1[@class="video-title"]/span/text()')[0]
return v_time

# 解析不需要登录的接口 返回类型是xml文件
def parse_url(self):
# 获取指定视频的cid/oid
cid = self.get_cid()
# 对页面进行伪装请求,这里注意不要转换成text,使用二进制
response = requests.get(url=self.barrage_url.format(cid), headers=self.comment).content
# etree解析
data = etree.HTML(response)
# 定位到所有的d元素
barrage_list = data.xpath('//d')
for barrage in barrage_list:
# 获取d元素的p属性值
info = barrage.xpath('./@p')[0].split(',')
# 获取弹幕内容
content = barrage.xpath('./text()')[0]
item = {'出现时间': info[0], '弹幕模式': info[1], '字体大小': info[2], '颜色': info[3], '发送时间': info[4], '弹幕池': info[5],
'用户ID': info[6], 'rowID': info[7], '内容': content}
# 因为这只是一部分弹幕 所以就没有进行持久化存储 没有必要
print(item)

# 循环爬取所有弹幕 需要传入month的数据 根据视频发布的日期到现在的所有月份
def parse_date_url(self, month):
print('正在爬取{}月份的数据'.format(month))
# 存放爬到的数据
result = []
# 获取视频的oid
oid = self.get_cid()
# 获取日期索引
date_by_month = requests.get(url=self.index_url.format(oid, month), headers=self.date_headers).json().get(
'data')
# 根据日期索引循环请求
if date_by_month:
for day in date_by_month:
print('{}月份数据下的{}'.format(month, day))
# 注意还是二进制文件
date_page = requests.get(url=self.date_url.format(oid, day), headers=self.date_headers).content
date_data = etree.HTML(date_page)
# 解析到到所有的d元素
barrage_list = date_data.xpath('//d')
# 循环解析数据
for barrage in barrage_list:
# 获取d元素的p属性值
things = barrage.xpath('./@p')[0].split(',')
# 获取弹幕内容 并去掉所有空格
content = barrage.xpath('./text()')[0].replace(" ", "")
item = {'出现时间': things[0], '弹幕模式': things[1], '字体大小': things[2], '颜色': things[3], '发送时间': things[4],
'弹幕池': things[5],
'用户ID': things[6], 'rowID': things[7], '内容': content}
result.append(item)
# 返回封装好的数据
return result

# 根据现在的时间遍历所有的月份信息
def parse_month(self):
start_day = datetime.datetime.strptime(self.get_video_time(), '%Y-%m-%d')
end_day = datetime.date.today()
months = (end_day.year - start_day.year) * 12 + end_day.month - start_day.month
m_list = []
for mon in range(start_day.month - 1, start_day.month + months):
if (mon % 12 + 1) < 10:
m_list.append('{}-0{}'.format(start_day.year + mon // 12, mon % 12 + 1))
else:
m_list.append('{}-{}'.format(start_day.year + mon // 12, mon % 12 + 1))
return m_list

# 舍友指导下的一行代码生成词云 编译器自动格式化了 本质还是一行代码
def wordCloud(self):
WordCloud(font_path="C:/Windows/Fonts/simfang.ttf", background_color='white', scale=16).generate(" ".join(
[c for c in jieba.cut("".join(str((pd.read_csv('{}弹幕池数据集.csv'.format(self.video_name))['内容']).tolist()))) if
len(c) > 1])).to_file(
"{}词云.png".format(self.video_name))


if __name__ == '__main__':
# 输入指定的视频bv号
bv_id = input('输入视频对应的bv号:')
# new一个对象
spider = BarrageSpider(bv_id)
spider.parse_month()
# 请求今年1月和去年12月的数据 并合并数据
word_data = []
months = spider.parse_month()
# 循环遍历爬取
for month in months:
word = spider.parse_date_url(month)
word_data.extend(word)
# 数据格式化处理 并输出csv格式文件
data = pd.DataFrame(word_data)
data.drop_duplicates(subset=['rowID'], keep='first')
# 字符集编码需要为utf-8-sig 不然会乱码
data.to_csv('{}弹幕池数据集.csv'.format(spider.video_name), index=False, encoding='utf-8-sig')
# # 生成词云
spider.wordCloud()

运行结果




这里只有十八万行…是因为刚开始测试的时候忘记在月份前加0。代码里已经更正。
更正后 爬取了骚猪的视频:

视频上显示16万弹幕,实际则有67万,而且爬到19年后,获取的都为空数据,预测实际弹幕有100万左右。

可以通过多账号的方式爬取完整弹幕,这里就不做了。

本文标题:按日期爬取b站弹幕二

文章作者:fanchen

发布时间:2021年01月16日 - 16:46:46

最后更新:2021年02月05日 - 17:56:26

原始链接:http://88fanchen.github.io/posts/24b45e76/

许可协议:署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。