在信息过载与注意力稀缺的时代,内容平台的竞争早已从“有没有”转向“好不好”,再进阶到“是否被看见、被记住、被互动”。作为中国最具活力的青年文化聚集地,Bilibili 不仅承载着多元化的创作生态,也折射出Z世代用户对知识、娱乐、情感与价值的独特偏好。每天数以万计的视频涌入平台,但只有极少数能冲上热榜、引发共鸣、形成现象级传播。那么,究竟是什么决定了一个视频能否脱颖而出?是选题的敏锐度、表达的感染力,还是时长的精准把控?抑或是某种更深层的互动逻辑?

为探寻这一问题的答案,我们对 Bilibili 某日全站热榜数据进行了系统性采集与多维度分析。数据覆盖数百条热门视频,包含播放量、点赞、投币、收藏、分享、实时在线人数、视频时长、所属分区及历史登顶记录等关键字段。通过构建可视化图表——从热度分布的条形图、分区格局的环形图,到互动质量的对比柱状图,再到时长与在线人数的散点关系图——我们试图穿透表层流量,还原爆款内容背后的结构性特征。这些图表不仅是数据的呈现,更是用户行为偏好的镜像,是平台算法逻辑的外显,也是创作者策略选择的参照系。

本篇文章并非仅停留在“描述发生了什么”,更希望回答“为什么发生”以及“我们可以怎么做”。无论是刚入局的新UP主,还是寻求突破的成熟创作者,亦或是关注社区生态的研究者,都能从中获得启发:高互动率是否依赖于情感共鸣?中视频是否仍是当前最优解?哪些分区正在悄然崛起?接下来的章节将逐一展开分析,带你从数据中看见趋势,从趋势中把握机会。毕竟,在B站,每一个爆款都不是偶然,而是理解用户、尊重内容、善用规律的结果。

Bilibili 排行榜查询地址:哔哩哔哩排行榜

首先,我们找到网点数据的存储位置,然后看3个关键部分标头、负载、 预览;

标头:通常包括URL的连接,也就是目标资源的位置;

负载:对于GET请求:负载通常包含了传递的参数,有些网页负载可能为空,或者没有负载,因为所有参数都通过URL传递,这里我们可以看到一些参数配置等标签,没有进行加密;

预览:指的是对响应内容的快速查看或摘要显示,可以帮助用户快速了解返回的数据结构或内容片段,我们可以看到数据在data的list里,这里返回是有关投稿作品的相关内容;

接下来就是数据获取部分,先讲一下方法思路,一共三个步骤;

方法思路

  1. 找到对应投稿视频数据存储位置并保存到本地csv;
  2. 我们通过get请求来查询所有前100个投稿视频数据;
  3. 把稿视频数据进行可视化,并增加定时获取功能;

经过测试发现,找到对应存储投稿视频信息的位置内容也很简单,就是检索任意一个视频的关键词或者标题,我们就找到了数据对应的响应请求,接下来就是根据请求里面的关键标签进行保存即可;

第一步:利用requests库发送HTTP请求遍历所有获取到的投稿视频相关播放数据,并根据标签进行保存,另存为csv;

完整代码#运行环境 Python 3.11

import requests
import csv
import time
from datetime import datetime


