一、实现目标

        获取B站视频弹幕,并对各时间段弹幕数量进行分段统计,最后用折线图进行展示。

二、实现步骤

        通过简单的思路分析,我们可以将整个过程分为网页分析、网页请求、网页解析、弹幕统计、绘制折线图五个步骤。

2.1 网页分析

        在对B站弹幕进行分析时,使用鼠标定位法我们会发现无法定位到弹幕的源代码,这种方式行不通。其实,在B站中,许多基础的数据,如弹幕、评论、视频基本信息等,都有非常成熟和稳定的应用程序接口(API)可供使用。也就是说,我们可以通过弹幕 API,获取一个视频的弹幕数据。弹幕接口为:https://comment.bilibili.com/{cid}.xml。

        在B站中,只要视频中有弹幕,就会有一个 cid 参数。cid 用来表示某个视频对应的弹幕池。
只要知道了 cid 参数值,就可以找到视频弹幕数据的位置了。如图我们只要在播放视频时开启弹幕,然后搜索cid,就可以定位到该视频的cid参数。

        然后我们就得到视频弹幕接口:https://comment.bilibili.com/1581931988.xml。将接口在浏览器中打开,可以轻松获得弹幕数据。

2.2 网页请求

        首先,导入 requests 模块。将弹幕接口链接以字符串的格式赋值给变量 url。接着,将变量 url 作为参数,添加进 requests.get() 函数中,再给赋值给变量 response。然后,使用 .text 属性获取 response 内容,赋值给变量 xml。在使用 print 输出变量 xml,即可获得视频弹幕接口 XML 代码。

# 使用import导入requests模块
import requests

# 将https://comment.bilibili.com/218710655.xml赋值给变量url
url = "https://comment.bilibili.com/1581931988.xml"

# 添加User-Agent参数,伪装成浏览器访问
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"}

# 将变量url作为参数,添加进requests.get()中,给赋值给response
response = requests.get(url, headers=headers)

# 使用.text属性获取response内容,赋值给xml
xml = response.text

# 使用print输出xml
print(xml)

        如图就获得了弹幕数据,在返回的结果中出现了一些奇奇怪怪的符号,并且没有中文。那些符号是中文乱码,出现乱码的原因是网页编码和爬取下来后的编码转换不一致。

        以下是一些常见的编码。

        前面我们提到,出现乱码的原因是网页编码和爬取下来后的编码转换不一致。我们可以使用response.encoding和response.apparent_encoding来分别获得requests编码和网页编码,如下图,requests模块使用了 ISO-8859-1 编码,网页使用了 utf-8 编码,编码方式不一样,导致出现中文乱码。

        我们可以对 response.encoding 属性重新赋值,将网站的编码方式,也就是将 response.apparent_encoding 赋值给 response.encoding,就可以修改编码了,让编码统一。

# 将网页编码方式赋值给response.encoding
response.encoding = response.apparent_encoding

        如下图,我们就解决了中文乱码问题

2.3 网页解析

        现在返回的结果中没有符号,能够输出弹幕内容。接下来,我们要提取出弹幕的发布时间。仔细观察 xml 代码,会发现所有的弹幕都在类似的节点中,只是属性 p 的值不一样,这些参数应该和弹幕的设置有关。在这里,我们就需要提取出和弹幕有关所有节点,再从每个节点中提取出弹幕时间对应的参数。

        想要提取出弹幕所在的节点,我们使用 Beautiful Soup 解析模块,从 bs4 中导入 BeautifulSoup 模块,创建一个 BeautifulSoup 对象,传入变量 xml 和解析器 lxml,将该对象赋值给变量 soup。接着使用 find_all() 函数中,传入 name 参数,其参数值为 d 。得到由所有 d 节点组成的列表。

# 从bs4中导入BeautifulSoup模块
from bs4 import BeautifulSoup

# 使用BeautifulSoup()读取xml,添加lxml解析器,赋值给soup
soup = BeautifulSoup(xml,"lxml")

# 使用find_all()查询soup中d的节点,赋值给content_all
content_all = soup.find_all(name = "d")

# 使用print输出content_all
print(content_all)

        提取出弹幕所在全部节点后,该如何获取弹幕的发布时间呢?在这里,在 p 对应的属性值中第一个参数就是弹幕的发布时间,例如:43.49200,它的单位是秒。也就是说,我们需要提取出 p 对应的属性值,再获取第一个参数值。

        想要把 P 对应的属性值提取出,需要使用 for 循环遍历列表 content_all。接着使用 .attrs 属性获取 p 对应的属性值,赋值给变量 data。 

