
做数据分析项目最怕的不是不会写代码而是没有好数据。之前刷过很多公开数据集总感觉和真实业务隔着一层直到我把目光放到B站上。这个项目用python完成了一套针对B站青少年模式相关内容的采集、清洗、分析和可视化展示系统从公开接口拿数据用pandas做处理再通过pyecharts和Flask搭成可视化大屏整体跑下来对数据分析全流程的理解比单纯刷十遍教程都深。如果你是python数据分析的初学者或者正在找毕业设计、课程项目选题可以参考下面这整套设计和实现过程从数据获取到图表落地每一步我都会讲清楚为什么这样做以及实际跑的时候踩过的坑。1. 为什么选青少年模式做数据切口选题逻辑与系统框架1.1 数据选题的真实性判断做可视化系统最怕的就是“伪需求”——拿着现成的csv文件画几张图看起来像个项目实际上没有解决任何真实问题。选B站青少年模式核心原因是它的数据边界足够清晰而且具备真实的社会关注度。青少年模式本身就是平台面向未成年人推出的基础使用功能它的内容池有明确的筛选逻辑比如以教育学习、动画、纪录片、音乐等分区为主直播和私信功能受限。这意味着我们可以围绕“青少年模式下内容的使用情况”构建一套完整的分析体系什么分区的内容供给多、什么内容互动效率高、哪些时段青少年内容播放更活跃、评论区呈现出什么样的情感倾向。数据获取层面不需要任何私有数据。B站的公开接口提供了视频基础信息、播放点赞投币收藏等互动指标、评论内容、排行榜单这些数据足够支撑分析。真正有价值的地方在于你需要把“青少年模式下的内容生态”这个概念翻译成可操作的字段和指标比如分区占比、互动率、发布时段分布、评论文本情感得分等。这个翻译过程就是数据分析里最核心的能力。1.2 系统模块划分与技术选型整个系统按数据流向分五个模块数据采集层、数据存储层、数据清洗层、分析计算层、可视化展示层。采集层负责从B站公开接口拉取数据存储层用csv和SQLite两种方式保存清洗层用pandas做标准化分析层计算衍生指标展示层用图表把结论呈现出来。技术选型上我全部基于python生态没有引入重型组件。具体如下表模块技术选型选择理由数据采集requests jsonB站接口返回JSON解析成本最低数据存储pandas SQLite数据量在万级csv够用SQLite方便查询数据处理pandas numpy行业标准处理结构化表格数据效率高文本分析SnowNLP jieba对中文评论文本友好库轻量适合教学演示可视化pyecharts Flaskpyecharts生成ECharts配置Flask轻量部署为什么不直接用Django因为这个项目的展示端只有一个大屏页面加几个路由Django的admin、ORM、中间件体系都是多余负担。Flask刚好能把Python计算能力和前端页面粘在一起学习成本也低。pyecharts则解决了纯前端ECharts需要手写大量JS配置的问题所有图表配置都能在Python里生成对数据分析背景的开发者非常友好。2. 数据采集模块从B站公开接口构建原始数据集2.1 采集目标与字段设计我把采集对象分成三个表视频信息表、评论信息表、分区排行表。视频信息表是分析的主表评论表用于文本情感分析排行表用于校验分区趋势。视频信息表的核心字段包括bvid视频唯一编号、标题、分区名称、视频时长、发布时间、播放量、点赞数、投币数、收藏数、分享数、评论数。注意不要重复存储冗余字段比如视频链接可以通过bvid拼接得到不需要单独建列。评论信息表包含评论ID、视频oid、评论内容、评论时间、点赞数。这里需要注意B站评论接口返回的层级结构比较复杂有reply一级评论和replies二级评论采集时可以先只取一级评论二级评论数量太大且质量参差对情感分析的影响可以在后续处理中消除。分区排行表用于观察青少年模式内容池中不同分区在不同时间段的供给变化主要采集分区ID、分区名称、日期、上榜视频数量。这张表可以作为视频信息表的辅助验证数据。2.2 采集代码实现与解析逻辑B站视频信息接口的调用方式很直接核心代码如下import requests import pandas as pd import time HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.bilibili.com } def fetch_video_info(bvid: str) - dict: url fhttps://api.bilibili.com/x/web-interface/view?bvid{bvid} resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() data resp.json().get(data, {}) if not data: return {} stat data.get(stat, {}) return { bvid: bvid, title: data.get(title, ), tname: data.get(tname, ), duration: data.get(duration, 0), pubdate: data.get(pubdate, 0), 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), reply: stat.get(reply, 0) }这里有几个容易被忽略的细节。接口返回的pubdate是Unix时间戳不是格式化时间需要后续用pd.to_datetime(df[pubdate], units)转换。duration单位是秒要展示成“分钟”需要在分析阶段计算。stat里的键名和中文含义要对应清楚比如favorite是收藏数不是“喜欢”的意思命名不规范很容易把自己绕晕。评论接口的参数结构稍复杂一些oid是视频的数字ID不是bvid。获取oid的方式是从视频信息接口的返回里取aid字段这是新人最容易踩的坑。评论采集的示例代码如下def fetch_comments(aid: int, page: int 1) - list: url https://api.bilibili.com/x/v2/reply params { type: 1, oid: aid, pn: page, sort: 1 } resp requests.get(url, headersHEADERS, paramsparams, timeout10) data resp.json().get(data, {}) replies data.get(replies) or [] result [] for r in replies: result.append({ comment_id: r.get(rpid), aid: aid, content: r.get(content, {}).get(message, ), ctime: r.get(ctime, 0), like: r.get(like, 0) }) return result2.3 采集策略与稳定性控制采集公开接口的数据最重要的不是代码能力而是分寸感。我在实际开发中坚持三条原则单次请求间隔不低于1秒单日采集总量控制在一万条以内只采集公开页面能看到的信息。这样既能保证数据量够用也不会对平台服务器造成压力。异常处理方面普通的try-except不够用。接口偶尔会返回code: -352这样的风控错误码或者网络超时。我的处理方案是写一个带重试机制的装饰器最多重试三次每次重试间隔递增。同时把每次成功采集的数据量、失败原因记录到日志文件方便后续回溯。提示采集公开数据用于学习研究时请控制请求频率并遵守平台相关规则。本项目所有数据均来自公开页面可见的信息不涉及任何非公开接口。数据存储我用了简单的df.to_csv()追加写入文件名按日期命名比如bili_video_20250501.csv。每天采集结束后合并一次最终形成一个全量数据集。不要小看这个设计它能让你随时回滚到某一天的数据状态避免一次误操作把整个数据集搞坏。3. 数据清洗与特征构建把播放量数字变成可解释的指标3.1 清洗流程中的细节处理原始数据采集下来之后脏数据比你想象的多。最常见的是标题里夹杂着表情符号、特殊字符发布时间字段出现0值播放量个别异常大或异常小。清洗不是简单dropna而是要逐字段判断业务合理性。我的清洗流程分成四步。第一步是去重按照bvid和comment_id两个维度去重保留最新记录。第二步是处理异常值播放量超过数据集中位数100倍以上的视频标记为异常单独存放而不是直接删除因为头部爆款视频本身就是B站内容生态的一部分不能粗暴剔除。第三步是处理缺失值分区为空的视频按“未知”填充互动数据为空的按0填充并加标记列。第四步是标准化格式统一把发布时间转成北京时间把时长转换成分钟把文本中的换行符和乱码字符去掉。这里有一条实战心得清洗过程中每做一步操作都要让数据量变化可追踪。我习惯用一个shape和info()输出记录每一阶段的行数列数确保清洗逻辑没有误伤有效数据。3.2 核心衍生指标的计算逻辑原始字段只能回答“是多少”回答不了“怎么样”。互动率、内容效率这类衍生指标才是分析的重点。互动率的计算公式是(点赞 投币 收藏) / 播放量。为什么要把这三个指标相加因为点赞代表认可投币代表强烈推荐收藏代表“以后还要看”三者共同构成用户对视频内容的真实态度。播放量只能说明“多少人点进来”互动率才能说明“多少人觉得有价值”。内容效率我拆成两个维度点赞率点赞/播放和收藏率收藏/播放分别反映内容的即时吸引力和长期留存价值。时间特征上我把发布时间拆成hour小时、weekday星期几、month月份用于分析青少年相关内容的发布和消费时段规律。代码实现如下df[pub_time] pd.to_datetime(df[pubdate], units) df[pub_time] df[pub_time].dt.tz_localize(UTC).dt.tz_convert(Asia/Shanghai) df[hour] df[pub_time].dt.hour df[weekday] df[pub_time].dt.weekday df[month] df[pub_time].dt.month df[interaction_rate] (df[like] df[coin] df[favorite]) / df[view] df[like_rate] df[like] / df[view] df[favorite_rate] df[favorite] / df[view]时间戳转换时要特别注意时区问题。B站接口返回的时间戳是UTC时间如果你不指定时区直接转得到的hour会偏移8小时导致后面分析时段分布时结论完全错误。我在这里栽过跟头后来统一用. dt.tz_localize(UTC).dt.tz_convert(Asia/Shanghai)处理。3.3 评论数据的情感倾向分析情感分析是青少年内容使用情况分析里最有洞察力的部分。想法很简单把每个视频的评论情绪量化看看哪些内容被讨论时情感更积极。我用的工具是SnowNLP它基于朴素贝叶斯训练的中文情感模型使用成本极低。核心代码from snownlp import SnowNLP df_comment[sentiment] df_comment[content].apply( lambda x: SnowNLP(str(x)).sentiments if x and len(str(x)) 2 else 0.5 )sentiments返回0到1之间的浮点数越接近1表示情感越积极0.5左右表示中性。需要注意SnowNLP的默认模型在短文本上的判断不太稳定特别是反讽、网络梗这些表达经常会把“这也太强了吧”评为负面。所以在用这个指标之前我对评论内容做了一层清洗去除纯表情评论、去除长度小于4个字的评论、去除包含明显广告关键词的评论。情感得分不能只看平均数还要看分布。我把评论按视频聚合后计算每个视频的平均情感得分、情感得分标准差。标准差大的视频说明评论两极分化严重这种内容往往带有一定争议性也值得单独拿出来看。4. 可视化大屏实现从图表到可用的分析系统4.1 大屏布局与分析叙事主线可视化不是把图堆在一起而是要让看的人按你设计的顺序理解结论。我的大屏布局采用经典的总分结构顶部放核心指标卡中间放主图两侧放辅助图。顶部指标卡展示四个核心数字采集视频总数、平均播放量、平均互动率、评论情感均值中性率。这四张卡回答“整体情况如何”。中间主图采用分区贡献占比的环形图展示青少年模式内容池中不同分区的视频数量和播放量占比回答“内容供给结构性特征”。左侧是播放量TOP10视频排行条形图和发布时段分布热力图右侧是互动率分布散点图和评论情感词云。这个布局的逻辑是先看总量再看结构然后看头部和趋势最后看文本内容。每一步都在递进追问上一个图表引起的疑问。4.2 图表的具体实现与配置细节pyecharts在0.5版本之后使用链式调用语法所有图表都可以统一用opts模块配置。以分区占比环形图为例from pyecharts.charts import Pie from pyecharts import options as opts def create_zone_pie(df): zone_data df.groupby(tname)[view].sum().sort_values(ascendingFalse) data_pair [list(item) for item in zone_data.head(8).items()] c ( Pie() .add( series_name分区播放占比, data_pairdata_pair, radius[40%, 65%], center[35%, 50%], ) .set_series_opts( label_optsopts.LabelOpts(formatter{b}: {d}%) ) .set_global_opts( legend_optsopts.LegendOpts(pos_left75%, pos_top30%) ) ) return c这里有个细节radius[40%, 65%]指的是内半径和外半径形成环形效果环形图比实心饼图更合适因为中心区域可以放置汇总数据标签。formatter{b}: {d}%会把标签格式化成“分区名: 百分比”这样图上不会出现挤成一团的数字。播放量TOP10排行用横向条形图因为视频标题普遍较长横向排列标签才能完整显示。时段分布用热力图横轴是星期纵轴是小时颜色深浅代表播放量高低。互动率散点图用横轴播放量对数刻度、纵轴互动率能清楚看到“高播放不一定是高互动”这一现象。4.3 Flask集成与图表渲染方式pyecharts生成的图表对象不能直接放到HTML里需要经过序列化。我采用的是“Python端生成图表配置JSON前端用ECharts渲染”的方案。这里有两种路径一种是pyecharts直接生成HTML片段通过iframe嵌入另一种是调用.dump_options()拿到JSON传给前端模板。我用的是后者因为它更灵活方便多个图表共用一套前端框架。后端代码如下from flask import Flask, render_template import json app Flask(__name__) app.route(/) def dashboard(): pie create_zone_pie(df_video) bar create_top_bar(df_video) scatter create_scatter(df_video) return render_template( dashboard.html, pie_jsonjson.dumps(pie.dump_options(), ensure_asciiFalse), bar_jsonjson.dumps(bar.dump_options(), ensure_asciiFalse), scatter_jsonjson.dumps(scatter.dump_options(), ensure_asciiFalse) )前端模板在div idchart-root/div这类容器上用echarts.init()初始化再setOption()填入后端传来的配置。注意dump_options()返回的是字符串一定要json.dumps转成JSON对象再传给前端直接传字符串会导致ECharts报“data is undefined”错误。提示Flask默认模板目录是templates静态资源目录是static。ECharts的JS文件放到static/js/echarts.min.js在HTML底部引用引用顺序必须放在自定义脚本之前。5. 实测复盘踩坑记录与数据结论的多维校验5.1 高频踩坑与排查链路第一个坑是接口字段变化。视频详情接口里的tname字段时有时无单独请求时正常批量请求时偶尔缺失。排查思路是单独抽查一条数据的完整JSON发现接口在视频被删除时不会报错而是返回空data。解决方案是在采集函数里判空返回不让空数据进入主流程。第二个坑是时区问题导致时段分析误差8小时。这是最隐蔽的坑因为错误结果看起来依然“合理”——播放高峰出现在早上9点到11点实际上应该是下午5点到晚上7点。排查方式是抽取已知发布时间的数据人工比对发现问题后统一加了时区转换。第三个坑是pyecharts版本不兼容。pyecharts1.9.1和pyecharts2.x的API差异很大网上的历史教程很多是0.5版本的写法。我的解决方式是锁定版本依赖在requirements.txt里固定版本号同时以官方文档为准。这点在给毕设写文档时尤其重要环境的版本漂移会让之前的分析结果失去复现性。第四个坑是图表JSON序列化的中文乱码。dump_options()返回的JSON里中文被转成\u形式直接传入前端模板会显示乱码。解决方法就是前面写的在JSON序列化时指定ensure_asciiFalse。5.2 从数据里读到的几点结论清洗完三千多条视频数据后我做了几组交叉分析。印象比较深的几个发现教育学习类和动画类内容占比确实高但纪录片类的互动率明显优于平均值说明青少年内容池里“高价值内容”和“高流量内容”并不完全重合。发布时段上周末上午的收藏率明显上升工作日晚上8点到10点的播放量大但互动率下降这个规律和青少年群体的作息高度吻合。评论文本情感分析显示以“经验分享”“知识讲解”为主要内容的视频评论情感得分普遍在0.6以上而以“二次创作”“娱乐向”为主的视频评论情感得分虽然均值不低但标准差普遍偏大。这说明知识类内容引发的讨论更一致娱乐类内容更容易出现两极分化。5.3 系统扩展方向与优化思路这套系统要往深做可以从三个方向发力。一是引入时间序列维度每天定时采集观察同一指标的变化趋势形成内容热度的动态演化分析。二是接入更多维度数据比如弹幕内容、UP主属性做用户画像和内容供给侧的关联分析。三是把情感分析模型换成基于预训练的中文模型或者在当前数据标注基础上做微调提升短文本情感判断的准确率。我个人实际跑下来最大的体会是数据分析项目的重点从来不是代码多花哨而是你能否从一堆原始字段里提炼出有解释力的指标并且用图表把这个解释链条清晰地展示出来。这个过程没有捷径多跑几遍数据多画几张错的图比对差异背后的原因成长反而比反复看教程快得多。最后说个小技巧——把所有图表的配色统一成一套色板不要在每张图里各用各的颜色。视觉大屏给人“专业感”的第一印象往往不是图表数量而是颜色是否协调。我一般只用四种主色其中两种用于强调这就够用了。