class BilibiliCrawler:
    def __init__(self):
        self.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',
            'Referer': 'https://www.bilibili.com/v/popular/rank/all',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Origin': 'https://www.bilibili.com',
            'Cookie': "buvid3=2D4B09A5-0E5F-4537-9F7C-E293CE7324F7167646infoc"
        }
        self.ranking_api = 'https://api.bilibili.com/x/web-interface/ranking/v2?rid=0&type=all'
        self.online_count_api = 'https://api.bilibili.com/x/player/online/total'
        self.results = []

    def get_ranking_videos(self):
        """获取B站全站排行榜视频列表"""
        try:
            response = requests.get(self.ranking_api, headers=self.headers, timeout=10)
            data = response.json()
            if data['code'] == 0:
                return data['data']['list']
            else:
                print(f"API 返回错误: {data.get('message', 'Unknown')}")
                return []
        except Exception as e:
            print(f"获取排行榜失败: {e}")
            return []

    def get_online_count(self, bvid, cid):
        """获取视频实时在线观看人数(字符串格式,如 '1.2万+')"""
        params = {'bvid': bvid, 'cid': cid}
        try:
            response = requests.get(self.online_count_api, params=params, headers=self.headers, timeout=8)
            data = response.json()
            if data['code'] == 0:
                return data['data']['total']
            return "0"
        except:
            return "0"

    def convert_count_to_number(self, count_str):
        """将 '1.2万+' 等格式转换为数字"""
        if not count_str or count_str in ("0", ""):
            return 0
        s = count_str.replace('+', '')
        if '万' in s:
            return int(float(s.replace('万', '')) * 10000)
        else:
            try:
                return int(s)
            except:
                return 0

    def format_timestamp(self, ts):
        """将 Unix 时间戳转为 YYYY-MM-DD 格式"""
        try:
            return datetime.fromtimestamp(ts).strftime('%Y-%m-%d')
        except:
            return ""

    def run(self):
        videos = self.get_ranking_videos()
        if not videos:
            print("未获取到任何视频,程序退出。")
            return

        print(f"共获取 {len(videos)} 个视频,开始逐个查询在线人数...")

        for i, item in enumerate(videos, 1):
            bvid = item['bvid']
            cid = item['cid']
            aid = item['aid']
            title = item['title'].replace('\n', ' ').replace('\r', ' ')
            owner = item['owner']['name']
            mid = str(item['owner']['mid'])
            pub_location = item.get('pub_location', '')
            tname = item.get('tname', '')
            tnamev2 = item.get('tnamev2', '')
            duration_sec = item.get('duration', 0)  # 单位:秒
            pubdate = self.format_timestamp(item.get('pubdate', 0))

            stat = item.get('stat', {})
            view = stat.get('view', 0)
            like = stat.get('like', 0)
            coin = stat.get('coin', 0)
            favorite = stat.get('favorite', 0)
            share = stat.get('share', 0)
            danmaku = stat.get('danmaku', 0)
            reply = stat.get('reply', 0)
            his_rank = stat.get('his_rank', 0)

            video_url = item.get('short_link_v2', f'https://www.bilibili.com/video/{bvid}')

            online_count_str = self.get_online_count(bvid, cid)
            online_count_num = self.convert_count_to_number(online_count_str)

            self.results.append({
                'bvid': bvid,
                'aid': aid,
                'title': title,
                'owner_name': owner,
                'mid': mid,
                'pub_location': pub_location,
                'tname': tname,
                'tnamev2': tnamev2,
                'pubdate': pubdate,
                'duration_sec': duration_sec,
                'video_url': video_url,
                'view': view,
                'like': like,
                'coin': coin,
                'favorite': favorite,
                'share': share,
                'danmaku': danmaku,
                'reply': reply,
                'his_rank': his_rank,
                'online_count_str': online_count_str,
                'online_count_num': online_count_num
            })

            print(f"[{i}/{len(videos)}] 已处理: {title[:30]}...")
            time.sleep(0.8)

        # 按实时在线人数降序排序
        sorted_results = sorted(self.results, key=lambda x: x['online_count_num'], reverse=True)

        # 生成带时间戳的文件名:bilibili_hot_videos_YYYYMMDD_HHMMSS.csv
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        csv_file = f'bilibili_hot_videos_{timestamp}.csv'

        fieldnames = [
            'rank', 'bvid', 'aid', 'title', 'owner_name', 'mid', 'pub_location',
            'tname', 'tnamev2', 'pubdate', 'duration_sec', 'video_url',
            'view', 'like', 'coin', 'favorite', 'share', 'danmaku', 'reply', 'his_rank',
            'online_count_str', 'online_count_num'
        ]

        with open(csv_file, 'w', encoding='utf-8-sig', newline='') as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            writer.writeheader()
            for idx, row in enumerate(sorted_results, 1):
                writer.writerow({'rank': idx, **row})

        print(f"\n数据已成功保存至: {csv_file}")
        print(f"总记录数: {len(sorted_results)}")