# for循环遍历content_all
for comment in content_all:

    # 使用.attrs获取p对应的属性值,并赋值给data
    data = comment.attrs["p"]

    # 使用print输出data
    print(data)

        获取结果如下图

        现在我们获取到了所有 p 对应的属性值,来分析一下如何从字符串中,提取出弹幕发布时间。如下图所示,整个字符串由逗号将参数值之间隔开,弹幕时间在第一项。 

        要对字符串进行拆解,我们可以使用Python的内置函数 split(),取得第一个参数。将逗号作为分隔符传入函数中,在返回的列表中索引第一个元素,然后赋值给变量 time。 

    # 使用split()函数分割data,把第一个元素时间赋值给time
    time = data.split(",")[0]
    
    # 使用print输出time
    print(time)

        获取结果如下图

        接下来,我们将获得的发布时间存储在一起,可以新建一个列表timeList。由于获取到的分布时间是字符串,我们可以使用 float() 函数将变量 time 转换成浮点数 。接着在 for 循环中,用 append() 函数将浮点数添加进列表 timeList 中。 

# 新建一个列表timeList
timeList = []

# for循环遍历content_all
for comment in content_all:

    # 使用.attrs获取p对应的属性值,并赋值给data
    data = comment.attrs["p"]
    
    # 使用split()函数分割data,把第一个元素时间赋值给time
    time = data.split(",")[0]

    # 将time转换成浮点数,添加进列表timeList中
    timeList.append(float(time))

# 使用print输出timeList
print(timeList)

        获取结果如下图 

2.4 弹幕统计

        接下来,我们对弹幕进行分段统计,找出视频中弹幕数较多的部分。我们先对视频时间进行拆分,比如说以 60 秒为单位进行分段。在每个时间范围内进行弹幕数统计,就可以知道每个分段的弹幕数了。

        在这里可以使用字典进行统计。首先,创建一个空白字典;可以把视频以 60 秒为单位进行分段,将分段区间作为字典的键添加进字典中,对应的值设为 0。接着判断弹幕发布时间,是否在分段区间内;如果在分段区间内,就把对应的值进行累加。

        我们要分析的视频长度为22分钟,可以分为22段,我们用数学方式分别表示分段的起始值、结束值的话:起始值:60*x+1,结束值:60*(x+1),x的取值范围为 0-22。然后把各分段作为字典的键,添加进字典中,再将字典中键所对应的值设置为 0,具体代码如下。

# 新建一个字典subtitlesDict
subtitlesDict = {}

# 使用for循环遍历range()函数生成的0-24的数字
for x in range(22):

    # 将60*x+1赋值给变量start
    start = 60*x+1

    # 将60*(x+1)赋值给变量end
    end = 60*(x+1)

    # 格式化start和end
    # 用短横线相连,赋值给segment_range
    segment_range = f"{start}-{end}"

    # 将segment_range作为字典subtitlesDict的键,添加进字典中
    # 将字典中键所对应的值设置为0
    subtitlesDict[segment_range] = 0

# print输出subtitlesDict
print(subtitlesDict)

        结果如下图

        接下来,我们要判断弹幕发布时间是否在分段区间内。可以遍历字典的所有键,使用 split() 函数以短横线分隔字典的键。获取第一项也就分段区间的开始时间,赋值给变量 start_key;获取第二项也就分段区间的结束时间,赋值给变量 end_key。

        分隔好分段区间的一头一尾时间后,接下来就要对弹幕发布时间进行统计。首先 for 循环变量列表 timeList,如果弹幕分布时间在整型 start_key 和整型 end_key 之间,也就是大于等于整型 start_key,小于等于整型 end_key,就将字典中键所对应的值累加。

# for循环遍历字典subtitlesDict所有的键
for subtitle in subtitlesDict.keys():

    # 使用split()分隔字典的键获取第一项,赋值给变量start_key
    start_key = subtitle.split("-")[0]

    # 使用split()分隔字典的键获取第二项,赋值给变量end_key
    end_key = subtitle.split("-")[1]

    # for循环遍历列表timeList
    for item in timeList:

        # 如果弹幕分布时间在整型start_key和整型end_key之间
        if int(start_key)<= item <= int(end_key):

            # 将字典中键所对应的值累加
            subtitlesDict[subtitle] = subtitlesDict[subtitle] + 1

# 使用字典输出subtitlesDict
print(subtitlesDict)

        统计结果如下

 2.5 绘制折线图

        接下来我们要将这些数据进行可视化处理,绘制出折线图;在这里,视频时间分段区间作为折线图的 x 轴,分段区间内的弹幕总数作为 y 轴。

        想要生成折线图,我们需要从 pyecharts.charts 中导入 Line 模块,创建line对象,在add_xaxis()函数中,设置x轴的数据列表。在 add_yaxis() 函数中,设置数据统称和y轴的数据列表;最后使用render()函数保存图表。

