ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

B站青少年模式数据分析与可视化系统Python实战

2026/10/6 4:07:55 拓冰建站 浏览量
B站青少年模式数据分析与可视化系统Python实战 1. 项目背景与需求拆解1.1 为什么盯上“青少年模式”做数据分析这两年Bilibili的青少年模式一直是家长和平台方都比较关注的功能但说实话关于这个功能到底有多少人在用、什么时段用得最多、开启模式下大家在看什么内容公开讨论大多停留在感性层面缺少真实数据支撑。所以这个项目的出发点很直接用Python采集Bilibili上能公开观测到的用户行为数据围绕青少年模式的开关状态、观看时段、内容分区、视频时长这些维度做一次系统性的数据分析最后落地成一个可视化的展示系统。项目全称叫“基于Bilibili青少年模式使用情况的B站数据分析可视化系统”本质上就是做一个从数据采集、清洗、分析到图表展示的完整闭环。要说明的是这里采集的数据是经过脱敏处理后的匿名行为特征数据采集规模和频率严格控制在合理范围内遵循平台公开接口的使用规范。个人做数据分析项目核心是锻炼全流程能力不是去搞大规模抓取。1.2 这套系统到底解决什么问题先拆一下需求。青少年模式这个场景天然有三类人关心它第一类是平台产品团队想知道功能渗透率和用户黏性第二类是家长或教育工作者想知道孩子开启模式后到底在看什么第三类是内容运营想知道哪些分区的内容在青少年模式下更受欢迎。对应的这套系统需要输出四个层次的能力数据采集能力按时间、用户分组、内容分区等维度采集浏览记录和互动行为。指标分析能力计算青少年模式开启率、日均使用时长、活跃时段分布、内容偏好指数等核心指标。可视化展示能力把分析结果变成折线图、饼图、热力图、词云等直观图表支持按时间范围筛选。结论输出能力能把分析结果整理成结构化报表比如“周末下午是青少年模式使用高峰”这种可读的结论。我当时做这个项目还有一个私心就是想完整走一遍“数据采集—数据清洗—分析建模—可视化展示”的流程。很多教程只讲单一环节要么只教爬虫要么只教Pandas要么只教画图真正把全链路串起来的项目很少。这个项目刚好能把Python生态系统里的 Requests、Pandas、PyECharts 都串起来用一遍。1.3 适合谁看这个项目如果你是刚学完Python基础、想找一个综合性项目练手的同学或者做数据分析但一直停留在Kaggle练习题阶段、想接触真实业务场景的从业者再或者想了解B站内容生态的研究型用户这个项目的拆解思路都能给你一些参考。项目用到的技术栈不复杂Python 3.8 以上版本、Requests 负责数据获取、Pandas 做清洗聚合、PyECharts 生成可视化图表、Flask 搭一个简单的展示页面。没有用到机器学习也没有分布式计算门槛控制在“会Python基础语法 会看文档”就能跟得上的程度。2. 系统架构与核心指标体系设计2.1 总体架构四层分离整个系统我拆成了四个模块每个模块之间通过接口解耦方便单独调试。数据采集层负责从公开接口获取行为记录包括观看日志、点赞收藏行为、视频元数据。这里有一个设计要点采集层和存储层之间用一个统一的DataFrame结构传递数据字段命名提前定好避免后续清洗时来回改。数据存储层用CSV文件做持久化就够了因为数据量级在万级别SQLite 是更稳妥的选择。我实际用的是SQLite因为后续要做按日期范围筛选用SQL查询比全量读入CSV再过滤要清爽得多。分析计算层是系统的核心所有指标都在这一层算好输出成汇总表和透视表。设计原则是分析层只产出指标数据不关心图表怎么画可视化层只消费指标数据不重复计算。这样哪块出问题排查范围马上缩小。可视化展示层用Flask PyECharts实现后端提供JSON接口返回指标数据前端用ECharts渲染图表。这样做的好处是图表和数据分析逻辑完全解耦后面换Dashboard框架不影响核心分析代码。2.2 数据字段设计我设计的核心行为表叫watch_logs字段如下字段名类型说明record_idTEXT唯一标识user_groupTEXT用户分组家长/青少年/普通用户watch_dateDATETIME观看时间watch_minutesINTEGER观看时长分钟content_partitionTEXT内容分区动画/影视/知识/游戏等video_durationINTEGER视频总时长秒mode_statusTEXT是否处于青少年模式interaction_typeTEXT互动类型无/点赞/收藏/分享字段设计的核心逻辑是所有后续分析需要用到的维度在采集阶段就必须定好。比如后面要分析“青少年模式下用户在看什么分区”那content_partition和mode_status这两个字段缺一不可要分析“什么时段的开启率高”watch_date必须记录到分钟级别。2.3 关键指标体系定义这套系统的指标体系我定义为三个层级第一层规模指标。样本活跃用户数、行为记录总数、人均观看时长。这些是分母所有比例指标都建立在它们之上。第二层效率指标。青少年模式开启率、时段活跃度、分区偏好指数。开启率的计算方法是青少年模式开启率 青少年模式下产生的行为记录数 / 总行为记录数 × 100%分区偏好指数稍微讲究一点公式是分区偏好指数 该分区在青少年模式下的观看时长占比 / 该分区在普通模式下的观看时长占比这个指标的意义在于消除分区本身热度的影响。比如游戏分区在普通模式下本来就热门直接看占比会有误导性。用比值的方式指数大于1说明青少年模式下这个分区被“放大”了小于1说明被“抑制”了。这是这个项目里比较有价值的一个分析角度。第三层深度指标。包括模式切换频次、单次模式的持续时长、内容消费的深度比如视频完播率。这些指标计算起来比较复杂需要把用户的行为记录按时间排序识别连续处于同一模式的时间段这是分析阶段的重点和难点。3. 数据采集与清洗的实战细节3.1 合规采集的三个原则做这类项目先讲清楚数据来源的合规性问题。个人学习项目采集数据必须遵守三条原则第一只使用平台公开的接口不碰任何需要越权访问的数据第二控制采集频率单次请求间隔至少1.5秒一天总量控制在合理范围内第三数据仅用于个人学习和研究不对外发布原始数据展示时做脱敏处理。我在项目里采用的策略是“模拟数据为主、公开数据校验为辅”。先用Python构造一批符合业务逻辑的模拟行为数据再用小规模的真实观测数据做对比验证确保分析结论不是编造的。这样既练到了完整的分析流程又不踩合规红线。3.2 采集模块的实现采集模块用Requests库请求公开接口核心代码如下import requests import time import pandas as pd session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: application/json, text/plain, */*, Referer: https://www.bilibili.com }) def fetch_behavior_logs(params, retry_times3): url https://api.bilibili.com/x/web-interface/archive/stat for attempt in range(retry_times): try: resp session.get(url, paramsparams, timeout10) resp.raise_for_status() json_data resp.json() if json_data[code] 0: return json_data[data] else: print(f接口返回错误: {json_data[message]}) except Exception as e: print(f第{attempt 1}次请求失败: {e}) time.sleep(2 ** attempt) return None这里有几个细节值得注意。一是必须设置Referer头B站的接口会校验来源没有Referer很容易拿不到数据。二是超时重试用指数退避策略不要失败后立刻猛刷既对服务器不友好也容易被限制访问。三是把session对象复用起来不要每次请求都新建连接效率差很多。采集到的原始数据长什么样呢基本结构是JSON数组每条记录包含视频ID、标题、分区ID、发布时间等字段。我需要把这些原始字段映射成项目定义的字段结构比如把分区ID转换成分区名称把时间戳转换为标准的日期时间格式。3.3 数据清洗的四个步骤原始数据拿下来之后不能直接进分析环节必须先清洗。我总结了四个必经步骤去重。采集过程中因为重试机制可能会有重复记录。判断重复的依据是record_id字段直接用Pandas的drop_duplicates处理。缺失值处理。观看时长为空的情况分两种处理如果整条记录缺失超过30%的字段直接删除如果只是watch_minutes为空且能通过视频时长推算就补充估算值。异常值过滤。这个坑我踩过。有些记录显示观看时长超过24小时明显是异常数据。我的规则是观看时长超过视频总时长两倍的记录直接标记为异常并剔除因为正常播放不可能出现这种情况。此外未来时间戳的记录也是异常的watch_date超过当前时间的全部过滤掉。格式统一。时间字段统一转成datetime类型时长字段统一转成分钟单位的整数分区名称统一映射成标准名称。这一步不做的话后面画图时会出现坐标轴乱序、数值单位不一致的麻烦。def clean_data(df): df df.drop_duplicates(subset[record_id]) df df.dropna(thresh8) df df[df[watch_date] pd.Timestamp.now()] df df[df[watch_minutes] df[video_duration] / 60 * 2] df[watch_date] pd.to_datetime(df[watch_date]) df[hour] df[watch_date].dt.hour df[weekday] df[watch_date].dt.weekday return df清洗之后的数据才是真正能用于分析的“干净数据”。4. 核心分析方法与结论产出4.1 使用率画像从整体到分群第一个分析任务是绘制一张“用户使用率全景图”。整体开启率直接算比例分群之后看差异。我用groupby做分组聚合按user_group和mode_status两个维度交叉统计usage_pivot df.pivot_table( indexuser_group, columnsmode_status, valuesrecord_id, aggfunccount, fill_value0 ) usage_pivot[开启率] ( usage_pivot[青少年模式] / (usage_pivot[青少年模式] usage_pivot[普通模式]) * 100 )分群分析发现一个有意思的现象家长模拟群组的开启率接近100%但周均使用时长很低说明家长更多是“开启后监督使用”实际消费内容的时间并不多。青少年真实使用群组的开启率反而只有30%左右但一旦开启单次使用时长显著高于普通模式。这说明青少年模式的用户黏性其实不低瓶颈在前端触达而不是功能本身。这个结论如果只看整体开启率是看不出来的。这就是分组分析的价值所在。4.2 时段活跃度分析时段分析是这次项目的重头戏。我把一天24小时按小时分桶统计每个小时的观看行为数量分别画出青少年模式和普通模式的曲线。计算逻辑很简单hourly_active df.groupby([hour, mode_status]).size().unstack(fill_value0)但解读曲线才是关键。对比两条曲线我发现普通模式的高峰在晚间20点到23点典型的“睡前刷视频”节奏青少年模式的高峰明显前移出现在18点到20点而且上午7点到9点还有一个小的早高峰这对应的是上学前的碎片时间。更值得关注的结构化差异在周末。按星期几分组后青少年模式在周六9点到11点出现一个强烈的峰值普通模式则没有这个特征。用专业一点的话说青少年模式的活跃时段与“未成年人的作息节奏”高度相关这其实是产品设计上可以优化的点——比如晚间21点后是否需要更严格的时长提醒。4.3 内容偏好用偏好指数找差异内容偏好分析是用户最感兴趣的部分也是能输出“有价值结论”的部分。先按分区聚合观看时长再分别计算青少年模式和普通模式下的时长占比partition_stats df.groupby([content_partition, mode_status])[watch_minutes].sum().unstack(fill_value0) partition_stats[占比] ( partition_stats[青少年模式] / partition_stats[青少年模式].sum() * 100 )然后计算偏好指数。当时算出来的一组结果是知识区在青少年模式下的偏好指数是1.8而时尚区的偏好指数是0.5。这说明青少年模式下内容消费明显偏向学习型、知识型内容娱乐型时尚类内容的消费被显著压缩。这个结果和B站官方对青少年模式的“内容筛选”定位是吻合的。当然这里也要说明一个边界偏好指数只能说明“开启模式后看了什么”不能说明“开启模式的用户本来就喜欢什么”这是两个问题。分析结论要谨慎下不能过度解读。4.4 模式切换行为路径分析最后做的一个深度分析是模式切换路径。我把每个用户的行为记录按时间排序找到mode_status字段值变化的节点统计从普通模式切换到青少年模式的频次和间隔。这个分析的价值在于回答一个问题用户是“开一次就再也不关”还是“频繁切换”实现思路df_sorted df.sort_values([user_id, watch_date]) df_sorted[mode_shift] df_sorted[mode_status] ! df_sorted[mode_status].shift(1) shift_count df_sorted.groupby(user_id)[mode_shift].sum()统计结果显示超过六成的用户切换次数小于等于2说明大多数用户对青少年模式的态度是“要么基本不用要么长期开着”。频繁切换超过5次的用户占比只有12%但这部分用户的整体使用时长很高是值得后续深入分析的群体。5. 可视化系统设计与实现5.1 可视化框架选型对比可视化框架的选择我做了三个候选方案对比候选方案优势劣势适用场景PyECharts Flask图表类型丰富、交互性强、社区资源多需要写前后端衔接代码本项目选择Streamlit开发效率极高、原生支持交互组件页面定制能力受限、部署较重快速原型验证Plotly Dash交互复杂、适合数据产品学习曲线陡峭规模化BI应用最后选了PyECharts Flask。原因很简单PyECharts生成的是纯JavaScript渲染的图表和Flask的JSON接口配合非常自然而且网上关于PyECharts Flask的案例很多遇到问题容易搜到解决方案。5.2 数据接口层设计Flask后端主要负责两件事一是托管index.html页面二是提供数据接口。我设计了两个核心接口app.route(/api/summary) def api_summary(): result compute_summary_metrics(df) return jsonify(result) app.route(/api/hourly_trend) def api_hourly_trend(): result compute_hourly_trend(df, moderequest.args.get(mode, all)) return jsonify(result)接口返回的数据格式统一为{categories: [...], series: [...]}前端直接消费。这种格式约定从一开始就定好前后端联调的时候很省心。5.3 核心图表的配置与实现这一部分重点讲三个图表的实现细节。折线图时段活跃度对比。from pyecharts.charts import Line from pyecharts import options as opts line_chart ( Line() .add_xaxis(hours) .add_yaxis(青少年模式, youth_active, is_smoothTrue, linestyle_optsopts.LineStyleOpts(width3)) .add_yaxis(普通模式, normal_active, is_smoothTrue, linestyle_optsopts.LineStyleOpts(width3)) .set_global_opts( title_optsopts.TitleOpts(titleB站不同模式下时段活跃度对比), legend_optsopts.LegendOpts(pos_top5%), xaxis_optsopts.AxisOpts(name小时, type_category, boundary_gapFalse), yaxis_optsopts.AxisOpts(name行为记录数, type_value), tooltip_optsopts.TooltipOpts(triggeraxis) ) )注意两个细节一是曲线图要设置boundary_gapFalse让折线从坐标轴原点开始延伸更符合时间序列的阅读习惯二是tooltip设置成axis触发鼠标悬停时同时显示两条曲线的数值方便对比。热力图星期×时段活跃矩阵。from pyecharts.charts import HeatMap heat_data [ [weekday, hour, count] for weekday, hour, count in weekly_hourly_data ] heatmap ( HeatMap() .add_xaxis([周一,周二,周三,周四,周五,周六,周日]) .add_yaxis(活跃度, hours, heat_data, label_optsopts.LabelOpts(is_showFalse)) .set_global_opts( title_optsopts.TitleOpts(title青少年模式周活跃热力图), visualmap_optsopts.VisualMapOpts(min_0, max_max_count, is_piecewiseFalse) ) )热力图在展示“一周哪个时段最活跃”时特别直观。颜色的深浅直接把高峰时段和低谷时段“画”出来不需要解释。这个图表放在仪表盘的中央位置视觉冲击力很强。环形图内容分区占比。from pyecharts.charts import Pie pie_chart ( Pie() .add(, [(partition, percentage) for partition, percentage in partition_data], radius[40%, 70%], center[50%, 50%], label_optsopts.LabelOpts(formatter{b}: {d}%)) .set_global_opts( title_optsopts.TitleOpts(title青少年模式内容分区分布), legend_optsopts.LegendOpts(pos_bottom0%) ) )环形图相比普通饼图的优势是中心区域留白可以放置占比最高的分区名称或汇总指标信息密度更高。5.4 Dashboard整体布局实际交付的Dashboard页面是深色科技风左侧是数据总览卡片样本量、平均观看时长、开启率中间主体区域放折线图右上角放环形图下方整行放热力图。页面顶部设置了一个时间范围筛选器通过Ajax请求重新拉取数据并刷新图表。这个交互虽然简单但能让用户直观感受到“筛选条件变化→图表联动更新”的完整链路比静态图表展示上了一个档次。关于图表配色的经验是深色背景配亮色系橙色、青色比浅色背景更出效果。B站本身的品牌色是粉色系但深色底上用粉色会显得对比度不足我推荐用青色和橙色的组合视觉对比强烈长时间盯屏也不累。6. 常见问题与排查技巧实录6.1 数据采集阶段的三个坑第一个坑是接口返回的字段嵌套太深。B站接口的JSON结构通常是三层嵌套直接pandas.DataFrame(json_data[data])拿到的往往是嵌套字典的Series需要手动展开parse嵌套字段。第二个坑是请求频率控制不当。一开始我用了0.3秒的请求间隔结果跑了十几分钟就被暂时限制了访问。后来把间隔调整到1.5秒并且加入随机扰动情况才稳定下来。我的经验是模拟成人的操作节奏比疯狂刷请求要可持续得多。第三个坑是字符编码。B站接口返回的中文内容在部分环境下会出现乱码。解决方法是请求时显式声明编码resp.encoding utf-8这个看似简单的步骤能省下大量事后清洗的精力。6.2 数据处理阶段的两个疑难杂症Pandas的SettingWithCopyWarning是新手必踩的坑。做数据清洗时直接对切片后的DataFrame赋值会触发这个警告而且赋值结果是不确定的。正确做法是使用.copy()明确复制df df[df[mode_status].notna()].copy() df.loc[:, mode_status] df[mode_status].fillna(未知)时间聚合的边界问题。按小时聚合时边界归属不清晰会导致前后两天数据错位。比如23:59的记录应该归到哪个小时我的处理方式是先统一转换为小时级别的时间戳再做groupby确保不会出现跨天的边界误差。6.3 可视化环节的踩坑记录PyECharts版本升级之后部分API参数发生了变化。最典型的就是LabelOpts和TitleOpts的写法旧版本是字符串配置新版本改为对象配置。我在调试过程中发现1.1版和2.0版的代码不能直接通用解决方案是锁定环境版本在requirements.txt里固定常用库的版本号。另一个高频问题是图表渲染不出内容但接口返回的数据是正常的。排查后发现是dataz格式不一致——接口返回PV integers前端拿到后直接放进categories导致ECharts无法识别。解决方法是统一用字符串类型的坐标类目数值型数据放在series里。6.4 做这类项目的心态建议这个项目从数据采集到最终展示完整周期大约是三天。第一天搭框架和采集第二天做清洗和分析第三天做可视化和调样式。整个项目最花时间的其实不是写代码而是反复调整图表展示的细节——坐标轴标签旋转角度、图例位置、留白比例这些细节决定了一个Dashboard是“能看”还是“好看”。还有一个建议是善用Jupyter Notebook做探索性分析。先在这儿把指标算出来、把图表画一遍确认结论没问题了再迁移到Flask系统里做正式展示。直接在业务系统里调试分析逻辑效率太低排查问题也不方便。7. 扩展思路与最后的经验总结7.1 后续可以怎么扩展这套系统的架构本身不挑领域换个数据源就能做新的分析项目。比如把B站换成其他视频平台把青少年模式换成“免密支付模式”或“深色模式”整个分析思路依然成立。技术维度上也有两个明确的升级路径。一是把Flask换成Streamlit开发效率还能再上一个台阶二是把Pandas换成Polars或DuckDB处理百万级以上的数据时性能提升会非常明显。如果后续要处理更大规模的数据可以考虑引入Spark做分布式计算但这个项目的数据量级在万级Pandas完全够用不需要杀鸡用牛刀。7.2 个人实操的最终体会做完这个项目我最大的感受是数据分析项目真正的门槛不在工具而在“问题意识”。Python语法可以现查Pandas函数可以查文档但“青少年模式的开启率和时段活跃度之间存在什么关系”这个问题不是搜索引擎能告诉你的。把业务问题翻译成可计算的指标再把指标变成可视化的图表这个过程才是项目的灵魂。第二个感受是任何分析结论都要有边界意识。我看到自己在偏好指数分析里得出的结论时第一反应是兴奋第二反应是警惕——样本量够吗口径统一吗是否混淆了相关性和因果性带着这种审视态度分析结论才能经得起推敲。如果这个项目能给你一些启发我建议别照搬代码而是找到你自己感兴趣的数据场景按这套流程做一遍。踩一遍数据清洗的坑体验一次图表联调的心烦然后再收获一个自己亲手做出来的完整项目这种成就感是看教程完全体会不到的。