if __name__ == '__main__':
    crawler = BilibiliCrawler()
    crawler.run()

第二步:既然一次可以正常运行,那么我们通过相对高频率的获取形式就可以得到一整天的排行榜数据变化情况和上榜视频播放量变化情况,每次采集均记录精确到秒的时间戳(crawl_time 字段),用以区分不同批次的数据。同时,系统按自然日对数据进行归档:每日生成一个独立的 CSV 文件,文件名格式为 bilibili_hot_videos_YYYYMMDD.csv。脚本在运行时会自动检测当日文件是否存在——若存在,则追加新数据;若不存在,则创建新文件并写入表头。该设计既保证了数据的连续性与可追溯性,又避免了单个文件过大;

完整代码#运行环境 Python 3.11

import requests
import csv
import time
import os
from datetime import datetime

class BilibiliDailyCrawler:
    def __init__(self):
        self.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',
            'Referer': 'https://www.bilibili.com/v/popular/rank/all',
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Origin': 'https://www.bilibili.com',
            'Cookie': "buvid3=2D4B09A5-0E5F-4537-9F7C-E293CE7324F7167646infoc"
        }
        self.ranking_api = 'https://api.bilibili.com/x/web-interface/ranking/v2?rid=0&type=all'
        self.online_count_api = 'https://api.bilibili.com/x/player/online/total'

    def get_current_filename(self):
        """返回当天的文件名,如 bilibili_hot_videos_20260309.csv"""
        date_str = datetime.now().strftime("%Y%m%d")
        return f"bilibili_hot_videos_{date_str}.csv"

    def get_ranking_videos(self):
        try:
            response = requests.get(self.ranking_api, headers=self.headers, timeout=10)
            data = response.json()
            return data['data']['list'] if data['code'] == 0 else []
        except Exception as e:
            print(f"[{self.now_str()}] 获取排行榜失败: {e}")
            return []

    def get_online_count(self, bvid, cid):
        params = {'bvid': bvid, 'cid': cid}
        try:
            response = requests.get(self.online_count_api, params=params, headers=self.headers, timeout=8)
            data = response.json()
            return data['data']['total'] if data['code'] == 0 else "0"
        except:
            return "0"

    def convert_count_to_number(self, count_str):
        if not count_str or count_str in ("0", ""):
            return 0
        s = count_str.replace('+', '')
        if '万' in s:
            return int(float(s.replace('万', '')) * 10000)
        else:
            try:
                return int(s)
            except:
                return 0

    def format_pubdate(self, ts):
        try:
            return datetime.fromtimestamp(ts).strftime('%Y-%m-%d')
        except:
            return ""

    def now_str(self):
        return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    def fetch_and_save_batch(self):
        crawl_time = self.now_str()
        filename = self.get_current_filename()
        print(f"\n[{crawl_time}] 开始采集,目标文件: {filename}")

        videos = self.get_ranking_videos()
        if not videos:
            print("未获取到视频,跳过本轮。")
            return

        batch_data = []
        for i, item in enumerate(videos, 1):
            try:
                bvid = item['bvid']
                cid = item['cid']
                aid = item['aid']
                title = item['title'].replace('\n', ' ').replace('\r', ' ')
                owner = item['owner']['name']
                mid = str(item['owner']['mid'])
                pub_location = item.get('pub_location', '')
                tname = item.get('tname', '')
                tnamev2 = item.get('tnamev2', '')
                duration_sec = item.get('duration', 0)
                pubdate = self.format_pubdate(item.get('pubdate', 0))

                stat = item.get('stat', {})
                view = stat.get('view', 0)
                like = stat.get('like', 0)
                coin = stat.get('coin', 0)
                favorite = stat.get('favorite', 0)
                share = stat.get('share', 0)
                danmaku = stat.get('danmaku', 0)
                reply = stat.get('reply', 0)
                his_rank = stat.get('his_rank', 0)

                video_url = item.get('short_link_v2', f'https://www.bilibili.com/video/{bvid}')
                online_count_str = self.get_online_count(bvid, cid)
                online_count_num = self.convert_count_to_number(online_count_str)

                batch_data.append({
                    'crawl_time': crawl_time,
                    'bvid': bvid,
                    'aid': aid,
                    'title': title,
                    'owner_name': owner,
                    'mid': mid,
                    'pub_location': pub_location,
                    'tname': tname,
                    'tnamev2': tnamev2,
                    'pubdate': pubdate,
                    'duration_sec': duration_sec,
                    'video_url': video_url,
                    'view': view,
                    'like': like,
                    'coin': coin,
                    'favorite': favorite,
                    'share': share,
                    'danmaku': danmaku,
                    'reply': reply,
                    'his_rank': his_rank,
                    'online_count_str': online_count_str,
                    'online_count_num': online_count_num
                })

                print(f"  [{i}/{len(videos)}] {title[:30]}...")
                time.sleep(0.6)

            except Exception as e:
                print(f"  处理视频出错: {e}")
                continue

        # 排序(可选)
        batch_data.sort(key=lambda x: x['online_count_num'], reverse=True)

        # 写入当天文件
        file_exists = os.path.isfile(filename)
        with open(filename, 'a', encoding='utf-8-sig', newline='') as f:
            fieldnames = [
                'crawl_time', 'bvid', 'aid', 'title', 'owner_name', 'mid', 'pub_location',
                'tname', 'tnamev2', 'pubdate', 'duration_sec', 'video_url',
                'view', 'like', 'coin', 'favorite', 'share', 'danmaku', 'reply', 'his_rank',
                'online_count_str', 'online_count_num'
            ]
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            if not file_exists:
                writer.writeheader()
                print(f"首次创建文件: {filename}")
            writer.writerows(batch_data)

        print(f"[{datetime.now().strftime('%H:%M:%S')}] 本轮完成,新增 {len(batch_data)} 条记录")

    def run(self):
        print("启动 Bilibili 每日分表爬虫(每5分钟运行一次)")
        print("按 Ctrl+C 可随时停止\n")

        try:
            while True:
                self.fetch_and_save_batch()
                print("等待 5 分钟后下一轮...\n")
                time.sleep(300)  # 300秒 = 5分钟
        except KeyboardInterrupt:
            print("\n用户中断,程序已停止。")
        except Exception as e:
            print(f"\n异常: {e}")

