在简书发现一篇有趣的文章:爬虫,走起,用Excel实现5min抓取B站弹幕及初步处理
讲到了如何根据开发者工具,获得B站视频的弹幕信息,不过有个不足就是手动保存弹幕信息。而通过python我们可以轻松地自动存储。
很容易就猜想到弹幕的网址格式就是:
http://comment.bilibili.com/数字.xml ,我们可以尝试着修改一下数字,发现确实如此。
天啊,竟然有。那问题就太简单了。用正则表达式一下子就拿出来了:
danmu_id = re.findall(r'cid=(\d+)&', html)[0]
直接看下代码吧,没什么难的。
import requests, re
from bs4 import BeautifulSoup as BS
#打开网页函数
def open_url(url):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36'}
response = requests.get(url=url, headers=headers)
response.encoding = 'utf-8'
html = response.text
return html
#获取弹幕url中的数字id号
def get_danmu_id(html):
#用try防止有些av号没视频
try:
soup = BS(html, 'lxml')
#视频名
title = soup.select('div.v-title > h1')[0].get_text()
#投稿人
author = soup.select('meta[name="author"]')[0]['content']
#弹幕的网站代码
danmu_id = re.findall(r'cid=(\d+)&', html)[0]
print(title, author)
return danmu_id
except:
print('视频不见了哟')
return False
video_url ='http://www.bilibili.com/video/av10394940/'
video_html = open_url(video_url)
danmu_id = get_danmu_id(video_html)
if danmu_id:
danmu_url = 'http://comment.bilibili.com/{}.xml'.format(danmu_id)
danmu_html = open_url(url=danmu_url)
soup = BS(danmu_html, 'lxml')
all_d = soup.select('d')
for d in all_d:
#把d标签中P的各个属性分离开
danmu_list = d['p'].split(',')
#d.get_text()是弹幕内容
danmu_list.append(d.get_text())
print(danmu_list)
显示结果如下,可以再把这些个列表保存起来。
注意:
后来发现有一些B站网址还是不行, 比如埃罗芒阿老师这种网址,不是传统的av号形式(http://bangumi.bilibili.com/anime/5997/play#103921 )。
静态页面是实现不了,不过在开发者工具中还是能找到弹幕对应的数字号的,因此可以考虑之前说过的selenium+Chrome或PhantomJS的组合。
很好奇P中的各个数值都代表了什么,查了下。
前排占位置
p这个字段里面的内容:
0,1,25,16777215,1312863760,0,eff85771,42759017
中几个逗号分割的数据
第一个参数是弹幕出现的时间 以秒数为单位。
第二个参数是弹幕的模式1..3 滚动弹幕 4底端弹幕 5顶端弹幕 6.逆向弹幕 7精准定位 8高级弹幕
第三个参数是字号, 12非常小,16特小,18小,25中,36大,45很大,64特别大
第四个参数是字体的颜色 以HTML颜色的十进制为准
第五个参数是Unix格式的时间戳。基准时间为 1970-1-1 08:00:00
第六个参数是弹幕池 0普通池 1字幕池 2特殊池 【目前特殊池为高级弹幕专用】
第七个参数是发送者的ID,用于“屏蔽此弹幕的发送者”功能
第八个参数是弹幕在弹幕数据库中rowID 用于“历史弹幕”功能。
第一个参数,可以按照如下转换成时间(没找到对应的函数):
seconds = eval('1306.3900146484')
#商,余数 = divmod(被除数, 除数)
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
print ("%02d:%02d:%02d" % (h, m, s))
运行结果
00:21:46
-----------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
其中第四个参数,我们怎么知道具体的颜色是什么呢
比如输出反复出现的16777215是什么颜色。我们可以进行以下的转换:
十进制->十六进制->RGB颜色表示
十进制数16777215换成十六进制数FFFFFF,也就是255,255,255。白色!
知道方法后,我们可以在在线调色板(点我进入)就能知道任意数字的颜色了,比如那个41194(十六进制00A0EA)。
原来是蓝色呀。
-----------------------------------------------------------------------------------
第五个参数,我们可以把它转换下:
import time
print(time.ctime(1494238353))
输出结果:
Mon May 8 18:12:33 2017
后续更新了下程序,修正了某些网址不能用静态获取弹幕数字号的问题。
import requests, re, time, csv
from bs4 import BeautifulSoup as BS
from selenium import webdriver
#打开网页函数
def open_url(url):
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36'}
response = requests.get(url=url, headers=headers)
response.encoding = 'utf-8'
html = response.text
return html
#获取弹幕url中的数字id号
#当requests行不通时,采用selenium的方法。
def sele_get(url):
SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
driver = webdriver.PhantomJS(service_args = SERVICE_ARGS)
driver.get(url)
time.sleep(2)
danmu_id = re.findall(r'cid=(\d+)&', driver.page_source)[0]
return danmu_id
def get_danmu_id(html, url):
try:
soup = BS(html, 'lxml')
#视频名
title = soup.select('title[data-vue-meta="true"]')[0].get_text()
#投稿人
author = soup.select('meta[name="author"]')[0]['content']
#弹幕的网站代码
try:
danmu_id = re.findall(r'cid=(\d+)&', html)[0]
#danmu_id = re.findall(r'/(\d+)-1-64', html)[0]
#print(danmu_id)
except:
danmu_id = sele_get(url)
print(title, author)
return danmu_id
except:
print('视频不见了哟')
return False
#秒转换成时间
def sec2str(seconds):
seconds = eval(seconds)
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
time = "%02d:%02d:%02d" % (h, m, s)
return time
#csv保存函数
def csv_write(tablelist):
tableheader = ['出现时间', '弹幕模式', '字号', '颜色', '发送时间' ,'弹幕池', '发送者id', 'rowID', '弹幕内容']
with open('danmu.csv', 'w', newline='', errors='ignore') as f:
writer = csv.writer(f)
writer.writerow(tableheader)
for row in tablelist:
writer.writerow(row)
video_url ='http://www.bilibili.com/video/av5038338/'
video_html = open_url(video_url)
danmu_id = get_danmu_id(video_html, video_url)
all_list = []
if danmu_id:
danmu_url = 'http://comment.bilibili.com/{}.xml'.format(danmu_id)
danmu_html = open_url(url=danmu_url)
soup = BS(danmu_html, 'lxml')
all_d = soup.select('d')
for d in all_d:
#把d标签中P的各个属性分离开
danmu_list = d['p'].split(',')
#d.get_text()是弹幕内容
danmu_list.append(d.get_text())
danmu_list[0] = sec2str(danmu_list[0])
danmu_list[4] = time.ctime(eval(danmu_list[4]))
all_list.append(danmu_list)
print(danmu_list)
all_list.sort()
csv_write(all_list)