爬取B站某個up主的視頻和彈幕,並簡單分析數據


  話不多說,直接進入正題,這次采集的對象是B站吃播up主,山葯村二牛,本人一直挺喜歡他的視頻,所以想采集一下他的視頻信息,然后分析數據,看下他視頻的情況。

  首先是爬蟲部分,采集的邏輯是從視頻頁將每個視頻的信息和地址采集下來,再請求地址采集視頻的彈幕。

  進入視頻頁,https://space.bilibili.com/382534165/video,將中間的id換掉就是其他up主了。查看源碼並沒有視頻的信息,所以可能是用異步加載的方式加載數據的,那么用谷歌瀏覽器的檢查模式,很容易發現視頻的數據都在下面那個請求的響應體中,返回的是json數據。

 

 

 

 

   視頻的信息都在里面了,所以可以直接請求這個接口就可以獲取到信息了,而該接口的請求參數也比較容易分析。

https://api.bilibili.com/x/space/arc/search?mid=382534165&ps=30&tid=0&pn=1&keyword=&order=pubdate&jsonp=jsonp

  mid是up主的id,ps返回的視頻數量,tid在后面的請求都不變,所以可以直接賦為0,pn是頁數,后面的參數也直接復制即可。

  下面就是爬取彈幕了,彈幕的話本人並沒有在檢查模式里面找到,然后通過百度,彈幕數據都在這一個接口中。很明顯cid就是用於標識視頻的,那么獲取到cid就可以了。后來我才發現,這里最多只能獲取1000條彈幕。

http://comment.bilibili.com/{cid}.xml

  然后在視頻信息里面並沒有cid的信息,所以先從從視頻播放頁里面找。可以看到下面這一個接口返回的數據就有cid。

 

 

 

   然后看該接口的請求參數,只需要bvid就可以了,而bvid在視頻信息里面就可以提取到,那么分析到此結束了,開始爬蟲。

  這里我使用scrapy框架,首先編寫items,主要分為兩個視頻的和彈幕的,視頻有title標題,aid,bvid,comment-評論數,created_time-發布時間,length-視頻長度,play-播放量,cid。

title = scrapy.Field()
aid = scrapy.Field()
bvid = scrapy.Field()
comment = scrapy.Field()
created_time = scrapy.Field()
length = scrapy.Field()
play = scrapy.Field()
cid = scrapy.Field()

  彈幕就只有cid,和content-內容。

cid = scrapy.Field()
content = scrapy.Field()

  下面是爬取的函數。

    def start_requests(self):
        base_url = 'https://api.bilibili.com/x/space/arc/search?mid=382534165&ps=30&tid=0&pn={}&keyword=&order=pubdate&jsonp=jsonp'
        headers = self.settings.get('DEFAULT_REQUEST_HEADERS')
        for pn in range(1, 10):
            url = base_url.format(pn)
            yield scrapy.Request(url=url, headers=headers, callback=self.parse_video_info)

    def parse_video_info(self, response):
        detail = json.loads(response.text)
        headers = self.settings.get('DEFAULT_REQUEST_HEADERS')
        for info in detail.get('data').get('list').get('vlist'):
            item = BilibiliErniuItem()
            item['title'] = info.get('title')
            item['aid'] = info.get('aid')
            item['bvid'] = info.get('bvid')
            item['comment'] = info.get('comment')
            item['created_time'] = info.get('created')
            item['length'] = info.get('length')
            item['play'] = info.get('play')
            cid_url = 'https://api.bilibili.com/x/player/pagelist?bvid={}&jsonp=jsonp'.format(item['bvid'])
            yield scrapy.Request(url=cid_url, headers=headers, meta={'item': item}, callback=self.get_bullet_chat_url)

    def get_bullet_chat_url(self, response):
        detail = json.loads(response.text)
        headers = self.settings.get('DEFAULT_REQUEST_HEADERS')
        item = response.meta['item']
        cid = detail.get('data')[0].get('cid')
        item['cid'] = cid
        bullet_chat_url = 'http://comment.bilibili.com/{}.xml'.format(cid)
        yield scrapy.Request(url=bullet_chat_url, headers=headers, meta={'cid': cid}, callback=self.parse_bullet_chat)
        yield item

    def parse_bullet_chat(self, response):
        sel = scrapy.Selector(response)
        item = ErniuBulletChatItem()
        item['cid'] = response.meta['cid']
        item['content'] = sel.xpath('//d//text()').extract()
        yield item

  然后在pipelines實現數據的保存,這里直接保存為csv文件即可,注意的是保存時數據的分隔符用了'|',避免使用',',否則會影響pandas讀入。

    def __init__(self):
        self.dirs = 'E:\\dataset\\bilibili_erniu\\video.csv'
        if not os.path.exists(self.dirs):
            with open(self.dirs, 'w', encoding='utf-8')as fp:
                fp.write('title|aid|bvid|comment|created_time|length|play|cid\n')
        self.buttet_chat_dir = 'E:\\dataset\\bilibili_erniu\\buttet_chat'
        if not os.path.exists(self.buttet_chat_dir):
            os.makedirs(self.buttet_chat_dir)

    def process_item(self, item, spider):
        if isinstance(item, BilibiliErniuItem):
            with open(self.dirs, 'a', encoding='utf-8') as fp:
                fp.write(item['title'] + '|' + str(item['aid']) + '|' + item['bvid'] + '|' + str(item['comment']) + '|' + str(item['created_time']) + '|' + item['length'] + '|' 
+ str(item['play']) + '|' + str(item['cid']) + '\n') return item else: filename = str(item['cid']) + '.csv' if not os.path.exists(self.buttet_chat_dir + '\\' + filename): with open(self.buttet_chat_dir + '\\' + filename, 'w', encoding='utf-8')as fp: fp.write('content' + '\n') for content in item['content']: fp.write(content + '\n') return item

  最后在setting加上請求頭,以及啟用piplines即可。

  以上就是爬蟲部分,下面是數據分析。

  首先是讀入數據,查看數據。

 

   數據讀入正常,那么查看是否有缺失值。

 

   沒有,那么來處理一下數據,首先是將created_time轉換為日期的格式。

