1. 为什么你需要关注B站实时在线人数?

大家好,我是老张,一个在数据分析和爬虫领域摸爬滚打了十来年的“老码农”。今天想和大家聊聊一个挺有意思的话题:怎么用Python把B站的实时在线人数给“爬”下来,并且做成酷炫的图表。

你可能会问,这玩意儿有啥用?我跟你讲,用处可大了。如果你是B站的内容创作者,了解自己视频发布时段的平台整体热度,可以帮你判断观众的活跃时间,优化发布时间。如果你是做市场分析或竞品研究的,持续追踪某个热门视频的实时观看人数变化,能直观地看到它的传播速度和生命周期。甚至,你只是想做个好玩的个人项目,看看周末晚上到底有多少人和你一样在B站“冲浪”,这也充满了探索的乐趣。

说白了,这就是一种获取公开数据并加以利用的能力。B站作为一个巨大的内容平台,其用户活跃数据本身就是一座金矿。我们不需要去破解什么高深的东西,只是学会如何友好地“敲门”,向B站官方提供的API问个好,拿到那些它愿意公开分享的数据。整个过程合法合规,核心在于理解和运用正确的工具与方法。

接下来,我会手把手带你走完整个流程:从找到那个“隐藏”的API地址开始,到用Python代码把数据“请”下来,再到把一堆冰冷的数字变成一目了然的趋势图。你会发现,即便你之前没怎么写过爬虫,跟着我的步骤走,也能轻松搞定。咱们不搞那些虚头巴脑的理论,直接上干货,代码和截图都会给到位,保证你看了就能上手操作。

2. 动手之前:准备好你的“工具箱”

工欲善其事,必先利其器。在开始写代码爬数据之前,我们得先把环境搭好。别担心,整个过程就像安装手机APP一样简单。

2.1 安装必需的Python库

我们需要三个核心的Python库:requestsjsonmatplotlib。打开你的命令行(Windows上是CMD或PowerShell,Mac或Linux上是终端),输入下面这几条安装命令:

pip install requests matplotlib

如果速度慢,可以加上清华的镜像源来加速:

pip install requests matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple
  • requests:这是我们的“网络信使”。所有向B站服务器请求数据的活儿,都靠它来完成。它用起来比Python自带的urllib库要简单直观得多。
  • json:这个库是Python自带的,不需要额外安装。B站API返回的数据,几乎都是JSON格式(一种轻量级的数据交换格式)。这个库就是用来解析这种格式的,把一串文本变成我们Python里能直接操作的字典或列表。
  • matplotlib:这是我们的“画笔”。拿到数据后,我们可以用它来画折线图、柱状图,让数据动起来、活起来。它功能强大,但入门曲线平缓,画个简单的图几行代码就够了。

除了这些,我强烈建议你使用一个顺手的代码编辑器,比如 VS Code 或者 PyCharm。它们有代码高亮、自动补全和错误提示,能让你写代码的过程舒服很多。当然,用系统自带的记事本也不是不行,但那就像是拿着小刀砍大树,效率太低啦。

2.2 理解API:数据的大门在哪里?

API,你可以把它想象成餐厅的服务员。你想知道后厨(B站服务器)里有什么菜(数据),你不能直接闯进去,而是需要告诉服务员(API)。服务员会根据你的请求,把菜端出来给你。

B站有很多公开的API接口,我们今天主要用两个:

  1. 获取全站实时在线人数https://api.bilibili.com/x/web-interface/online
  2. 获取单个视频的实时在线观看人数:这个稍微复杂点,需要视频的aid(也叫av号)和cid(分P编号)来构造请求。