# 从pyecharts.charts中导入Line模块
from pyecharts.charts import Line

# 创建一个Line对象,赋值给line
line = Line()

# 使用list()将字典subtitlesDict所有键转换成列表,传入add_xaxis()中
line.add_xaxis(list(subtitlesDict.keys()))
# 使用add_yaxis()函数,将数据统称设置为"弹幕数"
# 将字典subtitlesDict所有值转换成列表,作为参数添加进函数中
line.add_yaxis("弹幕数", list(subtitlesDict.values()))

# 使用render()函数存储文件,设置文件名为弹幕统计.html
line.render("D:/学习资料/Python/stest/弹幕统计.html")

# 使用print输出success
print("success")

         执行结果如下

        生成了折线图后,通过分析折线图中的几个高峰值。发现视频开始的一分钟和第十三分钟用户的弹幕较多。

        以下是完整代码

# 使用import导入requests模块
import requests
# 从bs4中导入BeautifulSoup模块
from bs4 import BeautifulSoup
# 从pyecharts.charts中导入Line模块
from pyecharts.charts import Line


# 将https://comment.bilibili.com/218710655.xml赋值给变量url
url = "https://comment.bilibili.com/1581931988.xml"

# 添加User-Agent参数,伪装成浏览器访问
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"}

# 将变量url作为参数,添加进requests.get()中,给赋值给response
response = requests.get(url, headers=headers)

# 将网页编码方式赋值给response.encoding
response.encoding = response.apparent_encoding

# 使用.text属性获取response内容,赋值给xml
xml = response.text


# 使用BeautifulSoup()读取xml,添加lxml解析器,赋值给soup
soup = BeautifulSoup(xml,"lxml")

# 使用find_all()查询soup中d的节点,赋值给content_all
content_all = soup.find_all(name = "d")

# 新建一个列表timeList
timeList = []

# for循环遍历content_all
for comment in content_all:

    # 使用.attrs获取p对应的属性值,并赋值给data
    data = comment.attrs["p"]

    # 使用split()函数分割data,把第一个元素时间赋值给time
    time = data.split(",")[0]
    
    # 将time转换成浮点数,添加进列表timeList中
    timeList.append(float(time))

# 新建一个字典subtitlesDict
subtitlesDict = {}

# 使用for循环遍历range()函数生成的0-24的数字
for x in range(22):

    # 将60*x+1赋值给变量start
    start = 60*x+1

    # 将60*(x+1)赋值给变量end
    end = 60*(x+1)

    # 格式化start和end
    # 用短横线相连,赋值给segment_range
    segment_range = f"{start}-{end}"

    # 将segment_range作为字典subtitlesDict的键,添加进字典中
    # 将字典中键所对应的值设置为0
    subtitlesDict[segment_range] = 0

# for循环遍历字典subtitlesDict所有的键
for subtitle in subtitlesDict.keys():

    # 使用split()分隔字典的键获取第一项,赋值给变量start_key
    start_key = subtitle.split("-")[0]

    # 使用split()分隔字典的键获取第二项,赋值给变量end_key
    end_key = subtitle.split("-")[1]

    # for循环遍历列表timeList
    for item in timeList:

        # 如果弹幕分布时间在整型start_key和整型end_key之间
        if int(start_key)<= item <= int(end_key):

            # 将字典中键所对应的值累加
            subtitlesDict[subtitle] = subtitlesDict[subtitle] + 1


# 创建一个Line对象,赋值给line
line = Line()

# 使用list()将字典subtitlesDict所有键转换成列表,传入add_xaxis()中
line.add_xaxis(list(subtitlesDict.keys()))
# 使用add_yaxis()函数,将数据统称设置为"弹幕数"
# 将字典subtitlesDict所有值转换成列表,作为参数添加进函数中
line.add_yaxis("弹幕数", list(subtitlesDict.values()))

# 使用render()函数存储文件,设置文件名为弹幕统计.html
line.render("D:/学习资料/Python/stest/弹幕统计.html")

# 使用print输出success
print("success")

三、总结

        在本实例中,我们获取了B站视频的弹幕时间,并对弹幕进行了分段统计,最后以折线图的形式进行了展示。使用到的模块包括:requests、BeautifulSoup和pyecharts。

        requests:是一个常用的 HTTP 请求库,可以方便地向网站发送 HTTP 请求,并获取响应结果。requests 模块比 urllib 模块更简洁。

        BeautifulSoup:是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间。

        pyecharts:是一个用于生成 ECharts 图表的 Python 库,其提供了丰富的图表类型,包括折线图、柱状图、散点图、饼图、地图、热力图等。它支持高度定制,你可以通过简单的 Python 代码实现丰富的数据可视化效果。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