from datetime import datetime

def change_time(t):
    return datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M')

def split_date(t):
    return t.split(' ')[0]

def split_hour_min(t):
    return t.split(' ')[1]

videos['time'] = videos.created_time.apply(change_time)  # 時間戳轉為年月日時分
videos['date'] = videos.time.apply(split_date)  # 分出日期
videos['hour_min'] = videos.time.apply(split_hour_min)  # 分出時間
videos['year'], videos['month'], videos['day'] = videos.date.str.split('-').str  # 年月日分開 

  

 

   處理完成,接下來將年月日的數據從字符串轉換為整型,方便后面的數據可視化。並對這三列進行排序。

videos.year = videos.year.astype('int')
videos.month = videos.month.astype('int')
videos.day = videos.day.astype('int')
videos.sort_values(['year', 'month', 'day'], inplace=True)

  下面先取出了年和月的數據。方便可視化時添加時間軸。

years = set(videos.year)
months = []
for year in years:
    months.append(set(videos[videos['year'].isin([year])].month))

  使用pyecharts庫進行可視化,這里展示的數據是播放量和評論數隨時間變化的柱狀圖。

from pyecharts import options as opts
from pyecharts.charts import Bar, Timeline

timeline_2020 = Timeline()
for m in months[2]:
    bar = (
        Bar()
        .add_xaxis(list(videos[videos.year.isin([2020])][videos.month.isin([m])].day))
        .add_yaxis("播放量", list(videos[videos.year.isin([2020])][videos.month.isin([m])].play), yaxis_index=0,)
        .add_yaxis("評論數", list(videos[videos.year.isin([2020])][videos.month.isin([m])].comment), yaxis_index=1)
        .extend_axis(
            yaxis=opts.AxisOpts(
                name="評論數",
                type_="value",
                position="right",
                axislabel_opts=opts.LabelOpts(formatter="{value}"),
            )
        )
        .set_global_opts(title_opts=opts.TitleOpts("山葯村二牛{}年{}月視頻播放量和評論數".format(2020, m)))
    )
    timeline_2020.add(bar, '{}月'.format(m))

  

 

   這里僅展示了2020年的,但是這樣並不能看出二牛視頻的播放量波動情況。所以再使用折線圖展示。

from pyecharts.charts import Line

