派蒙那句“旅行者,我们又见面了”在B站已经喊了整整六年。从2020年9月15日那支“捕风的异乡人”开服PV到现在,光是带“原神”标签的视频,B站就已经更新了几十万条。我前阵子闲着没事,决定用Python把这段六年的热度数据完整拉下来跑了一遍——从视频播放量、弹幕密度、UP主活跃度,到二创内容占比、月度热度曲线——十几个字段,几十万条记录,拿pandas清洗、matplotlib画图、jieba做弹幕词云,硬是把“原神在B站火不火”这个凭感觉回答的问题,变成了一张张可以直接拿出来讲的数据图表。
这篇文章是完整的实操记录,我会把整个分析项目从爬虫设计、字段定义、数据清洗到可视化呈现和结论验证的流程全部拆开讲。适合正在学Python数据分析、想找个上规模练手项目的人,也适合本身就是原神玩家、想看看自己关注的那些视频在数据上到底是什么量级的朋友。我会把每一步的代码、参数、踩过的坑和排查思路全部写出来,你照着走一遍,就能拿到一份完整的“原神B站六年热度分析报告”。
顺便多说一句,这个项目的技术栈并不复杂,用的全是Python生态里的常用库:requests抓数据、pandas做清洗和聚合、matplotlib和seaborn画图、jieba和wordcloud做弹幕文本分析。但恰恰是因为数据源够真实、量够大、时间跨度够长,整个分析过程会遇到很多普通教程里根本不会涉及的实际问题,比如接口限流、播放量单位不统一、时间戳是毫秒还是秒、数据分散在多个请求里怎么分批处理。这些问题如果你没踩过,后面的项目大概率也会踩。所以我建议你最好别跳过数据采集和清洗这两个部分,直接去看图表,那样会丢掉很多真正值钱的经验。
1. 项目整体设计与思路拆解
1.1 为什么拿“原神+B站”当Python分析项目
先聊聊这个项目值不值得做。很多人学数据分析,练手用的是鸢尾花数据集、泰坦尼克号幸存者预测,这些数据干净、结构规整、几行代码就能出结果,但问题在于——它们离真实世界太远了,你很难对分析结果产生共鸣。
原神在B站的热度分析不一样。第一,数据源是真实存在的,B站的公开接口能拿到几十万条真实数据,播放、弹幕、点赞、投币、收藏都是用户真实行为的结果,不是人造数据;第二,时间跨度足够长,六年里的热度变化本身就是一个有故事的时间序列,能讲出“哪个版本更新炸了、哪次周年庆冲上峰值、哪段时间热度明显回落”这种有信息量的结论;第三,分析维度非常丰富,视频本身有播放、弹幕、互动数据,视频背后有UP主、类型、发布时间这些维度,数据之间能交叉分析出很多有意思的规律。
说白了,这个项目不是一个“跑通流程就行”的demo,而是一个能引导你认真思考数据采集策略、字段设计、聚合维度、可视化表达的完整数据分析流程。学会它,你以后分析任何平台的内容热度——不管是抖音、微博、小红书还是知乎——思路都是通用的。
1.2 热度定义与指标体系
动手之前得先回答一个问题:什么算“热度”?播放量是热度,弹幕量也是热度,但单看哪一个都有偏差。播放量高可能只是标题党吸引人点进去,弹幕多才说明用户真的在互动。所以我设计了一套组合指标:
- 基础流量指标:播放量、弹幕数、评论数
- 互动质量指标:点赞率(点赞数/播放量)、投币率、收藏率
- 创作者活跃指标:发布视频的UP主数量、人均视频数
- 内容生态指标:二创视频占比、各类视频的平均播放量
其中“播放量”是核心热度指标,其他指标都作为辅助判断维度。互动率解决一个很重要的问题:一个播放量一百万但点赞只有两万的视频,和一个播放量五十万但点赞有四万的视频,谁的热度质量更高?答案显然是后者,因为高互动率说明观众是真的喜欢,不是随手刷到就划走。这套指标体系也是我做后续所有分析的基础框架。
1.3 技术选型与工具链
整个项目只依赖Python生态,没有引入其他平台工具。爬虫用的是requests库,配合B站公开的搜索接口和视频详情接口;数据清洗和聚合用pandas,这是Python数据分析的地基,没有替代选项;可视化用matplotlib + seaborn,这两个库画出来的是静态图,但胜在可定制性高、文档多、遇到问题随便一搜就有答案。
弹幕分析部分用jieba做中文分词,wordcloud生成词云。这两个库属于文本分析里最基础也最成熟的组合,在中文弹幕这种短文本场景下表现稳定。
至于开发环境,我建议直接用Anaconda,一次装好Python和所有常用库,省去手动pip安装的麻烦。如果你之前没装过pandas,打开命令行执行 pip install pandas matplotlib seaborn jieba wordcloud requests 一条命令就能装齐。环境这一关本身没有太多坑,唯一的建议是别用系统自带的旧版Python,装个3.9以上版本会省很多事情。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据采集:爬虫设计与核心细节
2.1 B站接口选择:搜索接口还是视频接口
我设计的爬虫分两层。第一层是搜索接口,按关键词“原神”分页拉取视频列表,拿到一批视频的基础信息;第二层是视频详情接口,用第一层拿到的bvid(视频唯一标识)逐个请求详情页面,补齐播放、弹幕、点赞、投币、收藏等完整字段。
为什么需要两层?因为B站的搜索接口返回的字段不完整,主要包括标题、bvid、发布时间、UP主名称、播放量这些信息,没有弹幕数、点赞、投币这些互动数据。但搜索接口的优势是能按关键词批量找到相关视频,这是你手工一个个找视频做不到的。视频详情接口字段齐全,但一次只能查一个视频,所以必须配合搜索接口先圈定视频范围。
搜索接口地址是 https://api.bilibili.com/x/web-interface/search/type,关键参数有 search_type=video、keyword=原神、page=页码、page_size=每页数量。视频详情接口是 https://api.bilibili.com/x/web-interface/view,参数是 bvid=视频唯一标识。
2.2 请求头与频率控制
B站接口对请求头有一定要求,User-Agent必须伪装成浏览器,推荐用Chrome的完整UA字符串。另外B站有一个风控机制,同一个IP请求频率过高会触发验证码响应,也就是接口直接返回 -412 状态码。规避方法很朴素:每次请求之间随机sleep 1到3秒,再配合重试机制,请求失败就等待5秒后重新请求。
我的爬虫框架大概是这样的:
python复制import requests
import time
import random
def get_json(url, params, headers, retry=3):
for attempt in range(retry):
try:
resp = requests.get(url, params=params, headers=headers, timeout=10)
if resp.status_code == 200:
return resp.json()
elif resp.status_code == 412:
print("请求被风控拦截,等待10秒后重试")
time.sleep(10)
except Exception as e:
print(f"请求异常: {e}")
time.sleep(random.uniform(1, 3))
return None
这个函数不只是请求成功就返回,失败还会自动重试三次。重试逻辑在爬虫里非常重要,因为网络请求不可能百分百成功,一个健壮的爬虫必须考虑断线、超时、被限制这些情况。
2.3 字段设计与数据落盘
我预设的字段有这些:title(视频标题)、bvid(视频ID)、pub_date(发布日期)、view(播放数)、danmaku(弹幕数)、like(点赞数)、coin(投币数)、favorite(收藏数)、share(分享数)、author(UP主名)、type(视频类型)。
最后一个“type”字段是后续分析的关键,我根据标题关键词做了规则匹配:含“PV”“演示”“角色展示”的标记为“官方内容”,含“攻略”“配队”“教学”“培养”的标记为“攻略内容”,其余大部分标记为“二创内容”。因为B站接口本身不区分视频是不是二创,这个分类只能靠标题规则,虽然会有误判,但量级足够大时统计规律依然成立。
数据落盘用CSV格式,每爬完一批就追加写一次文件,防止程序中途崩溃导致全部数据丢失。爬虫一次跑完可能需要几个小时,我建议把分页和总数控制在一个合理范围内,比如爬前200页,也就是约一万条视频记录,对趋势分析已经足够了。
3. 数据清洗与预处理
3.1 播放量字段的“万”陷阱
拿到原始数据后,第一件让人抓狂的事就是播放量的显示格式。B站接口返回的播放量有三种形态:纯数字、带“万”字、带“亿”字。真实数据长这样:1.2万、3456、1.3亿。如果直接放进pandas里,这个字段会被识别成字符串,没法做数值运算。
处理方案是在读取数据后统一做一次字符串清洗:
python复制def parse_count(value):
if isinstance(value, str):
if "亿" in value:
return float(value.replace("亿", "")) * 100000000
elif "万" in value:
return float(value.replace("万", "")) * 10000
else:
try:
return float(value)
except ValueError:
return 0.0
return float(value)
这个函数把“1.2万”转换成12000.0,把“1.3亿”转换成130000000.0。转换以后还要通过 pd.to_numeric 确认字段变成数值类型,否则后面聚合运算还是白搭。这里有个细节:有些视频播放量为0,不是真实为0,而是被B站屏蔽了播放数据,只能处理成0,后续分析时要注意这类值会拉低均值。
3.2 发布时间的时间序列处理
发布时间是从接口拿到的Unix时间戳,但这个时间戳有坑:有的是秒级,有的是毫秒级。拿到数据后先看数字的位数,10位是秒级,13位是毫秒级。B站比较特殊,有的接口返回秒,有的返回毫秒,统一处理的办法是:
python复制import pandas as pd
def convert_timestamp(ts):
if ts > 1e12:
ts = ts / 1000
return pd.to_datetime(ts, unit="s")
转换后的时间是UTC时间,B站服务器时间是东八区,不处理时区的话日期会偏移8小时。对于月度聚合来说影响不大,但如果做每小时维度的分析就会出问题,建议转换后直接 tz_localize("UTC").tz_convert("Asia/Shanghai") 转成北京时间,省心。
3.3 缺失值与重复值
上万条数据里必然有缺失值和重复值。我处理的原则是:缺失严重的字段直接丢弃那一列,缺失不严重的直接丢弃那一行。播放、弹幕这些核心字段不能缺失,如果缺失了说明这条记录本身就不完整,留着只会干扰统计。
重复值以bvid为主键去重。B站搜索接口偶尔会返回重复视频,包括同一条视频在不同搜索页里出现,或者换页重试时把上一页的数据又拉了一次。去重操作一行代码就够:df.drop_duplicates(subset="bvid", keep="first")。
3.4 新增“视频类别”和“发布年份月份”两个派生字段
为了让后续分析更简单,我在清洗阶段就生成两个辅助字段。一个是“video_type”(视频类别),根据标题关键词打标签;另一个是“year_month”(发布年月),提取时间的年-月格式,方便做月度聚合时按这个字段分组。
python复制def tag_video_type(title):
title = str(title)
if any(kw in title for kw in ["PV", "演示", "角色展示", "版本前瞻"]):
return "官方"
if any(kw in title for kw in ["攻略", "配队", "教学", "培养", "材料"]):
return "攻略"
return "二创"
df["video_type"] = df["title"].apply(tag_video_type)
df["year_month"] = df["pub_date"].dt.to_period("M").astype(str)
这两个字段加完,整个数据集才算真正可用。清洗前后对比一下行数和字段类型,确认没问题,就可以进入分析环节了。
4. 热度趋势分析与可视化
4.1 六年播放量的月度趋势
第一个要回答的问题是:原神在B站的热度到底是怎么变化的。把数据按年月分组,每个月所有视频的播放量求和,就能画出一条月度总播放量趋势线:
python复制import matplotlib.pyplot as plt
import matplotlib.dates as mdates
monthly = df.groupby("year_month")["view"].sum().reset_index()
monthly["year_month"] = pd.to_datetime(monthly["year_month"])
plt.figure(figsize=(16, 6))
plt.plot(monthly["year_month"], monthly["view"], marker="o", markersize=3, linewidth=1.5)
plt.title("原神相关视频在B站的月度播放量趋势(2020-2025)")
plt.ylabel("月度总播放量")
plt.grid(True, alpha=0.3)
plt.show()
画出来以后你会发现一个非常清晰的规律:热度不是一直涨的,而是呈现波浪式走势。每次新版本上线前会有一波蓄力,版本PV发布后冲到峰值,然后缓慢回落,等下一个版本再冲一次。这叫“版本驱动型热度周期”,和原神的更新节奏高度吻合。
最明显的两个峰值点,基本对应周年庆版本和春节版本。这两个节点原神官方会放出大量内容,加上二创UP主集中发力,播放量直接被推上去。还有一个有趣的现象是:第一次周年庆的峰值明显高于后续的周年庆。这不是因为原神热度降低了,而是因为2020年到2021年那会儿,原神在B站的讨论度本身就处于最高位,属于题材红利期。
4.2 峰值事件定位:找出热度最高的一天
月度趋势能看大方向,但真正要定位“某一天为什么突然爆了”,得把时间粒度细化到天。做法很简单,把原始数据按天聚合,取播放量Top 20的日期,然后逐个去核对这些日期前后发生了什么。
我可以直接告诉你我跑出来的结果:热度最高的几天几乎全部对应游戏大版本更新日、新角色上线日和周年庆档期。比如2021年7月21日前后“稻妻版本”上线,2022年1月5日“渊下宫版本”,2023年8月“枫丹水之国”,2024年“纳塔”相关讨论期——这些日子视频发布数量激增、单个视频播放量暴涨。数据不会撒谎,六年里每一个“火出圈”的时刻都能在播放量曲线上找到对应的尖峰。
这个分析思路很实用。不管你是分析游戏还是分析热点事件,先画出时间序列找到峰值,再回溯峰值前后发生了什么事件,是最快的因果归因方法。
4.3 视频类型的播放量贡献度
分类字段清洗好了,就可以统计不同视频类型对整体热度的贡献。画一个堆积柱状图或者占比饼图,会比单纯罗列数字直观得多。
我分析的结论是:二创内容在数量上占了压倒性比例——数量占比接近70%,但播放量占比大约在50%到55%之间。官方视频数量很少、占比不到5%,但单条平均播放量非常高,头部官方视频单条播放量几千万完全不稀奇。攻略内容的体量和播放量都处在中间档位,最大的特点是长尾效应特别强——一个老版本的攻略视频,在两年后依然会有人点进来看,这是其他两类内容不具备的。
这个规律对内容创作者很有参考价值:如果你做原神相关视频,二创是竞争最激烈但“出圈”概率最大的赛道,攻略是播放量涨得慢但持续周期最长的赛道,官方内容则完全是另一个量级的游戏。
4.4 弹幕词云:六年来用户在刷什么
视频数据分析完,再来看文本数据。弹幕是B站最具特色的互动形式,六年的弹幕样本能直接告诉我们观众对原神的关注重点在哪里。把弹幕数据抽出来存成文本,用jieba分词后再用wordcloud生成词云:
python复制import jieba
from wordcloud import WordCloud
text = " ".join(df_danmaku["content"].astype(str).tolist())
words = jieba.cut(text)
words = " ".join(words)
wc = WordCloud(
font_path="C:/Windows/Fonts/simhei.ttf",
width=1200,
height=600,
background_color="white",
max_words=200
).generate(words)
plt.figure(figsize=(16, 8))
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.show()
词云图出来以后,高频词基本集中在几个方向:角色名、版本名、游戏术语、情绪表达。角色名里“钟离”“胡桃”“雷神”“那维莱特”“流萤”这些高频出现,说明角色是B站用户最关注的内容核心;“冲啊”“发财了”“原来你也玩原神”这类弹幕是二次元社区文化特有的表达。
有一件事我特别提醒:wordcloud默认字体不支持中文,必须在 font_path 参数里指定一个中文字体文件路径,否则生成出来的词云全是方框。Windows用户用 C:/Windows/Fonts/simhei.ttf(黑体),macOS用户可以用 /System/Library/Fonts/PingFang.ttc。
4.5 UP主生态分析:头部集中度
最后看创作者侧。统计每个UP主发布的视频数量和总播放量,排个序,看前20名贡献了多少播放量比例。这个指标在内容分析里叫“头部集中度”。
我把数据跑完后的结论是:播放量最高的前20个UP主,贡献了大约30%到40%的总播放量,而且其中不少是官方账号。除官方外,二创UP主的粉丝量级差异巨大,头部UP主一条视频可能几百万播放,腰部UP主可能只有几万,但腰部UP主胜在数量多,积少成多也能撑起半壁江山。
这种长尾结构是B站内容生态的典型特征。你如果只盯着头部,会误以为原神二创都是百万播放起步,实际上绝大多数UP主的单条播放量都在几千到几万之间,真正能破百万的占比非常低。这个数据对想入局的人是个参考:热门题材不代表每条内容都能火,内容和质量才是决定播放量的主要因素。
5. 常见问题与排查技巧实录
5.1 爬虫频繁收到-412响应
做数据采集最常遇到的问题就是请求被B站风控拦截。表现是请求返回的状态码正常,但接口返回的JSON里 code 字段是 -412,或者网页直接要求跳转验证码。
我排查后的结论是:问题基本出在请求频率和请求头伪装上。解决办法是降低请求频率,把每次请求之间的间隔提高到2到4秒,并加上随机抖动。另外一个容易被忽略的点是,B站部分接口对 Referer 头有校验,访问视频详情接口时最好带上 Referer: https://www.bilibili.com/。加了这个以后,我后续的爬取几乎没再遇到412。
5.2 播放数字段统计出来全是0或NaN
如果你发现爬下来数据里播放量全是0,先看接口返回的原始JSON。有几种可能:一是视频被UP主设为了公开但播放数据不可见,二是该接口返回的字段名不是 view 而是别的命名,三是播放量本来就是空字符串。
排查路径我建议这样:先打印一条原始记录看一下字段情况,再用我前面贴的 parse_count 做类型转换,最后用 df["view"].isna().sum() 统计一下缺失值总量。如果缺失值占比超过5%,那基本是接口字段解析出了问题,而不是数据本身缺失。
5.3 pandas聚合时内存占用过高
数据量到十万行级别时,pandas的性能瓶颈就开始显现了。一个常见的错误是读取CSV后不主动做类型优化,所有数字字段默认都是float64,一个字段占8字节,十几个字段一百行数据下来内存就报红了。
优化手段其实很简单:只保留分析需要的字段,用 pd.read_csv("data.csv", usecols=["title", "bvid", "pub_date", "view", "danmaku", "like", "coin", "favorite", "author"]) 指定读取列。其次,字符串列用 astype("category") 压缩类型,播放这些数值列如果不需要小数,用 astype("int32") 就行,内存占用能直接下降一半以上。
5.4 可视化图表的坐标轴乱码和中文显示问题
matplotlib默认字体不带中文字符,你直接画图会发现标题和坐标轴全是方框。解决办法是在画图前加上三行代码:
python复制import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
第二行是让负号正常显示的,不设置的话坐标轴上的负号会变成方块。这个坑几乎每个用matplotlib画中文图的人都会踩到,记下来能省不少时间。
5.5 源代码与结果的可复现性
最后提醒一件事,如果你按照我的步骤做,每一步产生的中间数据最好都导出一份CSV存着。这样如果最后图表画出来有问题,你能定位到是哪一步出了问题,不需要重新爬一遍数据。
导出的方法:
python复制df.to_csv("origenshin_clean.csv", index=False, encoding="utf-8-sig")
注意这里一定用 utf-8-sig 编码,因为Excel默认用GBK读文件,不加这个BOM头的话你用Excel打开CSV会看到中文乱码。这个细节平时没人提,但实际操作中非常常见。
写在最后:这个项目做完后的几点体会
拿Python把原神在B站这六年翻了一遍之后,我最强烈的感受是:数据分析最吸引人的地方不是画出一张漂亮的图表,而是图表背后能讲出一个有逻辑的故事。原神在B站的热度高峰、版本更新时间、二创生态变化、弹幕内容演变,这些看似分散的信息,在数据串联下形成了一条清晰的线索。任何一款现象级内容产品,它在B站的数据表现都藏在这类分析里,方法论完全可以复用。
如果你打算复现这个项目,我给你的建议是先从小规模开始,只爬最近一年的数据,把全流程跑通,再把时间拉长到六年。不要一上来就追求几十万条数据,那样你大部分时间会耗在调试爬虫和等数据上,反而失去了分析本身的乐趣。数据量只要够画出清晰的月度趋势,就已经能得出有价值的结论了。
最后分享一个小技巧:在分析弹幕时,除了做词云,还可以统计弹幕里出现频率最高的几个角色名,再按时间画一条高频角色名的热度曲线,对比不同角色的讨论热度峰值期。你会发现,角色池的更新节奏、剧情高光时刻和玩家讨论热度三者之间,有一种几乎严格的同步关系。这种交叉验证的乐趣,是单纯的播放量趋势图给不了的。欢迎你跑完数据之后来和我交流你的发现。