if __name__ == '__main__':
    crawler = BilibiliDailyCrawler()
    crawler.run()

这里有一个tips:现在这里的time.sleep(300),也就是5分钟的获取频率,可以根据需求调整,虽然是get请求,但是也不建议太频繁的请求频率;

获取数据标签如下:rank(实时热度排名)、bvid(视频唯一标识符)、title(视频标题)、owner_name(UP 主昵称)、pub_location(视频发布时的地理位置)、tname(视频所属的一级分区名称)、tnamev2(视频所属的二级细分标签)、pubdate(视频的发布时间)、duration_sec(视频时长)、video_url(视频跳转链接)、view(累计播放量)、like(点赞数)、coin(投币数)、favorite(收藏数)、share(分享次数)、danmaku(弹幕数量)、reply(评论数)、his_rank(历史最高全站排名)、online_count_str(当前实时在线观看人数),其他一些非关键标签,这里省略;

接下来,我们进行看图说话:

本次分析基于 2026年3月9日某个时刻获取的 Bilibili 全站热门视频榜单数据,聚焦于“实时在线观看人数”这一核心指标。该指标反映的是在数据采集瞬间,正在观看某视频的用户数量,是衡量内容即时热度与用户注意力集中度的最直接依据。排名前十的视频中,在线人数从 22,000 到 3,000 不等,头部效应显著——第一名《抄作业就能活:伊朗接下来怎么打》的在线人数几乎是第二名的 1.6 倍,凸显了热点事件对用户注意力的强大吸附能力。

