【python】B站彈幕數據分析及可視化(爬蟲+數據挖掘)



成果展示

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

項目地址

完整代碼可在我的github中下載:https://github.com/XavierJiezou/python-danmu-analysis

爬取彈幕

可以看我之前寫的這篇文章:10行代碼下載B站彈幕

下載代碼

# download.py
'''依賴模塊 pip install requests '''
import re
import requests

url = input('請輸入B站視頻鏈接: ')
res = requests.get(url)
cid = re.findall(r'"cid":(.*?),', res.text)[-1]
url = f'https://comment.bilibili.com/{cid}.xml'
res = requests.get(url)
with open(f'{cid}.xml', 'wb') as f:
    f.write(res.content)

樣例輸入

B站番劇《花丸幼稚園》第六集視頻播放地址:https://www.bilibili.com/bangumi/play/ep17617

樣例輸出

彈幕文件51816463.xml:https://comment.bilibili.com/51816463.xml

數據處理

下載彈幕文件51816463.xml后,我們打開看一下:

<?xml version="1.0" encoding="UTF-8"?>
<i>
    <chatserver>chat.bilibili.com</chatserver>
    <chatid>51816463</chatid>
    <mission>0</mission>
    <maxlimit>3000</maxlimit>
    <state>0</state>
    <real_name>0</real_name>
    <source>k-v</source>
    <d p="302.30800,1,25,16777215,1606932706,0,b105c2ee,41833218383544323">長頸鹿呢?還是大象呢?(</d>
    <d p="608.17000,1,25,16707842,1606926336,0,af4597df,41829878640672775">我也不想的,實在是太大了呀</d>
    <d p="249.95600,1,25,14811775,1606925877,0,af4597df,41829637765988357">真是深不可測啊</d>
    此處省略很多字
</i>

可以看到xml文件中d標簽的text部分就是彈幕的文本,而d標簽的p屬性應該是彈幕的相關參數,共有8個,用逗號分隔。

參數略解

  1. stime: 彈幕出現時間 (s)
  2. mode: 彈幕類型 (< 7 時為普通彈幕)
  3. size: 字號
  4. color: 文字顏色
  5. date: 發送時間戳
  6. pool: 彈幕池ID
  7. author: 發送者ID
  8. dbid: 數據庫記錄ID(單調遞增)

參數詳解
① stime(float):彈幕出現時間,單位是秒;也就是在幾秒出現彈幕。
② mode(int):彈幕類型,有8種;小於8為普通彈幕,8是高級彈幕。

  • 1~3:滾動彈幕
  • 4:底端彈幕
  • 6:頂端彈幕
  • 7:逆向彈幕
  • 8:高級彈幕

③ size(int):字號。

  • 12:非常小
  • 16:特小
  • 18:小
  • 25:中
  • 36:大
  • 45:很大
  • 64:特別大

④ color(int):文字顏色;十進制表示的顏色。
⑤ data(int):彈幕發送時間戳。也就是從基准時間1970-1-1 08:00:00開始到發送時間的秒數。
⑥ pool(int):彈幕池ID。

  • 0:普通池
  • 1:字幕池
  • 2:特殊池(高級彈幕專用)

⑦ author(str):發送者ID,用於"屏蔽此發送者的彈幕"的功能。
⑧ dbid(str):彈幕在數據庫中的行ID,用於"歷史彈幕"功能。

了解彈幕的參數后,我們就將彈幕信息保存為danmus.csv文件:
在這里插入圖片描述

# processing.py
import re

with open('51816463.xml', encoding='utf-8') as f:
    data = f.read()

comments = re.findall('<d p="(.*?)">(.*?)</d>', data)
# print(len(comments)) # 3000
danmus = [','.join(item) for item in comments]
headers = ['stime', 'mode', 'size', 'color', 'date', 'pool', 'author', 'dbid', 'text']
headers = ','.join(headers)
danmus.insert(0, headers)

with open('danmus.csv', 'w', encoding='utf_8_sig') as f:
    f.writelines([line+'\n' for line in danmus])

數據分析

詞頻分析

詞雲圖在線查看:https://jsrun.net/uMwKp/embedded/all/light

在這里插入圖片描述

# wordCloud.py
'''依賴模塊 pip install jieba, pyecharts '''
from pyecharts import options as opts
from pyecharts.charts import WordCloud
import jieba

with open('danmus.csv', encoding='utf-8') as f:
    text = " ".join([line.split(',')[-1] for line in f.readlines()])

words = jieba.cut(text)
_dict = {}
for word in words:
    if len(word) >= 2:
        _dict[word] = _dict.get(word, 0)+1
items = list(_dict.items())
items.sort(key=lambda x: x[1], reverse=True)

c = (
    WordCloud()
    .add(
        "",
        items,
        word_size_range=[20, 120],
        textstyle_opts=opts.TextStyleOpts(font_family="cursive"),
    )
    .render("wordcloud.html")
)

情感分析

餅狀圖在線查看:https://jsrun.net/6SwKp/embedded/all/light

在這里插入圖片描述

由餅狀圖可知:3000條彈幕中,積極彈幕超過一半,中立彈幕有百分之三十幾。

當然,彈幕調侃內容居中,而且有很多梗,會對情感分析造成很大的障礙,舉個栗子:

>>> from snownlp import SnowNLP
>>> s = SnowNLP('阿偉死了') 
>>> s.sentiments
0.1373666377744408

"阿偉死了"因帶有""字,所以被判別為消極情緒。但實際上,它反映的確是積極情緒,形容對看到可愛的事物時的激動心情。

# emotionAnalysis.py
'''依賴模塊 pip install snownlp, pyecharts '''
from snownlp import SnowNLP
from pyecharts import options as opts
from pyecharts.charts import Pie

with open('danmus.csv', encoding='utf-8') as f:
    text = [line.split(',')[-1] for line in f.readlines()[1:]]

emotions = {
    'positive': 0,
    'negative': 0,
    'neutral': 0
}
for item in text:
    if SnowNLP(item).sentiments > 0.6:
        emotions['positive'] += 1
    elif SnowNLP(item).sentiments < 0.4:
        emotions['negative'] += 1
    else:
        emotions['neutral'] += 1
print(emotions)


c = (
    Pie()
    .add("", list(emotions.items()))
    .set_colors(["blue", "purple", "orange"])
    .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c} ({d}%)"))
    .render("emotionAnalysis.html")
)

