切换语言为:繁体

如何通过Python爬取视频网站的弹幕信息

  • 爱糖宝
  • 2024-09-04
  • 2059
  • 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.