简介:基于大数据的B站热门视频数据分析与研究毕业设计论文文档,面向需要完成相关选题的本专科学生、数据分析爱好者及视频平台运营人员。内容基于Python、Flask框架与Hadoop技术,完整阐述了B/S架构系统的设计思路,覆盖用户信息管理、注册审核、权限分配、热门视频多维度热度评估、动态榜单更新、视频分类管理与标签优化等模块,并结合平台、创作者、行业三个层面分析了应用价值。文档包含中英文摘要、绪论、技术概述等章节,结构完整,既可作为毕业设计撰写范例,也可为视频内容分析项目提供参考。资源包共1个doc文件,大小约3.66MB,已有68人浏览学习。该文档将大数据分析落地到B站热门视频场景,详细展示了研究方法与系统模块划分,对同类课题研究和社会化媒体数据分析有直接借鉴意义。
1. 用 Python 把 B 站热门视频从“看得见”变成“算得清”
这个题目摆在开题答辩现场,本质上不是“写一篇论文”,而是要在论文之外交付一套能说清楚“热门为什么是热门”的小型数据分析系统。B 站热门视频每天更新一批,播放、点赞、投币、收藏、转发、弹幕这些指标堆在一起,肉眼只能看出量级大小,看不出哪个指标在真正驱动热度。把这套逻辑做成可复现的 Python 工程,才是数据分析与数据研究这两个词在这个毕设里的真实分量。
做这件事的人一般是数据科学、大数据或软件工程方向的学生,也可能是在职工程师拿来练手。工作流并不复杂:先把热门视频列表抓下来,再逐条取统计详情,清洗后放进 DataFrame,做相关性分析和综合热度打分,最后用图表把结论导出成论文配图。相比推荐系统或自然语言处理方向的毕设,它的工程量适中,核心风险全在数据质量、字段口径和结果可解释性这三处。下面按我通常会采用的落地顺序展开。
2. 先立框架:B 站热门视频数据分析系统的分层设计
很多毕设翻车不是因为爬虫写不出来,而是数据抓完才发现不知道下一步该算什么。拿回几十万行原始记录,没有分层设计,清洗和分析写在同一段脚本里,改一个字段就要全流程重跑。所以第一步不是写 requests,而是把系统拆成四层:采集层、存储层、分析层、展示层。
2.1 数据研究的分析链路:采集、清洗、建模、可视化的边界
采集层只做一件事:把 B 站接口返回的原始报文完整落盘,不加工字段语义。存储层建议保留两种形态,raw JSONL 是原始证据,清洗后的 CSV 或 SQLite 是分析输入。分析层负责去重、缺失值处理、标准化和建模,计算结果统一输出成带版本号的中间表。展示层只读取中间表,不碰原始数据。
这样拆分的好处体现在论文评审环节:导师问“你这份图的数据从哪来”,你能直接指到数据快照文件,而不是含糊说“跑了一下爬虫”。各层之间传递的唯一格式是 DataFrame,任何一层出了问题,都可以从上一层的输出重新计算,不用重抓网络数据。
2.2 字段模型:B 站视频指标里哪些字段对“热门”真正有用
分析系统不是字段越多越好,字段太少则结论单薄,字段太多则清洗成本失控。参考 B 站公开接口的常见返回结构,我一般保留以下字段作为基础指标:
| 字段 | 类型 | 含义 | 是否参与建模 |
|---|---|---|---|
| bvid | string | 视频唯一编号 | 主键,不建模 |
| title | string | 视频标题 | 文本分析用 |
| owner_name | string | 作者昵称 | 不建模 |
| pub_ts | int | 发布时间时间戳 | 时间维度 |
| view | int | 播放量 | 是 |
| danmaku | int | 弹幕数 | 是 |
| reply | int | 评论数 | 是 |
| favorite | int | 收藏数 | 是 |
| coin | int | 投币数 | 是 |
| share | int | 分享数 | 是 |
| like | int | 点赞数 | 是 |
注意两个容易踩坑的点。第一,B 站各端接口对字段命名不完全一致,有的叫stat.view,有的直接平铺,落盘前要统一映射。第二,pub_ts尽量用原始时间戳,不要在地层里就转成北京时间的字符串,时区换算放到分析层做。我用过一次性把时间转成字符串的写法,后面做小时级分布分析时被迫重新解析,白白浪费一晚上。
2.3 用 dataclass 把数据定义写死在代码里
字段不落到代码里,分析脚本就会到处出现魔法字符串。我习惯用dataclass冻结一个视频记录对象,同时兼顾类型提示和字段映射。
from dataclasses import dataclass, asdict from typing import List @dataclass class VideoStat: bvid: str title: str owner_name: str pub_ts: int view: int danmaku: int reply: int favorite: int coin: int share: int like: int @classmethod def from_api(cls, item: dict) -> "VideoStat": """把接口返回的 dict 转成结构化对象,同时做字段默认值兜底""" stat = item.get("stat", {}) return cls( bvid=item.get("bvid", ""), title=item.get("title", ""), owner_name=item.get("owner", {}).get("name", ""), pub_ts=item.get("pubdate", 0), view=stat.get("view", 0), danmaku=stat.get("danmaku", 0), reply=stat.get("reply", 0), favorite=stat.get("favorite", 0), coin=stat.get("coin", 0), share=stat.get("share", 0), like=stat.get("like", 0), ) def to_dict(self) -> dict: return asdict(self)这里from_api是关键。接口偶尔会缺失某个统计字段,直接item["stat"]["view"]会抛 KeyError,用get配合默认值 0 可以保证单条数据异常不会中断整个采集。to_dict用于后续把对象写入 CSV 或 JSONL,dataclasses.asdict会自动把嵌套对象递归转成字典。
工程目录我会按下面的习惯组织,论文里画系统架构图时直接对应到目录层级,答辩时很容易讲清楚:
bilibili_hot_research/ crawler/ # 采集层 hot_list.py video_detail.py storage/ # 存储层 raw/ # jsonl 原始数据 cleaned/ # csv 清洗结果 analysis/ # 分析层 preprocess.py models.py correlation.py heat_score.py visualization/ # 展示层 charts.py dashboard.py main.py # 总入口3. 数据生命线:B 站热门视频数据采集与预处理的落地方案
架构搭完,下一步就是让数据流动起来。这个章节解决两个问题:数据从哪个口子拿,拿到手之后怎么变成能算的样子。
3.1 数据源选型:为什么优先读公开 Web API 而不是硬解析网页
B 站有多个视频列表入口,但“热门视频”这个场景下,最可靠的做法是直接请求官方 Web 端公开接口,拿到 JSON 后解析字段。为什么不用网页解析?因为 HTML 结构改版频繁,一个 class 名变了,正则和 XPath 全部失效,而 JSON 接口的字段相对稳定,容错空间更大。
采集节奏上我建议低频慢跑。以 300 到 500 条视频为一批,每两条请求之间至少停顿 2 到 3 秒,一整天跑下来数据量已经足够做分布分析。高频抓取对论文研究没有任何增量贡献,反而容易触发风控,轻则验证码,重则封 IP。作为毕设项目,数据合法性说明也要写进论文,明确标注采集时间窗口、数据用途仅限于学术研究。
3.2 最小采集脚本:从热门列表到视频统计详情
先取热门视频 ID 列表,再逐条请求统计详情接口,这个两段式结构比单次拉全量数据更稳。部分场景下热门列表接口只返回视频元信息,统计值不全,所以video_detail这一步是必要的。
import json import time import requests HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0 Safari/537.36", "Referer": "https://www.bilibili.com/", } def fetch_hot_list(page_size: int = 50) -> list[str]: """拉取热门视频列表,返回 bvid 数组""" url = "https://api.bilibili.com/x/web-interface/popular" params = {"ps": page_size, "pn": 1} resp = requests.get(url, headers=HEADERS, params=params, timeout=10) resp.raise_for_status() data = resp.json() if data.get("code") != 0: raise RuntimeError(f"接口返回异常: {data.get('message')}") return [item["bvid"] for item in data["data"]["list"]] def fetch_video_stat(bvid: str) -> dict: """按 bvid 取视频详情,返回原始 JSON""" url = f"https://api.bilibili.com/x/web-interface/view" params = {"bvid": bvid} resp = requests.get(url, headers=HEADERS, params=params, timeout=10) resp.raise_for_status() payload = resp.json() if payload.get("code") != 0: return {} return payload["data"] # 主流程:取列表 -> 逐条取详情 -> 写 JSONL bvids = fetch_hot_list(page_size=50) with open("storage/raw/hot_videos.jsonl", "a", encoding="utf-8") as f: for bv in bvids: detail = fetch_video_stat(bv) if detail: f.write(json.dumps(detail, ensure_ascii=False) + "\n") time.sleep(2.5) # 控制请求频率,避免高频触发风控这段代码需要重点解释三个参数。第一,HEADERS里的Referer在 B 站接口中有实际作用,不带可能直接 412 错误;User-Agent用常规浏览器标识即可满足多数场景。第二,timeout=10是必填项,不设超时,某个接口卡住会让整个采集进程永久挂起,写论文数据时一个请求等十分钟没有任何意义。第三,time.sleep(2.5)是整段脚本的节奏器,它的作用是让单 IP 请求频率保持在一个低水平,这也是数据分析采集模块里合法且必要的自我约束。
采集过程中如果出现code: -412,表示请求被风控拦截,正确做法是停下来,等待较长时间后再继续,而不是换着法去绕过限制。毕设数据采集讲究的是稳定可持续,不是短时间梭哈。
3.3 清洗与规整:把脏字段整理成 Pandas 能直接计算的样子
接口数据落到 JSONL 之后,还不能直接喂给模型。常见脏数据有三类:热门外列表里可能存在重复视频;某些视频刚发布不久,互动数据为零;时间字段是 Unix 时间戳,需要转成可读时间。清洗目标是把这些不规则处理成规整的表格。
import pandas as pd from pathlib import Path def load_raw_to_frame(raw_path: str) -> pd.DataFrame: records = [] for line in Path(raw_path).read_text(encoding="utf-8").splitlines(): if not line.strip(): continue raw = json.loads(line) records.append({ "bvid": raw.get("bvid"), "title": raw.get("title", ""), "owner_name": raw.get("owner", {}).get("name", ""), "pub_ts": raw.get("pubdate", 0), "pub_time": pd.to_datetime(raw.get("pubdate", 0), unit="s", utc=True) .tz_convert("Asia/Shanghai"), "view": raw.get("stat", {}).get("view", 0), "danmaku": raw.get("stat", {}).get("danmaku", 0), "reply": raw.get("stat", {}).get("reply", 0), "favorite": raw.get("stat", {}).get("favorite", 0), "coin": raw.get("stat", {}).get("coin", 0), "share": raw.get("stat", {}).get("share", 0), "like": raw.get("stat", {}).get("like", 0), }) return pd.DataFrame(records) df = load_raw_to_frame("storage/raw/hot_videos.jsonl") # 去重:bvid 是唯一键 df = df.drop_duplicates(subset=["bvid"], keep="last") # 过滤无效记录:视频为空或 bvid 缺失都直接剔除 df = df[df["bvid"].notna() & df["bvid"].str.startswith("BV")] # 互动量为空的部分,统一切 0 并记录数量便于论文写数据说明 interact_cols = ["danmaku", "reply", "favorite", "coin", "share", "like"] df[interact_cols] = df[interact_cols].fillna(0).astype(int) # 删除播放量为 0 且发布超过 7 天的视频(通常是异常记录) df = df[~((df["view"] == 0) & (df["pub_ts"] < pd.Timestamp.now().timestamp() - 7 * 86400))] df.to_csv("storage/cleaned/hot_cleaned.csv", index=False, encoding="utf-8-sig") print(f"清洗完成,保留 {len(df)} 条有效记录")这段清洗代码有几个参数和逻辑值得展开。keep="last"表示同一条视频重复出现时保留最后一次采集值,因为视频的热度指标随时间增长,最新抓取的数据更有信息量。astype(int)是隐式截断,互动数本身是整数,接口返回浮点数的概率极低,但统一转类型能避免后续groupby或corr时出现 dtype 不一致的隐患。encoding="utf-8-sig"是 Windows 场景下的必要设置,Excel 打开 CSV 不会乱码。
清洗规则的完整记录本身就值得写进论文的“数据预处理”章节,评审老师通常关注的不是你清洗了多少行,而是你有没有意识到哪些数据不能直接用——这比清洗代码本身更能体现数据研究能力。
3.4 B 站数据采集与合规节奏:单 IP 下如何把控抓取频率
这个话题在论文里容易被忽略,但对毕设答辩很关键。B 站热门接口本质上是公共资源,作为学术研究可以低频调用,但不能把服务端当成数据仓库来拖库。
我个人建议把采集控制参数集中定义成常量,方便论文方法论部分引用:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 单批视频数 | 30~50 | 覆盖热门榜一个页面足够分析 |
| 请求间隔 | 2.5~5 秒 | 低频稳定,避免集中请求 |
| 单日总请求数 | ≤ 2000 | 达到上限即停止,次日再跑 |
| 超时时间 | 10 秒 | 超过即重试,最多重试 3 次 |
| 重试退避 | 30 秒起步 | 失败后指数退避,连续失败则退出 |
这套参数组合跑一个样本周期,能拿到几千条独立记录,做相关分析和建模绰绰有余。部分同类项目会把频率压到几百毫秒一次,短期看起来效率高,但一旦触发风控,整个数据采集窗口就断了,反而耽误论文进度。
4. 让视频热度可解释:指标标准化、相关性与热度评分
数据清洗完,接下来的任务是回答研究问题,也就是“热门视频为什么热”。这需要从三个角度切入:指标之间的关系、综合热度量化、时间维度的影响。
4.1 相关性矩阵:先看哪两个指标会同时涨跌
数据分析项目的第一步通常不是建模,而是看相关性。播放量、点赞、投币、收藏、分享这些指标之间存在明显的共生关系,比如用户投币前大概率先点赞,但收藏的行为逻辑可能不同。
import pandas as pd import seaborn as sns import matplotlib.pyplot as plt df = pd.read_csv("storage/cleaned/hot_cleaned.csv") model_cols = ["view", "danmaku", "reply", "favorite", "coin", "share", "like"] corr = df[model_cols].corr() plt.figure(figsize=(10, 8)) sns.heatmap(corr, annot=True, fmt=".2f", cmap="RdBu_r", center=0, square=True, cbar_kws={"shrink": 0.8}) plt.title("热门视频指标相关矩阵") plt.tight_layout() plt.savefig("visualization/out/corr_heatmap.png", dpi=300)df[model_cols].corr()默认计算 Pearson 相关系数,输出结果是 7×7 的对称矩阵。annot=True会在格子里标注数值,方便直接截图放进论文;fmt=".2f"控制保留两位小数。cmap="RdBu_r"用红蓝色带区分正负相关,色调越深代表相关程度越强。
解读时重点关注两处。第一,view和其他指标的相关系数若普遍在 0.7 以上,说明热门视频的互动行为高度同步,后续建模中选一两个代表指标即可,避免多重共线性。第二,若favorite与view的相关性明显低于like与view,说明收藏是另一种用户行为,可以单独作为“内容价值”维度的代理指标。这个发现写进论文里,比罗列数据有说服力得多。
4.2 构建“综合热度指数”:为什么不能直接算平均分
直接对播放量和点赞数做加权平均算热度,是很多毕设常见的硬伤。播放量动辄几十万,弹幕只有几百,量纲差异下,加权平均的结果基本被播放量主导,其他指标形同虚设。正确做法是先标准化,再赋权。
import numpy as np def minmax_normalize(series: pd.Series) -> pd.Series: """Min-Max 标准化到 [0,1] 区间""" vmin, vmax = series.min(), series.max() if vmax - vmin == 0: return pd.Series(0.0, index=series.index) return (series - vmin) / (vmax - vmin) # 各互动指标标准化后等权相加 weight = { "view": 0.3, "like": 0.2, "coin": 0.2, "favorite": 0.1, "share": 0.1, "reply": 0.05, "danmaku": 0.05, } score = pd.Series(0.0, index=df.index) for col, w in weight.items(): score += w * minmax_normalize(df[col]) df["heat_score"] = score df.sort_values("heat_score", ascending=False, inplace=True) print(df[["bvid", "title", "view", "like", "heat_score"]].head(10))vmax - vmin等于 0 时,说明该指标在样本内完全没有变化,直接填 0 是稳妥做法,避免除零错误。权重设置参考了指标的信息量和业务直觉:播放量是热度基本盘,权重最高;投币比点赞更能代表用户认可度,因此coin和like各占 0.2;弹幕和回复体现了互动深度,但数值波动大,权重压低。
这个方案的优点是透明可解释,论文里可以放公式,答辩时能讲清楚每一步。进阶一点的做法是熵值法,用指标变异程度自动确定权重,避免主观赋权被质疑,但也要付出解释成本。毕设阶段我建议先在等权基础上跑通,再用熵值法做对比,两种结果放一起本身就是一项不错的分析内容。
4.3 时间维度的对比:热门视频的发布时间与互动率
热度不仅取决于视频质量,也受发布时间影响。把pub_time拆出小时维度,看哪个时段发布的视频更容易获得高互动,是数据研究中比较出彩的小发现。
df["pub_hour"] = pd.to_datetime(df["pub_time"]).dt.hour df["interact_per_view"] = ( df["like"] + df["coin"] + df["favorite"] + df["reply"] + df["danmaku"] ) / df["view"].clip(lower=1) hourly_stats = ( df.groupby("pub_hour") .agg( video_count=("bvid", "count"), median_view=("view", "median"), median_interact=("interact_per_view", "median"), ) .reset_index() ) print(hourly_stats.sort_values("median_interact", ascending=False).head(8))clip(lower=1)把播放量下限设为 1,防止新视频播放量为 0 时计算得到无穷大。agg里分别统计了每个小时段内的视频总量、播放量中位数、互动率中位数,用中位数而不是平均值,是因为热门列表里存在少量爆款数据,会显著拉高平均值的代表性。
实际分析中,我通常会发现下午和晚间时段发布的视频在热门列表中占比更高,但互动率未必更高,因为那个时段竞争也激烈。这个结论表面简单,背后涉及供给和需求双侧的数据逻辑,很适合作为论文“数据分析结果”章节的第一个实证案例。
4.4 可解释研究:让数据研究系统输出 30 秒能讲清的结论
数据分析研究系统的最终产出不只是图表,还要有几个“一句话结论”。我一般会准备三个固定格式的发现,放在论文摘要或答辩 PPT 的显眼位置。
| 研究问题 | 分析方法 | 结论示例口径 |
|---|---|---|
| 哪些指标驱动热门 | 相关性矩阵 | “点赞与播放线性相关最强,投币与收藏体现独立价值” |
| 如何量化综合热度 | Min-Max + 加权 | “综合热度评分与单日播放量排名重合度约 70%” |
| 发布时间是否有影响 | 小时分组聚合 | “晚间 18 到 22 时发布视频热门占比明显偏高” |
示例结论里的具体数字会因采集批次而变化,你只需要保留“用什么方法得到什么结论”这个结构。数据研究系统体现的是分析方法和结论的透明性,不是造出一个黑盒模型得一个分数。
5. 数据可视化与毕设交付的 3 个提效细节
到了交付阶段,常见做法是论文配图、答辩 PPT、演示系统图表三处各要一套图。如果三个地方分别画图,不仅工作量重复,还容易因为数据不同步被评委抓住问题。下面三个技巧可以避免这种尴尬。
5.1 一次计算,多处渲染:用同一份中间表出图
所有图表一律从analysis/输出的结果表读取,论文配图和 ECharts 大屏共用同一份 CSV 或 JSON。具体的做法是在分析层最后导出analysis/out/chart_data.json,可视化脚本只消费这个文件。
chart_json = { "corr": corr.round(2).values.tolist(), "fields": model_cols, "heat_score_top10": ( df.head(10)[["title", "heat_score", "view"]] .to_dict(orient="records") ), } with open("analysis/out/chart_data.json", "w", encoding="utf-8") as f: json.dump(chart_json, f, ensure_ascii=False, indent=2)to_dict(orient="records")输出的记录列表可以直接被 Pyecharts 或 ECharts 读取,indent=2方便人工检查。这样论文里的表格、答辩 PPT 里的柱状图、演示系统的数据大屏,都引用同一份计算结果,任何一次数据更新只需重跑分析流程。
5.2 中文乱码与字体缺失的通用处理
Matplotlib 默认字体不支持中文,是可视化环节最常见的报错。plt.title里的中文标题输出成方块,论文里不能用,反复修改又会浪费时间。
import matplotlib matplotlib.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial Unicode MS"] matplotlib.rcParams["axes.unicode_minus"] = False把这两行放在可视化脚本首部,axes.unicode_minus设为 False 是为了同时解决负号显示异常。不同操作系统下字体名有差异,Windows 通常用Microsoft YaHei,macOS 用Arial Unicode MS,Linux 服务器上需要确认是否有中文字体包,没有的话提前安装。
5.3 数据快照与随机种子:让毕设统计结果可复现
评审后修改建议最常出现在“结论是否可以复现”这一条。你在 3 月跑的结论,5 月答辩前如果想重新演示一遍,数据可能已经完全不同,采集接口和榜单都变了。解决办法是对采集样本做快照冻结。
SNAPSHOT_VERSION = "20250601" df.to_csv(f"storage/cleaned/hot_cleaned_{SNAPSHOT_VERSION}.csv", index=False, encoding="utf-8-sig")论文里写明分析基于哪个快照版本,答辩现场演示时指定读取该版本文件,不重新拉取数据。分布在论文各章节的图表,都在图注中标注“数据来源:2025-06-01 采集快照”。这个方法能直接向评审证明研究结论的稳定性,也避免演示当天网络或接口异常导致的翻车。数据分析系统的一个重要加分项就在这个细节上——它让你的研究过程和结果都能被验证。
本文还有配套的精品资源,点击获取