精彩片段

折線圖在線查看:https://jsrun.net/HSwKp/embedded/all/light

在這里插入圖片描述
由折線圖可知:第3分鍾,第8、第9分鍾,還有第13分鍾分別是該視頻的精彩片段。

# highlights.py
'''依賴模塊 pip install snownlp, pyecharts '''
from pyecharts.commons.utils import JsCode
from pyecharts.charts import Line
from pyecharts.charts import Line, Grid
import pyecharts.options as opts


with open('danmus.csv', encoding='utf-8') as f:
    text = [float(line.split(',')[0]) for line in f.readlines()[1:]]


text = sorted([int(item) for item in text])
data = {}
for item in text:
    item = int(item/60)
    data[item] = data.get(item, 0)+1


x_data = list(data.keys())
y_data = list(data.values())
background_color_js = (
    "new echarts.graphic.LinearGradient(0, 0, 0, 1, "
    "[{offset: 0, color: '#c86589'}, {offset: 1, color: '#06a7ff'}], false)"
)
area_color_js = (
    "new echarts.graphic.LinearGradient(0, 0, 0, 1, "
    "[{offset: 0, color: '#eb64fb'}, {offset: 1, color: '#3fbbff0d'}], false)"
)
c = (
    Line(init_opts=opts.InitOpts(bg_color=JsCode(background_color_js)))
    .add_xaxis(xaxis_data=x_data)
    .add_yaxis(
        series_name="彈幕數量",
        y_axis=y_data,
        is_smooth=True,
        symbol="circle",
        symbol_size=6,
        linestyle_opts=opts.LineStyleOpts(color="#fff"),
        label_opts=opts.LabelOpts(is_show=True, position="top", color="white"),
        itemstyle_opts=opts.ItemStyleOpts(
            color="red", border_color="#fff", border_width=3
        ),
        tooltip_opts=opts.TooltipOpts(is_show=True),
        areastyle_opts=opts.AreaStyleOpts(
            color=JsCode(area_color_js), opacity=1),
        markpoint_opts=opts.MarkPointOpts(
            data=[opts.MarkPointItem(type_="max")])
    )
    .set_global_opts(
        title_opts=opts.TitleOpts(
            title="",
            pos_bottom="5%",
            pos_left="center",
            title_textstyle_opts=opts.TextStyleOpts(
                color="#fff", font_size=16),
        ),
        xaxis_opts=opts.AxisOpts(
            type_="category",
            boundary_gap=False,
            axislabel_opts=opts.LabelOpts(margin=30, color="#ffffff63"),
            axisline_opts=opts.AxisLineOpts(
                linestyle_opts=opts.LineStyleOpts(width=2, color="#fff")
            ),
            axistick_opts=opts.AxisTickOpts(
                is_show=True,
                length=25,
                linestyle_opts=opts.LineStyleOpts(color="#ffffff1f"),
            ),
            splitline_opts=opts.SplitLineOpts(
                is_show=True, linestyle_opts=opts.LineStyleOpts(color="#ffffff1f")
            )
        ),
        yaxis_opts=opts.AxisOpts(
            type_="value",
            position="left",
            axislabel_opts=opts.LabelOpts(margin=20, color="#ffffff63"),
            axisline_opts=opts.AxisLineOpts(
                linestyle_opts=opts.LineStyleOpts(width=2, color="#fff")
            ),
            axistick_opts=opts.AxisTickOpts(
                is_show=True,
                length=15,
                linestyle_opts=opts.LineStyleOpts(color="#ffffff1f"),
            ),
            splitline_opts=opts.SplitLineOpts(
                is_show=True, linestyle_opts=opts.LineStyleOpts(color="#ffffff1f")
            ),
        ),
        legend_opts=opts.LegendOpts(is_show=False),
        tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="line")
    )
    .render("highlights.html")
)

