文章目錄
- 前言
- 一、頁面分析
- 二、selenium安裝與使用
- 1.安裝庫
- 2.下載驅動
- 3.模擬瀏覽器發出請求并捕獲彈幕
- 三、所有代碼
- 1.匯入庫
- 2.創建一個BiliLive類并寫上構造方法
- 3.創建兩個get方法,分別來獲得htnl頁面和里面的彈幕
- 4. 把新的彈幕串列更新到總體的串列中
- 5.保存彈幕
- 6.跑起來
- 四、(更新)眼瞎作者找到api了
- (更新)附上代碼
- 運行結果
- 五、總結
前言
上次咱們爬了bilibili的熱門視頻,今天整點花活,爬一下彈幕
但是爬視頻彈幕有很多人都做過了,所以想整一下爬直播的彈幕
話不多說,走起!
一、頁面分析
還是老樣子,要想獲取彈幕,先要抓包來捕獲介面
隨便打開個直播,然后按F12進入開發者模式

可以發現有一堆請求,但是箭頭指的這兩個應該是視頻推流,跟彈幕關系不大
為了排除干擾,把直播給暫停了試試

結果是 空空如也
然后我又試了試用Fiddler抓包,也是一樣的情況,啥都沒有,只能抓到直播的推流
所以我推測,彈幕的互動應該是b站自己寫的協議

答案可能藏在這些js里面,看了半天看地頭皮發麻
雖然我們沒有找到用來請求彈幕的介面,但是我們發現,所有的彈幕都被動態重繪在了前端的html上

如果我們通過網頁url拿到前端的html,不就可以拿到彈幕串列了嗎?
(ps:你上次不是說網頁是動態重繪的,用網頁url請求,回傳回來的html只是一個框架,里面什么都沒有!你這不是打自己臉嗎!!)
咳咳,如果我們直接用http請求當然會這樣,但是我們不妨換一種思路,
如果我們模擬一個瀏覽器,讓瀏覽器去執行腳本,重繪頁面,我們只需要拿到重繪后的界面就行了!
所以,我使用了selenium庫!
二、selenium安裝與使用
1.安裝庫
直接pip就行了:
pip install selenium
2.下載驅動
selenium需要使用瀏覽器的內核,我用的是chrome的,去這里下載就行

版本選和自己一樣的
下載完之后,解壓后的exe放到自己的python安裝目錄
3.模擬瀏覽器發出請求并捕獲彈幕
from selenium import webdriver
import time
import re
url = 'https://live.bilibili.com/308543'
print('正在打開瀏覽器')
browser = webdriver.Chrome()
browser.get(url)
print('正在讀取網頁')
# 延時一下
time.sleep(3)
# 得到動態的html
html = browser.page_source
"""
資料模板
<div class="chat-item danmaku-item " data-uname="幻月凜" data-uid="36894563" data-ts="1610605190" data-ct="63C5CCED" data-danmaku="還有救">
"""
pattern = '<div class="chat-item danmaku-item .*?"(.*?)>'
danmus = re.findall(pattern=pattern, string=html)
print(danmus)

成功捕捉到彈幕!!
webdriver.Chrome() 創建了一個瀏覽器物件 browser ,直接呼叫 browser 的page_source就能得到當前的html了!
三、所有代碼
1.匯入庫
from selenium import webdriver
import time
import re
import my_tools.my_print as mp
這個my_print是我自己寫的小庫,可以把串列按行列印,并且可以選擇顏色
2.創建一個BiliLive類并寫上構造方法
class BiliLive:
"""
用來抓取彈幕的類
"""
def __init__(self, room_num):
"""
構造方法
:param room_num: 要打開的直播房間號碼
"""
self.url = 'https://live.bilibili.com/' + str(room_num)
print('正在打開瀏覽器')
self.browser = webdriver.Chrome()
print('正在打開網頁')
self.browser.get(self.url)
print('打開成功')
# 等待瀏覽器內容加載
time.sleep(2)
# 彈幕串列
self.danmus = []
# 彈幕串列的最后一條彈幕
self.last_danmu = ''
3.創建兩個get方法,分別來獲得htnl頁面和里面的彈幕
def get_html(self):
"""
獲得頁面
:return: 當前頁面的源檔案
"""
return self.browser.page_source
def get_new_danmus(self):
"""
決議頁面內容,從當前頁面獲得新的彈幕的串列
:return:
"""
pattern = '<div class="chat-item danmaku-item .*?"(.*?)>'
danmus = re.findall(pattern=pattern, string=self.get_html())
return danmus
4. 把新的彈幕串列更新到總體的串列中
def flush_danmus(self):
"""
重繪彈幕串列,將讀取到的新的彈幕添加到彈幕串列
"""
# 獲取新的彈幕串列
new_list = self.get_new_danmus()
"""
ps: 你當然可以把串列轉換成集合然后直接添加進去,或者使用for
回圈遍歷,但是這兩種的時間復雜度都很高
"""
# 找到彈幕串列最后一條彈幕在新串列中的位置
if self.last_danmu in new_list:
# 找到其索引
flush_index = new_list.index(self.last_danmu) + 1
else:
# 彈幕串列不在新串列中
flush_index = 0
# 更新串列
self.danmus += new_list[flush_index:]
# mp.print_list(self.danmus, 'y')
# 更新最后一條彈幕
if len(self.danmus) > 1:
self.last_danmu = self.danmus[-1]
合并的思想是,使用原本彈幕串列的最后一個元素,在新得到的彈幕串列中找到相同的一項,設它在新串列中的索引為flush_index,那么只需要將從flush_index開始往后的彈幕合并到彈幕別表就行了,這樣可以消除重復的彈幕

你當然可以將彈幕串列轉化為set,然后再向里面添加新的彈幕,或者使用[i for i in new_danmus if …]的方式來合并彈幕,但是兩種方法時間復雜度都很高,強迫癥受不了
5.保存彈幕
def save_danmus(self):
"""
保存彈幕(懶......)
"""
pass
6.跑起來
def run(self, max_time=65536, flush_time=1):
"""
:param flush_time: 重繪彈幕的時間
:param max_time: 最大運行時間
:return:
"""
time.sleep(3)
start_time = time.time()
now_time = time.time()
while now_time - start_time < max_time:
# 重繪彈幕串列
self.flush_danmus()
# 自己寫的列印庫,換成print就行
# print(self.danmus)
mp.print_list(self.danmus, 'g')
time.sleep(flush_time)
now_time = time.time()
# 建議每次回圈都保存一下,防止關了瀏覽器之后資料丟失
self.save_danmus()
if __name__ == '__main__':
live = BiliLive('308543')
live.run()

爬取成功!
四、(更新)眼瞎作者找到api了
阿巴阿巴,寫完這篇文章之后,又用Fiddler仔細找了一下

復制到瀏覽器康了一下

主要是這個介面重繪頻率很慢,而且直接抓包得到的都是亂碼,當時就給忽略過去了
介面名叫https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory?roomid=<房間號>
想要爬取的小伙伴直接拿來用吧!!
不說了,配眼鏡去了!!
(更新)附上代碼
import requests
import my_fake_useragent
import json
import my_tools.my_print as mp
import time
class BiliLive2:
"""
用來爬取直播彈幕的類
"""
def __init__(self, room_num):
self.url = 'https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory'
self.params = {'roomid': str(room_num)}
self.danmus = []
self.last_danmu = ''
def get_headers(self):
"""
生成回應頭
:return: 生成的回應頭
"""
user_agent = my_fake_useragent.UserAgent()
ua = str(user_agent.random())
headers = {
'user-agent': ua
}
return headers
def get_response(self):
"""
得到請求
:return:
"""
response = requests.get(url=self.url, params=self.params, headers=self.get_headers())
return response
def get_new_danmus(self):
"""
訪問url得到新的彈幕
:return:
"""
resopnse = self.get_response()
text = resopnse.text
# 轉化為字典
info = json.loads(text)
ret_list = []
danmu = {}
for dm in info['data']['room']:
# 保存文本
danmu['text'] = dm['text']
# 保存uid
danmu['uid'] = dm['uid']
# 保存用戶名
danmu['nickname'] = dm['nickname']
# 保存時間
danmu['timeline'] = dm['timeline']
ret_list.append(danmu.copy())
danmu.clear()
return ret_list
def upgrade_danmus(self):
"""
重繪彈幕串列,將讀取到的新的彈幕添加到彈幕串列
"""
# 獲取新的彈幕串列
new_list = self.get_new_danmus()
"""
ps: 你當然可以把串列轉換成集合然后直接添加進去,或者使用for
回圈遍歷,但是這兩種的時間復雜度都很高
"""
# 找到彈幕串列最后一條彈幕在新串列中的位置
if self.last_danmu in new_list:
# 找到其索引
flush_index = new_list.index(self.last_danmu) + 1
else:
# 彈幕串列不在新串列中
flush_index = 0
# 更新串列
self.danmus += new_list[flush_index:]
# 更新最后一條彈幕
if len(self.danmus) > 1:
self.last_danmu = self.danmus[-1]
def save_danmus(self):
"""
保存彈幕(懶......)
"""
pass
def run(self, max_time=65536, flush_time=1):
"""
:param flush_time: 重繪彈幕的時間
:param max_time: 最大運行時間
:return:
"""
# time.sleep(3)
print('開始運行')
start_time = time.time()
now_time = time.time()
while now_time - start_time < max_time:
# 更新彈幕串列
self.upgrade_danmus()
# 列印一下
mp.print_list(self.danmus, 'g')
# 延時
time.sleep(flush_time)
now_time = time.time()
print('結束運行')
self.save_danmus()
if __name__ == '__main__':
live = BiliLive2(12885328)
live.run(max_time=200, flush_time=3)
運行結果

五、總結
雖然剛開始繞了點彎路,但最后好炊訓是做出來了,不得不說,暴力爬蟲確實爽
還有一點就是回傳回來的json里面有兩個串列,一個是admin,只顯示會員的彈幕,另一個是room,顯示直播間里面所有的彈幕
兩個類里面的save方法沒有完善(懶),有需要的小伙伴可以自己動手寫哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/249071.html
標籤:python