怎么找到这些“服务员”呢?一个最实用的方法就是“偷师”。用Chrome或Edge浏览器打开B站任意一个视频页面,按下键盘上的 F12 键,打开“开发者工具”。然后切换到 Network(网络)标签页,接着刷新一下页面。你会看到一大堆网络请求刷刷地出现。在这些请求里,仔细找找,很可能就会发现包含“online”、“player”等关键词的请求,点进去看看它的“地址”(Request URL),那就是API的地址。通过查看它的“回复”(Response),你就能知道它返回的数据长什么样。这个方法在探索其他网站时也通用,是爬虫工程师的必备技能。

3. 实战第一步:爬取全站在线人数

好了,工具齐备,目标明确,咱们开始写第一个爬虫脚本。这个最简单,也最能让你快速获得成就感。

3.1 编写爬虫代码

新建一个Python文件,比如叫 bilibili_online.py,然后把下面的代码复制进去:

import requests
import json
import time

def get_total_online():
    """
    获取B站全站实时在线人数和观看人数
    """
    # 目标API地址
    url = 'https://api.bilibili.com/x/web-interface/online'

    # 设置请求头,模拟浏览器访问,这一步很重要,能避免被简单的反爬机制拦截
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
    }

    try:
        # 发送GET请求
        response = requests.get(url, headers=headers, timeout=5)
        # 检查请求是否成功(状态码200表示成功)
        response.raise_for_status()

        # 解析返回的JSON数据
        data = response.json()

        # 判断B站API返回的状态码(code为0表示成功)
        if data['code'] == 0:
            online_data = data['data']
            web_online = online_data['web_online']  # 全站在线人数
            play_online = online_data['play_online']  # 全站观看人数
            all_count = online_data['all_count']  # 另一种统计口径的总数

            print(f"请求时间:{time.strftime('%Y-%m-%d %H:%M:%S')}")
            print(f"全站在线人数(web_online):{web_online:,} 人")
            print(f"全站观看人数(play_online):{play_online:,} 人")
            print(f"总计(all_count):{all_count:,} 人")
            print("-" * 40)

            # 返回数据,方便后续使用
            return {
                'timestamp': time.time(),
                'web_online': web_online,
                'play_online': play_online,
                'all_count': all_count
            }
        else:
            print(f"API返回错误,代码:{data['code']}, 信息:{data.get('message', '未知错误')}")
            return None

    except requests.exceptions.RequestException as e:
        print(f"网络请求出错:{e}")
        return None
    except json.JSONDecodeError as e:
        print(f"JSON数据解析出错:{e}")
        return None

# 测试一下这个函数
if __name__ == '__main__':
    result = get_total_online()

我来解释一下这段代码的关键点:

  • 请求头(Headers):里面的 User-Agent 是告诉服务器,我们是一个“浏览器”在访问,而不是一个脚本程序。这是最基础的反爬虫绕过技巧。
  • 异常处理(try-except):网络请求充满了不确定性,服务器可能出错,网络可能中断。用 try-except 把代码包起来,能让程序更健壮,出错时给出友好提示而不是直接崩溃。
  • 数据解析response.json() 方法直接把返回的文本转换成Python字典,用 data['data']['web_online'] 这种形式就能层层取到我们想要的数字。

运行这个脚本,你就能在控制台看到类似这样的输出:

请求时间:2023-10-27 15:30:22
全站在线人数(web_online):4,101,115 人
全站观看人数(play_online):5,400,994 人
总计(all_count):32,720 人
----------------------------------------

看,是不是很简单?你已经成功从B站拿到了实时数据!

3.2 让爬虫定时运行

只获取一次数据不过瘾,我们可能想看看数据随时间的变化。这就需要让我们的爬虫每隔一段时间自动运行一次。我们可以写一个简单的循环:

import time

def monitor_total_online(interval_seconds=300, duration_minutes=30):
    """
    持续监控全站在线人数
    :param interval_seconds: 每次请求的间隔时间(秒),默认5分钟
    :param duration_minutes: 总监控时长(分钟),默认30分钟
    """
    end_time = time.time() + duration_minutes * 60
    history_data = []  # 用来存储历史数据

    print(f"开始监控,每隔 {interval_seconds} 秒获取一次数据,持续 {duration_minutes} 分钟...")
    while time.time() < end_time:
        data_point = get_total_online()
        if data_point:
            history_data.append(data_point)
        time.sleep(interval_seconds)  # 等待指定的间隔时间

    print("监控结束。")
    return history_data

