[Python] [Bilibili] B站歷史彈幕爬蟲

b站彈幕最多的天鵝臂視頻爲例,可視化效果見哪位藝人的老婆們最有毅力?b站彈幕爬蟲及可視化

1. 最新彈幕

最新彈幕可以通過NetWork中的list.so...獲取,具體操作可參考歷史彈幕的爬取。
在這裏插入圖片描述
但是最新彈幕只能最多獲取3000條,如果視頻的彈幕超過3000條,則需要通過別的方式獲取。
在這裏插入圖片描述

2. 歷史彈幕

2.1 探索

觀察彈幕列表可以發現,b站提供了歷史彈幕的查看途徑。
在這裏插入圖片描述
點擊不同的日期,可以發現不斷有以history?開頭的資源加載進來。
在這裏插入圖片描述
從預覽中可以看到,其實就是對應日期最新的3000條彈幕。觀察一下url,有關的參數是typeoiddateoid就是視頻對應的id,date則是日期,type暫時不知道幹哈的。

https://api.bilibili.com/x/v2/dm/history?type=1&oid=5627945&date=2020-03-02

再把這個網址放到沒有登錄b站賬號的瀏覽器打開,顯示賬號未登錄,也就是需要登錄才能獲取數據,可以考慮用模擬登錄解決。
在這裏插入圖片描述

2.2 數據獲取

2.2.1 模擬登錄

用的是selenium搭配谷歌瀏覽器,需要先配置一下和瀏覽器版本匹配的驅動,可自行百度或參考這裏的第五點

# 導入需要的包
from bs4 import BeautifulSoup as bs
import pandas as pd
import re
from selenium import webdriver
import time, datetime

# 模擬登錄
## 登錄界面鏈接
url = 'https://passport.bilibili.com/login'
## 模擬登錄啓動
driver = webdriver.Chrome()
driver.get(url)
## 接下來就是在打開的谷歌瀏覽器裏手動登錄一下自己的賬號,
## 	* 因爲b站登錄需要驗證,這一步自動化的代價比較大,所以手動了。

2.2.2 獲取每一天的數據

探索部分,看看即可:

# 以2019-01-01爲例
date = '2019-01-01'
url = 'https://api.bilibili.com/x/v2/dm/history?type=1&oid=5627945&date=%s'%date

# 轉入歷史彈幕的頁面
driver.get(url)

# 獲取數據部分的html
html = driver.find_element_by_tag_name('i').get_attribute('innerHTML')

# 解析
soup = bs(html, 'lxml')

# 先康康第一條
print(soup.find('d'))
# 彈幕內容
print(soup.find('d').text)
# 彈幕其他信息
print(soup.find('d').get('p'))

# 經過一番探索,以逗號分割,第一個參數是視頻時間(秒),第五個參數是時間戳,第七個參數是用戶id,其他反正暫時不重要,不探索了
# 整理成DataFrame
data = pd.DataFrame([i.get('p').split(',') + [i.text] for i in soup.findAll('d')], \
                    columns=['second', 'b', 'c', 'd', 'timestamp', 'f', 'user', 'h', 'text'])
data.head(2)

在這裏插入圖片描述
封裝成函數後:

def get_per_day(date):
    # url
    url = 'https://api.bilibili.com/x/v2/dm/history?type=1&oid=5627945&date=%s'%date
    # 轉入歷史彈幕的頁面
    driver.get(url)
    # 獲取並解析
    soup = bs(driver.find_element_by_tag_name('i').get_attribute('innerHTML'), 'lxml')
    # 整理成DataFrame
    data = pd.DataFrame([i.get('p').split(',') + [i.text] for i in soup.findAll('d')], \
                    columns=['second', 'b', 'c', 'd', 'timestamp', 'f', 'user', 'h', 'text'])
    # 只取有用的列
    data = data[['timestamp', 'text']]
    return data

2.2.3 獲取一段時間的數據

探索部分,看看即可:

# 設定開始和結束日期
start = '2019-01-01'
end = '2019-02-01'

# 創建空DataFrame方便後面放數據
data = pd.DataFrame()

# 轉成日期形式方便加天數
start = pd.datetime.strptime(start, '%Y-%m-%d')
end = pd.datetime.strptime(end, '%Y-%m-%d')

# 循環
while start <= end:
    date = pd.datetime.strftime(start, '%Y-%m-%d')
    print(date,end=', ')
    data = data.append(get_per_day(date))
    start = start + datetime.timedelta(days=1)

封裝成函數後:

def get_period(start, end):
    # 創建空DataFrame方便後面放數據
    data = pd.DataFrame()

    # 轉成日期形式方便加天數
    start = pd.datetime.strptime(start, '%Y-%m-%d')
    end = pd.datetime.strptime(end, '%Y-%m-%d')

    # 循環
    while start <= end:
        date = pd.datetime.strftime(start, '%Y-%m-%d')
        print(date,end=', ')
        data = data.append(get_per_day(date))
        # 去重(因爲如果當天彈幕量不到3000會獲取到前一天的彈幕,導致數據大量重複)
        data.drop_duplicates(inplace=True)
        start = start + datetime.timedelta(days=1)
        
    return data

於是就可以通過設定開始和結束的時間爬取一段時間的彈幕。

data = get_period('2019-01-01', '2019-01-31')

2.2.4 代碼整理

如果不想看上面的部分,直接複製下面這段。

def get_per_day(date, driver):
	'''
	獲取一天的數據
	date: 	str, %Y-%m-%d
	'''
    # url
    url = 'https://api.bilibili.com/x/v2/dm/history?type=1&oid=5627945&date=%s'%date
    # 轉入歷史彈幕的頁面
    driver.get(url)
    # 獲取並解析
    soup = bs(driver.find_element_by_tag_name('i').get_attribute('innerHTML'), 'lxml')
    # 整理成DataFrame
    data = pd.DataFrame([i.get('p').split(',') + [i.text] for i in soup.findAll('d')], \
                    columns=['second', 'b', 'c', 'd', 'timestamp', 'f', 'user', 'h', 'text'])
    # 只取有用的列
    data = data[['timestamp', 'text']]
    return data

def get_period(start, end):
	'''
	先登錄,然後獲取數據。
	start: 	str, %Y-%m-%d
	end:	str, %Y-%m-%d
	'''
	# 登錄
	url = 'https://passport.bilibili.com/login'
	driver = webdriver.Chrome()
	driver.get(url)
	input('請在瀏覽器登錄賬號後回車')
	
    # 創建空DataFrame方便後面放數據
    data = pd.DataFrame()

    # 轉成日期形式方便加天數
    start = pd.datetime.strptime(start, '%Y-%m-%d')
    end = pd.datetime.strptime(end, '%Y-%m-%d')

    # 循環
    while start <= end:
        date = pd.datetime.strftime(start, '%Y-%m-%d')
        # 時不時打印一下方便追蹤進度
        if start.day == 1:
        	print(date,end=', ')
       	# 拼接數據
        data = data.append(get_per_day(date, driver))
        # 去重
        data.drop_duplicates(inplace=True)
        # 下一天
        start = start + datetime.timedelta(days=1)
        
    return data

data = get_period('2019-01-01', '2019-12-31')

2.3 數據處理

2.3.1 計算彈幕發送時間

把獲取的時間由時間戳專成字符串或整型。

# 轉成字符串
#data['time'] = data['timestamp'].apply(lambda x: time.strftime("%Y%m%d", time.localtime(int(x))))
# 轉成整型方便後面計算
data['time'] = data['timestamp'].apply(lambda x: int(time.strftime("%Y%m%d", time.localtime(int(x)))))

2.3.2 從彈幕中提取各種粉絲類型和明星名字

