
1. 为什么专门拆一个青少年模式的分析项目1.1 青少年模式不是单纯的开关而是一套内容过滤机制我是在做B站内容数据分析时顺手点开青少年模式体验了一下发现它和很多人理解的只能看部分动漫完全不同内容池被重新组织搜索、推荐、弹幕入口都有独立的策略还会触发时长提醒。换句话说它是一套独立的内容过滤机制而不是一个简单的布尔开关。既然是一套机制就一定会产生数据。内容侧有哪些内容能被放进来、哪些被挡在外面的供给结构用户侧有谁开启、什么时候用、活跃多深、在哪里流失的行为痕迹。这些数据放在一起就意味着我可以不做主观推测而是直接量化回答B站青少年模式的真实覆盖和使用情况到底是怎么样的。这正是我用Python做这套系统的原因——一个基于Bilibili青少年模式使用情况的数据分析可视化系统本质上是把内容供给和用户行为两个观察视角接进同一张大屏让趋势、结构、偏好都能被直观呈现。1.2 这套系统到底想回答哪些问题项目启动之前我先把业务问题收敛成三句话这几句话也是后续所有指标的源头青少年模式处于什么健康状态——看启用率、活跃用户数、日均使用时长在时间维度上的变化。内容池的结构是否合理——看不同分区下可用内容的占比、高播放内容的分布判断供给端有没有明显偏科。用户偏好集中在哪些时段和内容类型——看小时维度活跃度、热门内容分区定位真实使用节奏。这三问分别对应趋势分析、内容生态分析和用户行为画像分析。项目中所有代码、图表、页面布局都围绕这三句话展开没有一句空转。1.3 什么样的读者适合照搬这套做法这个项目很适合三类人去参考。第一类是做Python数据分析方向课程设计或毕业设计的学生整套链路从采集、清洗、聚合到可视化都齐全且主题有现实意义。第二类是想建立内容平台监控看板的从业者可以把里面的指标体系和Streamlit框架直接迁移到别的平台。第三类是对青少年内容生态本身感兴趣的研究者可以重点看指标口径和解读方法代码反而是次要的。需要先说清楚的是用户侧的青少年模式开启率、活跃时长这类数据平台并不会公开提供。所以我在系统里做了双通道设计平台内容侧用公开接口采集用户行为侧用标明假设条件的仿真数据补全。这种混源方式在真实项目中很常见关键是把仿真部分标清楚避免把伪造数据当成真数据来解读。2. 数据通道设计真实公开接口加脱敏仿真数据2.1 平台侧数据B站公开API能拿到什么平台侧数据负责回答内容池长什么样。我选择的是B站公开排行榜接口不需要登录、不需要额外签名按分区参数就能拿到榜单视频元数据包括标题、分区、播放量、弹幕数等字段。有人可能会想直接用搜索接口做青少年模式可见性的对比。但据我实测搜索接口目前在未登录状态下的可用性很差非登录态做大规模抓取统计既不现实也不稳定我不建议把时间花在高耦合的逆向参数上。我的替代方案是分区结构分析把主要分区的榜单位数据抓下来按分区聚合播放量、弹幕量、视频数量观察内容供给结构在分区维度的分布情况。这里要特别强调一个边界排行榜反映的是全站的公开展示内容不能直接等同青少年模式下的内容池。但把它作为供给结构的近似样本是合理的用于判断哪些分区内容更丰富、哪些分区头部效应更强这类结构性问题参考价值依然很高。2.2 用户侧行为数据为什么必须用仿真方案用户侧数据在数据伦理上是敏感的。真实的开启率、个人观看时长、每个用户的分区偏好都属于个人行为数据。我作为一个独立开发者没有平台授权也没有合理途径拿到这些明细最稳妥的做法就是构造一份仿真数据集并且把仿真两个字写清楚。仿真不是拍脑袋我结合公开信息中平台用户规模的量级、常规内容产品的活跃时段规律、以及节假日效应来设定参数区间。例如启用率落在30%到45%之间一天内晚间20点到22点活跃度自然抬升周末比工作日高几个百分点。用这些约束生成的数据用于演示分析链路和看板效果是够用的但它不代表真实运营数据这一点要在所有输出页面上标注。2.3 数据字典与最小可复现采集代码在设计阶段我先定义了统一数据字典防止后面每张图各算各的、字段对不上。字段名类型含义数据来源bvidstring视频唯一标识B站排行榜接口titlestring视频标题B站排行榜接口partitionstring一级分区名B站排行榜接口view_countint播放量B站排行榜接口danmakuint弹幕数B站排行榜接口datedatetime统计日期本地生成total_activeint当日活跃用户数仿真数据enable_ratefloat青少年模式启用率仿真数据hourly_activedict24小时活跃分布仿真数据采集部分直接按这个字典落字段。排行榜接口的调用代码很简洁import requests import pandas as pd import time HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def fetch_ranking(rid: int 0, page_size: int 50) - pd.DataFrame: 抓取B站公开排行榜数据rid0表示全站 url https://api.bilibili.com/x/web-interface/ranking/v2 params {rid: rid, type: all, page_size: page_size} resp requests.get(url, headersHEADERS, paramsparams, timeout10) data resp.json() if data.get(code) ! 0: raise ValueError(f接口返回异常: {data.get(message)}) items data.get(data, {}).get(list, []) rows [{ bvid: it.get(bvid), title: it.get(title), partition: it.get(tname), view_count: it.get(stat, {}).get(view, 0), danmaku: it.get(stat, {}).get(danmaku, 0), } for it in items] return pd.DataFrame(rows)实际跑的时候我会把rid从0换到动画、音乐、知识、影视、生活等主要分区循环请求每次间隔0.8秒左右。一次能拿到几百条覆盖多分区的公开视频样本足够做结构分析。2.4 合规与请求频率别把接口打挂了我在第一个版本里犯过请求过快的错误结果很快触发接口限流。后来调整成三条纪律第一每个请求之间sleep 0.5秒以上第二定期更换User-Agent字符串第三只保存自己要用的字段不长时间重复抓同一批数据。采集代码跑完我直接把原始DataFrame落成CSV后续分析只读本地文件避免反复请求线上接口。这个习惯值得坚持。数据合规不是一句口号落到工程上就是能本地存就本地存能少量请求就少量请求既保护自己也保护平台服务的稳定性。3. 指标体系把使用情况拆成四类可计算的量3.1 第一层启用与活跃类指标这是整个大屏的北极星部分。最核心的是两个青少年模式启用率 启用青少年模式的用户数 / 适龄用户总数日活跃用户数 当天至少有1次页面访问行为的用户数这两个指标分别回答渗透和黏性。启用率反映有多少适龄用户认可这个功能日活跃反映启用之后是否真的在使用。如果启用率高但活跃低问题大概率出在提醒机制或内容吸引力上如果启用率低但活跃高说明存量用户忠诚度不错接下来要考虑的是扩大触达。为了让趋势更立体我在仿真数据里给每日启用率加了一个随机波动区间并叠加周末抬升效应日活跃用户数按量级线性相关。这部分逻辑全部集中在生成脚本里页面层只看最终表不关心随机过程。3.2 第二层时段与时长类指标时段信息用来回答什么时间在用。这里设计两个指标时段活跃度把一天切成24小时统计每个小时窗口内的活跃用户占当日活跃用户的比例。日均使用时长 当日累计使用时长 / 日活跃用户数。时段活跃度用热力图呈现X轴放一周七天Y轴放0点到23点颜色深浅代表活跃比例。这样一眼能看到周末的下午和晚上是不是比工作日更活跃、晚间有没有明显的高峰窗口。日均使用时长单独做成柱状图或小折线放在启用率趋势图旁边用来观察用得更久和用得更广是否同步变化。实际分析中这两者经常错位启用率平稳但时长上升说明留存用户在加深使用启用率上升但时长下降则可能是新用户拉低均值需要进一步看分用户层的数据。3.3 第三层内容生态结构类指标内容侧的核心指标围绕供给结构展开。我在真实排行榜数据上计算分区覆盖率 该分区上榜视频数 / 榜单视频总数高播放内容占比 播放量超过100万的视频数 / 该分区上榜视频数分区平均播放量 该分区上榜视频播放量的均值这三个指标合起来能说明很多问题哪些分区给内容池贡献了更多存量哪些分区虽然上榜多但头部效应严重平均播放量被少数爆款拉高。内容运营看到这类数据可以针对偏低的分区做供给补充。分区结构的聚合代码很清晰def aggregate_partition(df: pd.DataFrame) - pd.DataFrame: 按分区聚合视频结构指标 result ( df.groupby(partition) .agg( video_count(bvid, count), avg_view(view_count, mean), high_view_ratio(view_count, lambda s: (s 1_000_000).mean()), total_danmaku(danmaku, sum), ) .reset_index() .sort_values(video_count, ascendingFalse) ) return result3.4 指标计算脚本封装因为大屏上要反复挂载这些指标我在core/metrics.py里把它们封装成一个入口避免在页面文件里堆公式import pandas as pd def compute_key_metrics(daily: pd.DataFrame, hourly: pd.DataFrame) - dict: return { avg_enable_rate: daily[enable_rate].mean(), latest_active: int(daily[total_active].iloc[-1]), avg_duration: daily[total_duration].mean() / daily[total_active].mean(), peak_hour: int(hourly.mean().idxmax()), enable_rate_change: daily[enable_rate].iloc[-1] - daily[enable_rate].iloc[0], }这样一个函数返回所有北极星指标页面层只负责展示指标口径的变更只发生在这一处。后面任何人接手只需要看数据字典和这几个函数不用翻完整个前端页面。4. 可视化大屏Streamlit加Plotly的工程落地4.1 技术选型对比为什么要走Streamlit路线可视化系统有很多搭法我在动手前先做了个简单的横向对比方案优点缺点适合场景Streamlit Plotly纯Python、开发速度极快、图表交互开箱即用大屏精细定制能力弱内部看板、分析汇报、课程设计Flask ECharts前端定制自由度高、视觉效果大气需要前后端同时写、联调成本高正式对外产品大屏Jupyter Matplotlib探索分析顺手、代码量少静态图多、不适合持续观察一次性分析报告考虑到项目核心是把指标逻辑和可视化流程讲清楚而不是做一个对外运营级别的炫酷大屏我选了Streamlit。它的优势是写纯Python就能产出交互页面表格、下拉框、日期选择器都是现成组件不需要额外学前端。Plotly那一层负责产出交互式图表我目前用下来非常稳改指标后刷新页面即可不用重启服务。4.2 工程目录与模块边界整个项目的目录结构长这样bilibili_mode_analysis/ ├── app.py ├── data/ │ ├── raw_ranking.csv │ └── demo_usage.csv ├── core/ │ ├── cleaner.py │ ├── metrics.py │ └── aggregator.py └── views/ ├── overview.py ├── trend.py └── content.py数据文件放在data下core里只做清洗、聚合、指标计算views只负责把数据渲染成图表。app.py是唯一的入口负责初始化数据和路由到各个Tab。这个边界划分花了我一点时间但它直接决定了后面加需求时要不要动老代码。我的体验是如果指标计算和页面渲染混在同一个文件里加第三张图的时候一定会后悔。4.3 Dashboard布局与核心页面代码主入口按Tab组织页面结构import streamlit as st import pandas as pd from views import overview, trend, content st.set_page_config(page_titleB站青少年模式数据分析可视化系统, layoutwide) st.title(B站青少年模式使用情况数据分析系统) st.cache_data(ttl3600) def load_all(): daily pd.read_csv(data/demo_usage.csv, parse_dates[date]) hourly pd.read_csv(data/hourly_active.csv) ranking pd.read_csv(data/raw_ranking.csv) return daily, hourly, ranking daily, hourly, ranking load_all() tab1, tab2, tab3 st.tabs([总览, 趋势分析, 内容生态]) with tab1: overview.render(daily, hourly) with tab2: trend.render(daily, hourly) with tab3: content.render(ranking)总览页首屏是4个KPI卡片然后接一张趋势折线图往下是热力图和分区玫瑰图。核心图表用Plotly生成代码很直接import plotly.express as px def render(daily: pd.DataFrame, hourly: pd.DataFrame): c1, c2, c3, c4 st.columns(4) metrics compute_key_metrics(daily, hourly) c1.metric(平均启用率, f{metrics[avg_enable_rate]:.2%}) c2.metric(最新活跃用户, f{metrics[latest_active]:,}) c3.metric(峰值活跃时段, f{metrics[peak_hour]}:00) c4.metric(日均使用时长, f{metrics[avg_duration]:.2f} 分钟) fig px.line( daily, xdate, yenable_rate, title青少年模式启用率趋势, labels{enable_rate: 启用率, date: 日期} ) st.plotly_chart(fig, use_container_widthTrue)4.4 一条完整的数据流演练用总览页的启用率趋势图来走一遍完整链路CSV文件先是日期和启用率两列原始数据这是仿真环节生成的进入core/cleaner.py后做缺失值检查和日期类型转换再进入core/metrics.py计算均值最后在views/overview.py中渲染成Plotly折线图。一个指标对应一整个管道误差只可能出在三处数据生成、指标口径、展示字段名。这套结构的好处是问题定位快。有一次折线图的日期显示成数字时间戳我没有去动页面代码直接在cleaner里检查parse_dates配置一次就修好了。把变更收敛在必要层是我维护这个项目最大的体会。5. 跑起来之后六张关键图表及其业务解读5.1 总览页的KPI卡片大屏跑起来后首屏四张卡片直接给结论。我这组仿真数据的平均启用率在37%左右最新活跃用户量级在数百万上下峰值活跃时段落在晚上21点日均使用时长大约40多分钟。这四个数字放在一起第一眼就能判断当前处在什么状态启用率接近四成在内容产品里属于有明显用户基础但尚未主流化的阶段晚间高峰说明青少年模式的使用节奏和全站用户习惯基本一致。5.2 启用率趋势折线图折线图比卡片更能看出变化。我在仿真数据里特意叠加了周末和假期脉冲所以趋势线呈现出周期性锯齿——工作日平缓周末抬升遇到长假会出现一个明显尖峰。这个规律在业务上说得通使用节奏和在校时间强相关休息日集中使用。如果你换一套真实数据可以继续对比学期中与寒暑假的斜率差异这是一个相当有洞察力的分析角度。5.3 时段活跃热力图热力图是全场信息量最大的一张图。仿真数据里晚间20点到22点颜色最深周末下午14点到18点也有一片明显的暖色。这说明内容供给策略可以适当向晚间时段倾斜比如晚间多推知识类和优质动画而不是全天平均用力。这里还有一个细节值得注意如果凌晨1点到3点出现意外的高活跃区域那大概率不是真实的小用户群体而需要考虑设备共享、家长代看等异常因素。分析时段异常往往比分析时段峰值更能发现隐藏问题。5.4 内容生态玫瑰图与分区结构内容生态页用玫瑰图展示分区覆盖率数据来自真实公开接口。我实测跑到的结果是知识区、动画区、生活区上榜数量排在前列音乐区和影视区相对靠后。但高播放内容占比的排序完全不同——有些分区上榜数量不多单条爆款播放量极高说明该分区内容供给总体稀缺、头部效应严重。所以这三张图必须放到一起看单看任何一张都会得到偏颇的结论。5.5 偏好路径漏斗漏斗图用来模拟从进入青少年模式到长期留存的转化路径。我的口径是四级开启设置、当日观看、连续使用七天、主动调节偏好选项逐级收敛。仿真结果显示开启设置后当日观看比例约八成连续使用七天降到五成主动调节偏好选项只剩三成。这里能清楚看到产品还能优化的一环多数用户停留在被动接受默认过滤内容的状态真正主动定制内容偏好的比例很低。5.6 图表组合说明问题时的可信度边界我必须再次强调可信度边界平台内容结构指标来自公开接口具备参考意义用户行为类指标全部是仿真数据。所以这套大屏更适合用来演示分析方法论、验证工程链路和训练图表解读能力不能直接拿去做真实运营决策。我在页脚加一行标注就是这个原因——先标明数据性质再谈业务解读这是数据工作者的基本素养。6. 开发过程中踩到的坑和收尾建议6.1 B站接口返回code非零与字段兼容问题排行榜接口最容易踩的坑是请求频率一高接口开始返回code为负值的异常JSON。处理办法是采集层统一做code校验只要code不是0就抛错并停止本轮抓取避免把异常数据写进CSV。另外不同接口返回的播放量字段位置不一致有的在顶层有的藏在stat子对象里我建议固定用一个字段提取函数不要在每个地方各自解析。还遇到过一个隐蔽问题排行榜接口偶尔对某个分区返回空列表这不是报错而是正常返回空数据。如果不处理聚合时该分区会直接消失玫瑰图缺一块扇区。我在cleaner里对这种情况做了兜底空分区保留占位并填0值图表展示才不会缺失。6.2 热力图坐标密度与中文乱码热力图第一版做出来横坐标塞了连续日期图表密到根本看不清。后来我把X轴改成周一、周日Y轴保持0点到23点聚合逻辑用一周窗口做透视画面立刻清爽了。中文乱码是Plotly内置字体在部分Linux服务器上的老问题解决办法是显式指定系统已有的中文字体比如Microsoft YaHei或Noto Sans CJK SC。开发机上显示正常不代表部署服务器也正常字体问题一定要在目标环境提前测。import plotly.graph_objects as go fig_heat go.Figure(datago.Heatmap( zhourly_pivot.values, xhourly_pivot.columns, yhourly_pivot.index, colorscaleYlOrRd, texttemplate%{z:.1%}, textfont{size: 10} )) fig_heat.update_layout(font{family: Noto Sans CJK SC, Microsoft YaHei})6.3 Streamlit缓存导致数据不刷新这个坑特别容易被忽略。我给数据读取函数加st.cache_data后新增的CSV文件在页面上迟迟不出现原因就是缓存命中了旧文件。排查到后面我把cache_data的ttl参数设成3600秒并在侧边栏放一个刷新按钮调用st.cache_data.clear()强制清理。实际经验是缓存能大幅提升页面重刷时的响应速度但一定要留手动清理入口否则开发调试时会怀疑人生。6.4 留给后续的扩展空间这个项目目前的完成度我认为足够作为独立的数据分析案例使用。如果后续要继续扩展有两个方向性价比最高一是把CSV换成MySQL采集脚本做成定时任务让大屏变成持续更新的自动化看板二是引入弹幕文本分词和情感判断把内容生态分析从结构层面延伸到语义层面探究不同分区内容的情感分布这会是一个完全不同的研究课题。我自己做完这个项目的感受是青少年模式这个主题非常容易在讨论中滑向情绪化评价但数据给了一个特别有用的锚点——你可以不用情绪立场只用指标去观察变化。这种把模糊概念拆成可计算指标再用可视化大屏把结论讲清楚的流程放到任何平台分析项目上都成立。希望这套代码和思路能帮你把自己的数据想法真正落到画面上。