line = (
    Line()
    .set_global_opts(
        tooltip_opts=opts.TooltipOpts(is_show=True),
        xaxis_opts=opts.AxisOpts(type_="category"),
        yaxis_opts=opts.AxisOpts(
            type_="value",
            axistick_opts=opts.AxisTickOpts(is_show=True),
            splitline_opts=opts.SplitLineOpts(is_show=True),
        ),
        title_opts=opts.TitleOpts("山葯村二牛視頻播放量和評論數折線圖"),
        datazoom_opts=[opts.DataZoomOpts()],
    )
    .add_xaxis(xaxis_data=list(videos_new.time))
    .add_yaxis(
        series_name="播放量",
        y_axis=list(videos_new.play),
        symbol="emptyCircle",
        is_symbol_show=True,
        label_opts=opts.LabelOpts(is_show=False),
        yaxis_index=0,
    )
    .add_yaxis(
        series_name="評論數",
        y_axis=list(videos_new.comment),
        symbol="emptyCircle",
        is_symbol_show=True,
        label_opts=opts.LabelOpts(is_show=False),
        yaxis_index=1,
    )
    .extend_axis(
        yaxis=opts.AxisOpts(
            name="評論數",
            type_="value",
            position="right",
            axislabel_opts=opts.LabelOpts(formatter="{value}"),
        )
    )
)

    從該圖可以看出,二牛視頻的播放量和評論數波動不大,中間的最高點我去找了一下那個視頻,原來是華農送竹鼠那期,怪不得那么多播放量。

  接下來對視頻長度和播放量使用了散點圖可視化。

from pyecharts.charts import Scatter

c = (
    Scatter()
    .add_xaxis(list(videos_new.length))
    .add_yaxis("播放量", list(videos_new.play))
    .set_global_opts(
        title_opts=opts.TitleOpts(title="山葯村二牛視頻時長-播放量散點圖"),
        visualmap_opts=opts.VisualMapOpts(type_="size", max_=1000000, min_=27000),
    )
)

  

 

  從這個圖看出二牛的視頻播放量和時長無關,播放量大概集中在5萬左右。

  接下來將標題做成詞雲圖,看看二牛最喜歡用什么標題。

import jieba
import re
from pyecharts.charts import WordCloud
from collections import Counter

title_cut = []
def tokenizer(title):
    return jieba.lcut(title)

for t in videos_new.title:
    t = re.sub(r"[0-9,,。?!?!【】]+", "", t)
    title_cut += tokenizer(t)

title_count = []
for key, value in Counter(title_cut).items():
    title_count.append((key, value))

w = (
    WordCloud()
    .add(series_name="標題詞雲", data_pair=title_count, word_size_range=[10, 100])
    .set_global_opts(
        title_opts=opts.TitleOpts(
            title="標題詞雲", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
        ),
        tooltip_opts=opts.TooltipOpts(is_show=True),
    )
)

  這里我使用的是jieba庫進行分詞,用正則簡單處理了一下文本,並沒有加入停用詞。

 

  從詞雲圖看出,二牛一般都會用’吃‘什么,’做‘什么作為標題,’好吃‘,’過癮‘,’真爽‘等字眼也出現得不少。

  最后,用上了一直沒用的彈幕做成詞雲圖,因為彈幕最多只有1000條,所以就不做折線圖這些的可視化了。

buttet_chat = []
path = 'E:\\dataset\\bilibili_erniu\\buttet_chat'
dirs = os.listdir(path)
for file in dirs:
    with open(os.path.join(path, file), 'r', encoding='utf-8')as fp:
        content = fp.readlines()
        buttet_chat += content[1:-1]

buttet_chat_cut = []
for b in buttet_chat:
    b = b.strip()
    b = re.sub(r"[0-9,,。?!?!【】]+", "", b)
    buttet_chat_cut += tokenizer(b)

buttet_chat_count = []
for key, value in Counter(buttet_chat_cut).items():
    buttet_chat_count.append((key, value))

w = (
    WordCloud()
    .add(series_name="彈幕詞雲", data_pair=buttet_chat_count, word_size_range=[10, 100])
    .set_global_opts(
        title_opts=opts.TitleOpts(
            title="彈幕詞雲", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
        ),
        tooltip_opts=opts.TooltipOpts(is_show=True),
    )
)

  

 

  從詞雲圖可以看出,大家一般喜歡發的彈幕的是’我‘,’許願‘等,從小點的詞可以看出也是有很多許願的內容,例如:’四級‘,’面試‘,’不掛科‘,’脫單‘等。因為二牛在吃飯的時候有一個很有趣的點頭,所以大家都喜歡在他點頭的時候發彈幕,祈禱願望成真。

  好了,這一次的練手就到此結束了。


免責聲明!

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



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