有很多種方法,規則的學習成本最低所以用規則,具體不講了,主要就是正則匹配。

def clean(text):
    for i in ['前面的', '前面', \
              '前邊兒的', '前邊兒', '前邊', '你的', '討厭', '感覺'\
              '我是', '小姐姐', '揮着翅膀', '這位', '這個動作', \
              '我以後養', '大姨媽', '來姨媽', '姨媽', '第四天', '第五天', '第三天', '第二天', \
              '墜機', '哈哈', '廣場舞大媽', '做到這裏', '已經', '安利', '我的媽', '一半', \
              '沒有', '要做', '撞媽', '心疼', '雞翼', '要成爲', '好多', '未來']:
        text = text.replace(i, '')
    
    pattern = r'([^,.,。 !!??()\t0-9嗎嗚了啊的🟢着呦他說我你是·]{2,})'
    return text, pattern

def find_relation(text, names):
    text, pattern = clean(text)
    for i in names:
        pa = re.compile(pattern+i)
        if len(pa.findall(text)) != 0:
            return pa.findall(text)[0]
    return None

wife = ['的大老婆', '大老婆', \
        '的小老婆', '小老婆', \
        '的老婆', '老婆', \
        '的真老婆', '真老婆', \
        '的腦婆', '腦婆', \
        '的夫人', '夫人', \
        '的妻子', '妻子']
girlfriend  = ['的女朋友', '女朋友', \
               '的女票', '女票', \
               '的對象', '腦婆', \
               '的女盆友', '女盆友', \
               '的女友', '女友']
mon = ['的麻麻', '麻麻', \
      '的媽媽', '媽媽', \
      '的媽', '媽', \
      '的親媽', '親媽', \
      '的親麻', '親麻', \
      '的母親', '母親', \
      '的馬麻', '馬麻', \
      '的媽粉', '媽粉']
women = ['的女人', '女人']
girl = ['的女孩', '女孩']
daughter = ['的女兒', '女兒']
sister = ['的姐姐', '姐姐', \
         '的妹妹', '妹妹']

relation_dic = {'wife': '老婆', 'girlfriend': '女朋友', 'mon': '媽媽', \
               'women': '女人', 'girl': '女孩', 'daughter': '女兒', \
               'sister': '姐妹'}

data['wife'] = data['text'].apply(lambda x: find_relation(x, wife))
data['girlfriend'] = data['text'].apply(lambda x: find_relation(x, girlfriend))
data['mon'] = data['text'].apply(lambda x: find_relation(x, mon))
data['women'] = data['text'].apply(lambda x: find_relation(x, women))
data['girl'] = data['text'].apply(lambda x: find_relation(x, girl))
data['daughter'] = data['text'].apply(lambda x: find_relation(x, daughter))
data['sister'] = data['text'].apply(lambda x: find_relation(x, sister))

在這裏插入圖片描述

2.3.3 彙總每一天

relation_count = pd.DataFrame()
for k,v in relation_dic.items():
    df = pd.DataFrame(data.groupby(['time', k]).size())
    df.reset_index(inplace=True)
    df.columns = ['date', 'name', 'value']
    df['type'] = v
    relation_count = relation_count.append(df)

在這裏插入圖片描述

2.3.4 計算累計值

dates = data['time'].unique()
relation_sum = pd.DataFrame()
for i in dates:
    df = pd.DataFrame(relation_count[relation_count['date'] <= i].groupby(['name', 'type']).sum()['value'])
    df.reset_index(inplace=True)
    df['date'] = '%s-%s-%s'%(str(i)[:4], str(i)[4:6], str(i)[6:])
    df.sort_values('value', ascending=False, inplace=True)
    relation_sum = relation_sum.append(df.iloc[:30])
relation_sum['name'] = relation_sum.apply(lambda x: str(x['name'])+'的'+x['type'], axis=1)

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章