# 运行监控,每5分钟获取一次,总共监控1小时
if __name__ == '__main__':
    data_history = monitor_total_online(interval_seconds=300, duration_minutes=60)

这样,程序就会在后台安静地工作,每隔5分钟抓取一次数据,并把所有数据点保存在 data_history 列表里,为下一步的可视化做好准备。你可以根据自己的需要调整间隔和总时长,但务必注意频率不要太高,避免给B站服务器造成不必要的压力,这也是一个“有道德”的爬虫应该遵守的准则。

4. 进阶挑战:获取单个视频的在线观看人数

全站数据看的是大盘,有时候我们更关心某个具体视频的热度。比如你刚发布了一个视频,肯定想知道有多少人正在同时观看。这个数据的获取稍微绕一点弯,但原理相通。

4.1 找到视频的“身份证”:aid和cid

B站用两个ID来唯一标识一个视频及其分P:

  • aid / av:视频的唯一标识。比如地址 https://www.bilibili.com/video/BV1xx411c7mD 中的 BV1xx411c7mD 是它的BV号,但API历史版本中常用的是纯数字的av号。现在我们可以通过另一个API根据BV号获取aid。
  • cid:分P编号。如果一个视频有多个部分(P1, P2),每个部分都有一个独立的cid。

如何获取它们呢?最准确的方法是通过B站官方API来查询。下面这个函数,可以根据你输入的BV号,自动获取到它的aid和第一个分P的cid

def get_aid_cid_from_bvid(bvid):
    """
    根据BV号获取视频的aid和cid
    :param bvid: 视频的BV号,如 'BV1xx411c7mD'
    :return: (aid, cid)
    """
    url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}'
    headers = {
        'User-Agent': 'Mozilla/5.0 ...'  # 和之前一样的请求头
    }

    try:
        resp = requests.get(url, headers=headers, timeout=5)
        resp.raise_for_status()
        data = resp.json()

        if data['code'] == 0:
            info = data['data']
            aid = info['aid']
            # 获取第一个分P的cid
            cid = info['pages'][0]['cid']
            print(f"视频标题:{info['title']}")
            print(f"aid: {aid}, cid: {cid}")
            return aid, cid
        else:
            print("获取视频信息失败")
            return None, None
    except Exception as e:
        print(f"获取aid/cid出错:{e}")
        return None, None

4.2 调用获取在线人数的接口

拿到 aidcid 后,我们就可以构造请求单个视频在线人数的API了。根据我的测试和经验,一个可用的接口格式如下(请注意,这个接口可能需要特定的请求头,尤其是 Referer):

def get_video_online(aid, cid):
    """
    获取单个视频的实时在线观看人数
    :param aid: 视频av号
    :param cid: 视频分P编号
    :return: 在线人数, 若失败返回None
    """
    # 接口URL,注意这里的参数拼接方式
    url = f'https://api.bilibili.com/x/player/online/total?aid={aid}&cid={cid}'

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Referer': f'https://www.bilibili.com/video/av{aid}'  # Referer非常重要,需要设置为视频页面
    }

    try:
        response = requests.get(url, headers=headers, timeout=5)
        response.raise_for_status()
        data = response.json()

        if data['code'] == 0:
            online_num = data['data']['total']
            print(f"视频 av{aid} 的实时在线观看人数为:{online_num} 人")
            return online_num
        else:
            print(f"请求失败, code: {data['code']}")
            return None
    except Exception as e:
        print(f"请求出错:{e}")
        return None

# 使用示例
if __name__ == '__main__':
    # 假设你想监控的BV号
    bvid = "BV1g4411i7mn"
    aid, cid = get_aid_cid_from_bvid(bvid)
    if aid and cid:
        online_count = get_video_online(aid, cid)

运行这段代码,你就能得到指定视频此时此刻的在线观看人数。这里有个关键点Referer请求头。它告诉服务器这个请求是从哪个页面发出来的。很多API会校验这个字段,如果不设置或设置错误,服务器可能会拒绝你的请求,返回错误数据。所以,记得把它设置成对应视频的播放页地址。

5. 让数据“说话”:用图表进行可视化

爬取到的数据,如果只是一行行数字躺在列表里,那太可惜了。数据可视化就像给数据配上了“翻译官”,能让我们一眼看出趋势、发现规律。我们用 matplotlib 这个库来画图。

5.1 绘制全站在线人数趋势图

假设我们已经通过之前的监控函数,收集到了一段时间内的数据 history_data(一个字典列表)。现在我们来画一个折线图:

import matplotlib.pyplot as plt
from datetime import datetime

def plot_total_online_trend(history_data):
    """
    绘制全站在线人数变化趋势图
    :param history_data: 历史数据列表,每个元素是一个包含'timestamp', 'web_online'等的字典
    """
    if not history_data:
        print("没有可用的历史数据")
        return

    # 从数据中提取时间和人数
    timestamps = [item['timestamp'] for item in history_data]
    web_online_counts = [item['web_online'] for item in history_data]
    play_online_counts = [item['play_online'] for item in history_data]

    # 将时间戳转换为可读的日期时间格式
    times = [datetime.fromtimestamp(ts).strftime('%H:%M') for ts in timestamps]

    # 创建画布和坐标轴
    plt.figure(figsize=(12, 6))  # 设置图片大小

    # 绘制两条折线
    plt.plot(times, web_online_counts, marker='o', label='全站在线人数(web_online)', linewidth=2)
    plt.plot(times, play_online_counts, marker='s', label='全站观看人数(play_online)', linewidth=2)

    # 添加图表元素
    plt.title('B站全站实时在线人数监控趋势', fontsize=16, fontweight='bold')
    plt.xlabel('时间', fontsize=12)
    plt.ylabel('人数', fontsize=12)
    plt.legend(loc='best')  # 显示图例
    plt.grid(True, linestyle='--', alpha=0.7)  # 添加网格线,更美观
    plt.xticks(rotation=45)  # 旋转X轴标签,防止重叠

    # 自动调整布局,防止标签被截断
    plt.tight_layout()

    # 显示图表
    plt.show()

    # 也可以保存为图片文件
    # plt.savefig('bilibili_total_online_trend.png', dpi=300)

运行这个函数,一张漂亮的折线图就会弹出来。你能清晰地看到在监控时段内,web_online(全站在线人数)和 play_online(全站观看人数)两条曲线的起伏变化。通常play_online会高于web_online,因为一个人可能同时开着多个视频页面。通过观察曲线的波峰波谷,你就能推断出B站用户整体的活跃高峰期(比如晚上8点到10点)和低谷期(比如凌晨)。

5.2 绘制单个视频在线人数波动图

对于单个视频的监控,我们可以画一个更动态的图。假设我们每隔10秒获取一次某个视频的在线人数,持续5分钟:

def monitor_and_plot_video(bvid, interval=10, duration=300):
    """
    监控单个视频在线人数并实时绘图(简化版,实际应用可能需要更复杂的实时更新)
    :param bvid: 视频BV号
    :param interval: 监控间隔(秒)
    :param duration: 总监控时长(秒)
    """
    aid, cid = get_aid_cid_from_bvid(bvid)
    if not aid:
        return

    history = []
    time_points = []
    end_time = time.time() + duration

    print(f"开始监控视频 {bvid}, 持续 {duration//60} 分钟...")

    while time.time() < end_time:
        current_time = time.time()
        online = get_video_online(aid, cid)
        if online is not None:
            history.append(online)
            time_points.append(datetime.fromtimestamp(current_time).strftime('%H:%M:%S'))
            print(f"[{time_points[-1]}] 在线人数:{online}")
        time.sleep(interval)

    # 监控结束后绘图
    plt.figure(figsize=(10, 5))
    plt.plot(time_points, history, marker='*', color='red', linewidth=1.5)
    plt.title(f'视频 {bvid} 实时在线人数变化({interval}秒间隔)')
    plt.xlabel('时间')
    plt.ylabel('在线人数')
    plt.grid(True, linestyle=':', alpha=0.5)
    plt.xticks(rotation=45)
    plt.tight_layout()
    plt.show()

这张图能非常直观地反映一个视频的“瞬时热度”。例如,在视频刚发布、被大UP主转发、或者登上热门榜单的瞬间,你可能会看到一条陡峭的上升曲线。而当视频播放趋于平稳后,曲线则会变得平缓。这对于内容创作者分析视频的“引爆点”非常有帮助。

6. 避坑指南与最佳实践

爬虫路上不会一帆风顺,我踩过的坑希望你能绕过去。这里总结几个关键注意事项:

第一,尊重规则,控制频率。 这是最重要的一条。B站的API是服务于其产品和用户的,我们的爬虫应该是“有礼貌的访客”。千万不要设置几秒一次的高频请求,这无异于对服务器进行“攻击”,可能导致你的IP被暂时封禁。对于全站数据,间隔几分钟(如300秒)请求一次完全足够。对于单个视频,间隔10-30秒也绰绰有余。在代码中合理使用 time.sleep() 是美德。

第二,伪装请求头,处理异常。 我们的代码中已经设置了 User-AgentReferer。有时候,某些API可能还需要 Cookie(尤其是涉及个人数据的接口,但我们今天用的都是公开数据接口,通常不需要)。网络请求总是脆弱的,所以一定要用 try-except 包裹你的请求代码,并做好超时设置(timeout=5),这样程序在遇到网络波动时不会卡死,而是优雅地报错并继续后续任务或重试。

第三,数据存储与持久化。 上面的例子我们把数据存在内存的列表里,程序关闭就没了。对于长期监控,你需要把数据存下来。最简单的就是写入CSV文件或者SQLite数据库。例如,每次获取到数据后,可以这样追加到CSV文件:

import csv

def save_to_csv(data_point, filename='bilibili_online.csv'):
    """
    将单个数据点追加保存到CSV文件
    """
    file_exists = os.path.isfile(filename)
    with open(filename, 'a', newline='', encoding='utf-8-sig') as f:
        writer = csv.DictWriter(f, fieldnames=data_point.keys())
        if not file_exists:
            writer.writeheader()  # 如果文件不存在,写入表头
        writer.writerow(data_point)

第四,API的变动。 网站和APP都在不断更新,API接口也可能发生变化。今天能用的接口,明天可能就返回404错误或者数据结构变了。所以,一个健壮的爬虫程序应该包含对API响应状态的检查(就像我们代码里检查 data[‘code’] 是否为0),并且当接口失效时,能给出清晰的错误提示,而不是默默崩溃。多关注你获取数据的那个页面的网络请求,如果发现原来的接口失效了,就再用F12大法,寻找新的“服务员”。

最后,我想说,技术本身是中性的。我们学习用Python爬取和分析B站在线人数,核心是掌握“获取数据-处理数据-展示数据”这一套在现代社会极其有用的方法论。这个过程锻炼了你解决问题、阅读文档(哪怕是网络请求这类“隐形文档”)、编写代码和数据分析的综合能力。希望这篇文章能成为你探索数据世界的一块敲门砖,当你看到自己写出的程序成功抓取数据并画出图表时,那种成就感就是学习编程最大的乐趣之一。

更多推荐