这一瞬时快照不仅揭示了“谁最火”,更折射出 Bilibili 用户在特定时间点的兴趣焦点。榜单内容横跨国际局势分析、社会情感话题、新番导视、悬疑短剧等多个领域,说明平台生态具备高度多样性。同时,部分视频虽未占据榜首,但因历史上曾登顶全站第1名(如“不该让爷爷看B站的”),仍被系统标记,暗示其长期影响力。需要强调的是,此排名仅代表该采样时刻的热度状态,并不反映累计播放量或长期受欢迎程度,因此更适用于观察短期舆论风向与内容爆发力。

从当前实时数据来看,Bilibili 平台的用户注意力呈现出明显的多元化与结构性特征。其中,“人文历史”以 22.4% 的占比位居第一,成为最具吸引力的内容类别,反映出用户对社会议题、历史解读和现实热点的高度关注;紧随其后的是“小剧场”(11.6%)和“搞笑”(9.38%),分别代表了剧情化短视频和轻松娱乐内容的强大号召力。这些头部分区共同构成了平台主流内容生态的核心支柱。

值得注意的是,“其他”类别占据了高达 27.9% 的份额,说明仍有大量细分领域(如科技、影视、生活等)未能在前八名中集中体现,显示出 Bilibili 内容生态的丰富性与长尾特性。此外,动漫杂谈、社科法律心理、单机游戏等垂直赛道也拥有稳定受众,整体分布呈现出“头部集中、腰部多元、长尾广泛”的格局。这一结构表明,B站不仅是一个二次元社区,更已成为涵盖知识、娱乐、文化与社会讨论的综合性内容平台。

从当前数据来看,Bilibili 平台的头部视频在互动表现上呈现出显著的“情感驱动”与“话题引爆”双重特征。其中,《今天是周总理诞辰128周年……》以高达 52.3% 的点赞率 断层领先,远超其他视频,反映出该内容具有极强的情感共鸣力和价值认同感。尽管其投币率仅为 9.5%,评论与弹幕率几乎为零,说明用户更多是以“点赞”这一最直接的方式表达敬意与支持,而非参与讨论。这种“高赞低评”的模式常见于重大社会议题或怀旧类内容,体现了平台用户在特定情境下的集体情绪表达。

相比之下,其他视频则表现出更均衡的互动结构。例如《我好像买到了一只星期猫》在点赞率(12.7%)和投币率(10.8%)上均表现突出,说明其内容兼具娱乐性与深度,能够激发用户的正向反馈与收藏意愿;而《不该让爷爷看b站的》虽点赞率略低(11.3%),但评论率(0.3%)相对较高,显示出更强的社交传播潜力。整体来看,头部视频普遍具备“高互动率”特征,尤其在点赞与投币方面表现强劲,表明优质内容不仅能吸引观看,更能引发用户的深度参与与情感投入。

从当前数据来看,Bilibili 平台的热门视频在播放时长与实时在线人数之间呈现出明显的 “中短时长主导” 趋势。绝大多数高人气视频集中在 0~30分钟区间,其中10~20分钟的内容尤为密集,显示出该时长段在用户注意力留存和平台推荐机制中的核心地位。虽然存在个别超长视频(如接近60分钟)达到较高在线峰值,但其数量稀少且分布零散,难以形成稳定影响力。这表明平台用户更倾向于观看节奏紧凑、信息密度高的内容,而长时间内容虽有受众,但整体传播效率相对较低。

在线人数与视频时长之间并不存在强正相关关系。部分时长不足5分钟的短视频也能吸引超过1万的实时在线,说明优质内容(如热点事件、情感共鸣类)能在极短时间内迅速引爆流量。同时,许多时长超过20分钟的视频在线人数却未突破5千,反映出“时间越长=越受欢迎”的假设并不成立。真正驱动用户停留的关键因素是 内容质量、话题热度与完播率,而非单纯时长,以最大化传播效率与互动潜力。

文章仅用于分享个人学习成果与个人存档之用,分享知识,如有侵权,请联系作者进行删除。所有信息均基于作者的个人理解和经验,不代表任何官方立场或权威解读。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