高能時刻

更多時候,我們可能對精彩片段不太關注,而是想知道番劇的名場面出自幾分幾秒,即高能時刻

# highEnergyMoment.py
import re

with open('danmus.csv', encoding='utf-8') as f:
    danmus = []
    for line in f.readlines()[1:]:
        time = int(float(line.split(',')[0]))
        text = line.split(',')[-1].replace('\n', '')
        danmus.append([time, text])

danmus.sort(key=lambda x: x[0])
dict1 = {}
dict2 = {}
control = True
for item in danmus:
    if re.search('名場面(:|:)', item[1]):
        print(f'{int(item[0]/60)}m{item[0]%60}s {item[1]}')
        control = False
        break
    if '名場面' in item[1]:
        minute = int(item[0]/60)
        second = item[0] % 60
        dict1[minute] = dict1.get(minute, 0)+1
        dict2[minute] = dict2.get(minute, 0)+second
    else:
        pass
if control:
    minute= max(dict1, key=dict1.get)
    second = round(dict2[minute]/dict1[minute])
    print(f'{minute}m{second}s 名場面')

輸出:9m29s 名場面:懷中抱妹鯊。我們去視頻中看一下,9m29s確實是名場面:
在這里插入圖片描述

福利情節

字體顏色為黃色,也就是10進制顏色的值為16776960時,就是那種比較污的福利情節,同時為了防止異常,只有當該分鍾內出現黃色彈幕的次數≥3時,才說明該分鍾內是福利情節,並且輸出該分鍾內第一次出現黃色彈幕的秒數:

02m15s 吼吼吼
03m30s 什么玩意
06m19s 真的有那么Q彈嗎
08m17s 憋死
09m10s 前方萬惡之源
10m54s 噢噢噢噢
11m02s 這就是平常心
12m34s 這個我可以
17m19s 因為你是鋼筋混凝土直女
18m06s 假面騎士ooo是你嗎
19m00s 警察叔叔就是這個人
20m00s 金色傳說的說。。。
21m02s 嘿嘿嘿~
# textColor.py
with open('danmus.csv', encoding='utf-8') as f:
    danmus = []
    for line in f.readlines()[1:]:
        time = int(float(line.split(',')[0]))
        color = line.split(',')[3]
        text = line.split(',')[-1].replace('\n', '')
        danmus.append([time, color, text])

danmus.sort(key=lambda x: x[0])
dict1 = {}
dict2 = {}
for item in danmus:
    if item[1] == '16776960':
        minute = int(item[0]/60)
        second = item[0] % 60
        dict1[minute] = dict1.get(minute, 0)+1
        if dict2.get(minute) == None:
            dict2[minute] = f'{minute:0>2}m{second:0>2}s {item[2]}'
        else:
            pass
            
for key, value in dict1.items():
    if value >= 3:
        print(dict2[key])


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM