切換語言為:簡體

如何透過Python爬取影片網站的彈幕資訊

  • 爱糖宝
  • 2024-09-04
  • 2058
  • 0
  • 0

前言

今天有一個好兄弟說,他開學了,在學到資料採集的課程的時候,老師佈置了一個作業,爬取b站影片的彈幕。對此,他感覺到了非常困擾。

他說,“爬取小說我能夠理解,小說是文章,能夠直接看到,我知道該如何爬取,但是彈幕都在影片裡面,這要怎麼爬呢?是下載影片嗎?我不明白啊!”

可以看出,好兄弟確實是遇到了一些困惑,他可能不太瞭解影片網站彈幕的實現。其實,當然,彈幕也是可以爬取的,因為,彈幕也是由影片網站伺服器所提供的,自然也是可以爬取到的。

彈幕原理

如同我們經常用到的那樣,彈幕就是在影片/直播中,顯示一條文字。因此,只要將文字繪製出來,覆蓋在影片上方即可。一個常見的實現是,建立一個Canvas的彈幕系統,繪製彈幕,並且透過動畫效果,控制彈幕的移動。當然,簡單的覆蓋效果不理想,也要透過一些演算法,來調整顯示的方式,比如說必須考慮到彈幕之間不應該互相覆蓋,或者如果彈幕太多,螢幕無法顯示(對於解析度低,螢幕小的裝置,這尤其重要),需要限制彈幕池的大小。而一些高階的影片網站中,甚至可能檢測影片中的關鍵人物,避免彈幕遮擋到人物。

彈幕通常需要包括幾條重要的資訊,比如說使用者id,傳送時間,彈幕出現的位置,彈幕的內容等。在實際使用中,彈幕不僅僅要考慮到如何繪製和顯示,也要考慮到如何傳送,接受和儲存。並且在儲存的過程中需要考慮到安全性,例如,需要對使用者的輸入進行轉義,防止xss的注入。

對於彈幕爬取,在實際使用中,往往會有一個專門的彈幕介面,透過這個介面,就可以得到所有的彈幕資訊。如果是實時彈幕,或者彈幕數量特別多,往往會使用輪詢請求的方式,例如每隔幾秒鐘,就請求一次新的彈幕資訊。也就是說,爬取彈幕其實不需要下載影片,只需要請求這個彈幕介面就可以了。

彈幕實現

如果我們需要製作一個彈幕影片,一個非常快速的方式,就是使用danmaku的js庫,這是一個專門的彈幕庫,透過這個庫可以輕鬆的傳送彈幕。

首先,我們需要載入這個danmaku的js庫

<script src="https://cdn.jsdelivr.net/npm/danmaku/dist/danmaku.min.js"></script>

然後建立一個danmaku,這通常需要指定一個容器

var danmaku = new Danmaku({
            container: document.getElementById('my-video-container'),
            media: document.getElementById('my-video'),
            comments: []
        });

然後,就可以透過emit向螢幕傳送彈幕,設定時間,大小,顏色等

danmaku.emit({
  text: '大佬6666666',
  style: {
    fontSize: '24px',
    color: '#fff',
    border: 'none',
    backgroundColor: 'rgba(0, 0, 0, 0.5)'
  },
  time: 10
});

完整的一個html頁面可能像這樣:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>影片彈幕網站</title>
    <script src="https://cdn.jsdelivr.net/npm/danmaku/dist/danmaku.min.js"></script>

</head>
<body>
    <div id="my-video-container" style="width:640px;height:360px;position:relative;">
      <video id="my-video" src="你的影片.mp4" style="position:absolute;" controls></video>
    </div>

    <script>
        var danmaku = new Danmaku({
            container: document.getElementById('my-video-container'),
            media: document.getElementById('my-video'),
            comments: []
        });


        danmaku.emit({
            text: '大佬66666',
            style: {
                fontSize: '24px',
                color: '#FFFFFF',
                border: 'none',
                backgroundColor: 'rgba(0, 0, 0, 0.5)'
            },
            time: 10
        });

    </script>
</body>
</html>

這是關於影片彈幕的一種方法,如果是直播彈幕的話,還會有所不同,關於danmaku的更多瞭解,可以訪問該庫的github地址:github.com/weizhenye/D…

爬取彈幕

回到我們剛剛的問題上來,如果爬取b站的彈幕,同樣需要使用b站彈幕的介面,然後透過這個介面進行請求,得到結果即可,這個並不難做到,下面就是一個爬取b站彈幕的實現。

import sys
import aiohttp
import asyncio
import time
from parsel import Selector


class BiliDanmaku:
	
    # 適用與windows環境的事件迴圈邏輯,非windows環境使用者可自行註釋掉
    if sys.platform == 'win32':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    @staticmethod
    def parse_p(data):
        fields = data.split(',')
        appear_time = float(fields[0])

        send_time_epoch = int(fields[4])
        send_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(send_time_epoch))

        return {
            "影片內彈幕出現時間": appear_time,
            "彈幕傳送時間": send_time,
        }

    def __init__(self, bvid, agent="bilibili client"):
        self.bvid = bvid
        self.headers = {"user-agent": agent}
        self.base_url = "https://api.bilibili.com"


    async def initialize(self):
        url = "https://api.bilibili.com/x/web-interface/view"
        params = {"bvid": self.bvid}
        async with aiohttp.ClientSession() as session:
            async with session.get(url, params=params, headers=self.headers) as response:
                data = await response.json()
                self.cid = data.get("data", {}).get("cid")

    async def get_danmaku(self):
        await self.initialize()
        async with aiohttp.ClientSession() as session:
            async with session.get(f"{self.base_url}/x/v1/dm/list.so", headers=self.headers, params={"oid": self.cid}) as response:
                r = await response.text()
                s = Selector(r)
                d = s.xpath("//d")
                danmaku_list = []
                for i in d:
                    p = i.xpath("./@p").get()
                    text = i.xpath("./text()").get()
                    info = self.parse_p(p)
                    info["彈幕內容"] = text
                    danmaku_list.append(info)
        return danmaku_list


async def __crawl_danmaku(bvid):
    b = BiliDanmaku(bvid)
    danmaku = await b.get_danmaku()
    return danmaku

def crawl_danmaku(bvid):
    danmaku = asyncio.run(__crawl_danmaku(bvid))
    with open("danmaku.txt", "w", encoding="utf-8") as file:
        for item in danmaku:
            file.write(str(item) + "\n")
    return danmaku

if __name__ == "__main__":
    crawl_danmaku("BV1EcHgezEyF") # 輸入影片BV號

執行後會在同目錄下生成danmuku.txt,該影片的所有彈幕都儲存在裡面了!

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.