尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于大数据的B站青少年模式数据分析系统设计与实现

基于大数据的B站青少年模式数据分析系统设计与实现 最近正好把“基于大数据的Bilibili青少年模式使用情况的数据分析系统”这套东西完整过了一遍。这个题目单看很像毕业设计里的“经典款”但真正动手做的时候会发现它横跨数据采集、离线清洗、指标建模、Web可视化和部署交付每一步都藏着细节坑。整套系统以B站公开数据和青少年模式相关的内容表现作为分析对象用大数据技术完成数据采集、存储、计算和可视化最终形成一份能跑起来、能写进论文、也能现场演示的完整项目附带源码、lw文档、部署文档和讲解材料尤其适合计算机、大数据专业的学生做课题参考或二次开发。下面把我自己的实现过程、选型思路和踩坑记录完整梳理一遍给准备做同方向的人一些实际参考。1. 项目整体设计与思路拆解1.1 需求背景与目标定位青少年模式现在已经是各视频平台的标配功能B站自然也不例外。很多用户只知道开了青少年模式之后“内容变干净了”但到底哪些内容会被过滤、不同关键词下的结果差异有多大、内容生态呈现什么趋势很少有人用数据去量化。这套系统做的事情就是把这些“看不见的现象”变成一组可比较的数据指标。一开始做需求分析的时候我把问题收敛成三个数据从哪来数据怎么算结果怎么展示。围绕这三个问题项目被拆成采集模块、存储模块、分析模块和展示模块。最终目标不是做一个多复杂的商业平台而是形成一个完整闭环周期性采集公开数据清洗后入数仓用离线计算跑出指标最后在Web页面上用图表呈现青少年模式下的内容分布和趋势变化。这个定位决定了项目不会失控。很多毕设项目容易越做越大今天加个推荐算法明天加个实时计算最后连部署都跑不起来。我的经验是先把核心链路打通再谈花活。数据分析系统最怕的是数据没跑通就开始写前端页面后面返工成本极高。1.2 数据流设计与系统模块划分整个系统的数据流可以分成四层理解这也是论文里很好写的内容。第一层是采集层。通过B站网页端的公开接口按关键字和分区抓取视频元数据、UP主信息、标签信息和评论信息保存成原始JSON数据文件。为了保证数据新鲜度我加了一个定时触发逻辑每天固定时间跑一次增量采集。第二层是存储层。原始数据先落到本地文件再统一导入到Hive表里。为了兼顾快速查询和毕设演示我同时把聚合结果同步到MySQL方便Web后端直接查。存储层分成ods层和dwd层ods放原始数据dwd放清洗后的明细数据这样职责清晰写论文时也有东西可写。第三层是分析层。使用SparkSQL完成离线计算核心是几个维度的指标视频数量、播放均值、点赞收藏转化、分区占比、高频标签词、内容时效性等。分析结果输出为汇总表同时生成JSON格式的中间文件供可视化页面读取。第四层是展示层。采用Flask作为后端框架Jinja2模板渲染页面ECharts负责图表呈现。访问页面时通过Ajax请求后端接口拿到JSON数据前端做图表渲染。整个Web部分非常轻量不需要Node环境部署省心很多。这套分层设计最大的优点是每一层都可以独立测试。采集层写完先用命令行跑一次看输出文件存储层用SQL单独验证数据量分析层单独跑指标脚本看结果是否合理展示层只用一份固定的JSON也能调试样式互相之间不形成强耦合。1.3 技术栈选型背后的理由很多人在毕设选型时会犯一个毛病什么热门用什么结果环境装三天。我这套项目最后确定的技术栈是Python 3.8 Requests Selenium Pandas Hive SparkSQL MySQL Flask ECharts。选择Python做采集层是因为它处理JSON最方便B站接口返回的正好是JSON结构写起来够快。Hive和SparkSQL是为了体现“大数据”的技术要素。虽然这个项目的数据量可能只有几十万条单机MySQL也能跑但论文里如果没有HDFS、Hive、Spark这些词题目里的“大数据”就会显得空洞。所以哪怕数据量不大我也把整个流程接到了Hive上用SparkSQL做离线分析。Web端用Flask而不是前后端分离原因很简单Flask渲染模板足够快部署时只需要一个Python进程不容易出幺蛾子。ECharts的图表类型丰富社区案例多饼图、折线图、词云、地图样样都有可以节省大量前端开发时间。2. 核心技术点与工具选型解析2.1 数据采集公开接口与请求策略B站网页端有大量公开的数据接口比如搜索接口、视频详情接口、UP主主页接口、热门推荐接口等。我用浏览器开发者工具观察请求拿到关键的URL和参数再用requests模拟请求获取JSON数据。这里涉及到一个核心操作如何体现“青少年模式使用情况”。我的做法是准备两个账号场景一个开启青少年模式一个不开启在相同搜索关键词和相同时间窗口下分别请求推荐结果和搜索结果然后对比两边的视频集合差异。这样做完全基于自己的账号和公开接口不涉及任何非公开能力也不违反平台规则论文里的表述也站得住脚。采集模块的代码结构大概是这样的一个base_spider.py负责公共请求方法包括UA组合、cookie管理、请求重试和JSON解析search_spider.py负责搜索词采集recommend_spider.py负责推荐流采集comment_spider.py负责评论采集。每个爬虫都输出独立的JSON文件文件名带上日期和模式标识例如data/raw/search_normal_20250301.json和data/raw/search_teenage_20250301.json。当时的代码伪代码如下import requests import json import time from config import HEADERS, COOKIES def fetch_data(url, paramsNone): resp requests.get(url, headersHEADERS, cookiesCOOKIES, paramsparams, timeout10) if resp.status_code 200: return resp.json() else: time.sleep(5) return None请求间隔建议控制在2到5秒之间每次采集任务不要超过10分钟。连续高频请求会触发平台风控这个后面单独说。2.2 存储与计算Hive、MySQL和Spark的分工数据量不大时纯MySQL加Pandas完全够用但这个项目的题目是“基于大数据”所以我把主存储和分析放到了Hive和SparkSQL上。具体分工是这样的原始JSON文件解析成CSV后用LOAD DATA INPATH导入到Hive的ods_video_info表清洗时通过SparkSQL读取ods表做去重、过滤、时间格式统一写入dwd_video_clean表然后按分析维度做GROUP BY聚合把结果表同步到MySQL供Web端使用。Hive建表语句里需要注意处理字段类型比如发布时间用string保存原始时间戳稍后在清洗阶段用from_unixtime统一成标准时间。视频时长字段经常会出现“01:30”这样的文本格式转为秒数时需要解析字符串。评论数、弹幕数这些字段在接口返回里有些是字符串类型要用cast做类型转换否则后面算均值时会报错。MySQL里我建了三张表agg_keyword_trend、agg_category_distribution、agg_content_feature。Web后端只查这三个聚合表接口响应时间基本都在几十毫秒内演示时会很流畅。2.3 可视化ECharts Flask这种组合为什么省心可视化最怕的是图表组件引了一堆依赖结果页面白屏。我选ECharts的5.x版本直接离线引入echarts.min.js不做按需加载省去编译步骤。Flask端只提供JSON接口和页面模板接口通过jsonify返回DataFrame转成的字典结构。前端的整体结构是单个dashboard.html页面顶部放几个统计卡片中间区域放折线图、饼图下方增加一个热力图和一个高频词列表。页面加载时先请求/api/overview拿到总览数据再请求/api/trend拿到趋势数据最后用/api/category和/api/keywords填充其他图表。图表初始化放在window.onload回调里避免DOM未加载完成时绑定失败。这套组合的好处是前后端调试非常直观。我可以先把分析结果写成一个固定JSON文件放在Flask的静态目录里让前端引用等后端接口完成后再替换成真正的接口页面效果不会受后端进度影响。3. 实操过程从零实现数据分析系统3.1 搭建初始数据集建表和采集脚本先做数据库和表结构设计。Hive表结构我设计成如下格式CREATE TABLE ods_video_info ( video_id STRING, title STRING, category STRING, pub_time STRING, play_count BIGINT, like_count BIGINT, favorite_count BIGINT, comment_count BIGINT, up_name STRING, up_level INT, tags STRING, mode_flag STRING, dt STRING ) PARTITIONED BY (dt STRING) STORED AS ORC;分区的价值非常明显后续增量导入时只需要指定日期分区不会越积越多。模式标识mode_flag用normal和teenage区分两组场景是后续所有对比分析的基础字段。采集脚本运行一次后会生成类似下面这样的JSON结构。用json.loads解析后把关键字段压成一行再把多个文件合并成CSV。CSV导入Hive比一个个JSON文件方便很多。python src/spiders/collect_search.py --keyword 动漫 --mode teenage --pages 5 python src/spiders/collect_search.py --keyword 动漫 --mode normal --pages 5这里有个小技巧每个关键词都尽量保证两种模式采集的页数一致否则后续做占比对比时会出现样本偏差。我在脚本里强制要求页面数一致输入参数写一个pages两种模式共用。3.2 数据清洗环节从原始数据到可用明细原始数据里常见的问题是重复、字段缺失和异常值。B站搜索结果里同一个视频可能被多个关键词召回所以去重逻辑不能只看标题要根据video_id去重否则同一视频会被算多次。对于空值处理我的规则是如果标题为空直接删除如果发布时间为空那么用采集当天日期补上如果播放量为空用0填充而不是删除。这样能最大限度保留样本。清洗的SparkSQL脚本片段大致是这样INSERT OVERWRITE TABLE dwd_video_clean PARTITION(dt20250301) SELECT video_id, title, category, from_unixtime(cast(pub_ts as bigint), yyyy-MM-dd HH:mm:ss) as pub_time, play_count, like_count, favorite_count, comment_count, up_name, up_level, tags, mode_flag FROM ods_video_info WHERE dt20250301 AND video_id IS NOT NULL AND title ! AND length(title) 2跑完后我会在SparkSQL里做几条简单的验证SQL查每类模式的视频总数、检查是否有重复video_id、统计空值数量。这些语句对论文里的数据质量分析部分很有用。3.3 核心分析指标和对比维度的设计分析模块是整个系统的灵魂。我当时把指标分成四组第一组是内容覆盖度指标包括视频总数、涉及的分区数量、标签总数用来反映青少年模式下内容库的丰富程度。第二组是内容热度指标包括平均播放量、平均点赞量、点赞播放比、收藏播放比用来对比两个模式的推荐热度差异。第三组是内容结构指标统计不同分区的视频占比观察哪些分区在青少年模式下占比明显变化。第四组是时效性指标计算视频发布时间和采集时间的间隔中位数看模式之间是否存在内容更新延迟的差异。其中点赞播放比这个指标很有参考价值。比如正常模式下某个关键词下的平均点赞播放比可能是8%而青少年模式下的均值是5%这就说明两个模式下推荐的内容在用户互动倾向上存在差异。计算时用like_count / play_count在SparkSQL里直接算就行但要过滤掉播放量为0的记录否则会出除零错误。聚合结果输出成JSON接口返回格式统一为{ date: 2025-03-01, mode: teenage, category: 动画, video_count: 126, avg_play: 45000, like_ratio: 0.06 }3.4 Web可视化模块的实现细节Flask端的路由设计非常简洁主要注册了三个接口app.route(/) def dashboard(): return render_template(dashboard.html) app.route(/api/overview) def overview(): # 返回总卡片数据采集总量、模式数量差、分区覆盖等 pass app.route(/api/category) def category_dist(): # 返回饼图数据不同模式下的分区占比 pass前端ECharts初始化时我会设置统一的color列表让正常模式和青少年模式分别用蓝色和橙色表示。因为图表要做对比图例必须明确说明是哪个模式。如果只是把两组数据混在一起画观看者根本看不出差异。页面加载后卡片区显示采集视频总量、参与分析的分区数量、平均播放增幅比例等基础数字。饼图展示青少年模式下占比最高的5个分区。折线图展示不同搜索关键词下两种模式的推荐数量变化。由于ECharts的默认标题不支持换行我一般把标题放在HTML页面上用普通文本展示图表本身只放内容。这样减少很多样式调整的麻烦。4. 常见问题与排查技巧实录4.1 采集过程中请求频率过高触发风控这个问题几乎必然遇到。有一段时间我在测试接口时每秒请求两次结果不到三分钟就返回一个412状态码页面提示“请求异常”。排查后发现是请求频率太高触发了风控机制。解决办法是降低请求频率把每次请求后的延迟提高到2到5秒并且加上随机抖动。另一个关键点是不要用一个cookie连续打一整天最好隔一段时间重新登录一次更新cookie后换一个用户代理继续采集。对毕设数据量来说每天采集几千条已经足够完全不需要跑高频。还有一个细节是异常重试要有最大次数限制我设置最多重试3次超过3次就跳过当前关键词记录到日志里。因为有些关键词搜索结果本身就少不值得反复浪费时间。4.2 数据重复和指标统计口径不一致采集了多轮数据后会发现同一video_id在连续几天的数据里反复出现。如果做的是“事件全量统计”不去重会导致计数虚高。我的做法是在清洗层做全局去重保留第一次出现的记录。如果做“增量趋势分析”则保留每天的分区统计不跨越日期整体去重。这里还要注意时区问题。B站接口时间一般返回的是Unix时间戳我在清洗时统一转成北京时间。如果不转换一天的边界会偏移8小时日统计曲线会整体扭曲。遇到这种问题排查思路是先看原始时间戳再看转换后的时间最后核对当天0点的数据分布。4.3 部署到新环境后浏览器样式丢失在本地开发时页面一切正常部署到一台全新的服务器后打开页面发现ECharts图表不加载样式错乱。这个问题的根本原因是静态文件路径写的是绝对路径/static/echarts.min.js而部署时项目被挂载到了子路径下导致资源请求404。解决方法是使用Flask的url_for(static, filenameecharts.min.js)生成资源地址保证动态拼接正确的访问路径。另外echarts.min.js要确认是完整下载不能是网页源码复制出来的半截文件。曾经遇到过一个2MB的JS文件复制成只读到了800KB图表初始化一直报ECharts is not defined排查很久才发现是文件不完整。4.4 快速自查清单我整理了一份自查清单每次部署后先跑一遍能减少90%的低级问题具体包括MySQL和Hive的连通性、采集脚本生成的JSON是否为合法格式、清洗后的数据量是否和源数据量有合理落差、Flask接口返回状态码是否为200、静态资源是否可访问。把这些检查写成Shell脚本输出结果部署时一目了然。5. 源码组织、论文文档与部署交付5.1 源码目录和模块怎么划分项目源码目录我是这样组织的project/ ├── src/ │ ├── spiders/ # 采集相关代码 │ ├── etl/ # 数据清洗和入库脚本 │ ├── analysis/ # SparkSQL分析脚本 │ └── web/ # Flask后端和模板 ├── sql/ # Hive建表、MySQL建表语句 ├── conf/ # 配置项包括请求头、数据库连接 ├── docs/ # 开发文档、接口说明 ├── data/ # 数据文件原始数据和聚合结果 ├── requirements.txt └── start.shconf里我单独放了一个config.py把数据库连接信息、cookie、UA、搜索关键词列表都集中在这里。这样做的好处是部署时不用到处改代码只改一个配置文件就行。requirements.txt里固定版本号避免新版依赖带来的兼容性问题。5.2 部署文档应该写清楚哪些内容部署文档是我最后花时间补的一块。很多同学以为部署文档就是把启动命令贴上去其实远远不够。一份让人能复现的部署文档必须包含前置环境清单、版本号、数据库初始化步骤、启动步骤、验证步骤、常见问题。比如前置环境我写的是CentOS 7.9 Python 3.8MySQL 5.7Hadoop 3.3.0 Hive 3.1.2单机伪分布式即可Spark 3.0.0可选数据库初始化我写了具体的建库建表语句并且附上初始化脚本一条命令执行完成。启动步骤用start.sh封装一键启动Web后端访问地址和默认端口写清楚。验证步骤要求访问首页能正常看到统计卡片访问接口能返回JSON数据。#!/bin/bash source venv/bin/activate python src/web/app.py --host 0.0.0.0 --port 8080如果使用MySQL存储聚合结果还需要在启动前先导入init_db.sql。这个脚本我放在项目的sql/目录下部署文档里明确标注了执行顺序。5.3 讲解材料与现场演示准备的注意点这里分享一个实战体会。答辩演示时最怕的是页面加载慢、数据出错、现场网络不稳定。我的做法是准备两份数据一份是完整的新鲜数据现场可以跑采集和分析另一份是预先跑好的静态数据如果现场网络出问题就切到静态数据演示。讲解PPT的逻辑我按照“背景-目标-数据采集-数据清洗-指标分析-可视化-总结”的流程走每页控制在3分钟以内。关键是要讲清楚每个模块解决了什么问题而不是堆技术名词。比如讲SparkSQL时我会说“用SparkSQL做离线聚合是因为它天然支持大数据量的分布式处理同时数据入仓后可以统一管理分析口径”这句话既有技术点又有业务解释。另外代码讲解时不需要从头到尾读代码只要突出两个关键类或两个核心函数即可。我通常选择数据清洗函数和指标聚合函数因为这两个函数最能体现项目的核心逻辑。6. 我个人做完后的一些体会这套系统做完后我最大的感受是做数据分析项目先把指标定义写下来再写代码能省掉一半返工时间。最开始我直接上手写采集脚本结果采集了两天数据发现分析时需要按“分区”分组但采集时没有把分区字段存下来只能重新采集。后来学乖了第一版就把指标清单打印出来贴在显示器旁边再开始写代码整个过程顺畅很多。另一个感触是部署文档不是写完就完事一定要在一台干净的机器上按文档从头走一遍。我把部署文档发给同学试跑结果他反馈Python版本不匹配、MySQL初始化顺序不对等问题这些在我本地根本发现不了。经历过这次之后我在文档里补充了版本检查命令和初始化自检脚本后面再部署就顺利得多。如果之后想继续扩展这个项目还可以往两个方向走一是接入实时数据流用Kafka和Flink做滚动统计让页面上的趋势图接近实时更新二是增加更多特征维度比如分析青少年模式下UP主粉丝分布和内容标签的语义相似度能给论文带来更丰富的分析深度。不过这些都是后话了先把现有闭环做扎实才是最重要的。
返回列表