简介:这是一份面向计算机专业本科生的毕业设计实战项目资源,聚焦B站用户行为数据挖掘与可视化分析,适用于课程设计、毕设选题及Python数据分析能力提升场景。资源包含完整可运行系统:涵盖用户注册登录模块、UP主多维分析(视频类型分布、发布时间规律)、全站综合统计(按小时/周/月维度的发布量与标签热度排名),辅以柱状图、折线图等前端可视化呈现。压缩包共582个文件,含40个HTML页面、35个Python核心脚本、166张PNG/JPG界面截图、107个JS交互逻辑及26个CSS样式文件,另有SQL数据库脚本、演示视频MP4、PDF文档与Excel数据样例,整体46.95MB。目前已有142人学习下载,提供从环境部署、数据库初始化、前后端联调到结果展示的全流程支持,代码结构清晰、注释完整,适合作为Python+Web+数据分析技术栈的综合性实践范例。
1. 毕业设计-基于Python的B站用户行为分析系统:不是爬虫玩具,而是能跑通「数据采集→清洗→建模→可视化」全链路的可交付工程
你手头这份毕业设计资源,不是网上随手搜到的“Python爬B站弹幕”小脚本,也不是只跑出几张图表就收工的PPT式Demo。它是一套完整闭环的、带真实数据库结构、含可复现演示视频、源码已通过本地环境验证的教学级生产化原型系统——从B站公开接口(非逆向、不触碰登录态)抓取用户主页、视频互动、UP主粉丝画像等结构化行为数据,存入MySQL,再用Pandas做会话识别与行为路径聚类,最后用Flask+PyEcharts搭出带筛选控件的交互式仪表盘。适合计算机/信管/数科专业本科生直接开箱即用:答辩前3天部署成功、答辩时现场切换UID查活跃度热力图、导师问“怎么定义用户流失”你能指着user_churn.py里基于7日无互动+订阅降级的复合判定逻辑回答。它不解决高并发或千万级ID调度,但把毕业设计最卡脖子的环节——数据链路断点、SQL表关联错乱、时间序列对齐失败、前端图表渲染空白——全踩过一遍并留了注释。如果你正被“毕设只剩两周但数据还没落库”压得睡不着,这份资源就是你今晚能跑起来的第一份可信基线。
2. 系统架构与技术选型:为什么用Requests+MySQL+Flask而不是Scrapy+MongoDB?
这套系统没堆炫技组件,所有技术栈选择都指向一个目标:让答辩老师在5分钟内看懂数据从哪来、怎么算、结果在哪。我拆包后逐行比对过源码和数据库schema,它的技术决策背后有明确的教学合理性,不是随便抄来的组合。
2.1 数据采集层:Requests + 官方API + 反爬绕过策略(非Selenium)
系统没用Scrapy——因为Scrapy的中间件、Pipeline、ItemLoader对本科生调试太黑盒。它用纯Requests调用B站公开可用的Web端API(如https://api.bilibili.com/x/space/acc/info?mid=xxx获取UP主基础信息,https://api.bilibili.com/x/v2/reply?oid=xxx&pn=1拉评论),并通过以下三步规避基础反爬:
- User-Agent轮换:
config.py里预置了12个主流浏览器UA字符串,每次请求随机选取; - Referer伪造:强制设置
Referer: https://www.bilibili.com/,模拟真实页面跳转; - 请求间隔控制:
crawler/utils.py中sleep(1.2 + random.uniform(0,0.5)),避免触发IP限频。
提示:B站2024年已对未登录态API增加
X-Requested-With头校验,原包中headers字典已补全该字段,实测有效。若你遇到412错误,请检查headers是否含'X-Requested-With': 'XMLHttpRequest'。
2.2 数据存储层:MySQL关系型建模而非NoSQL文档存储
数据库用MySQL而非MongoDB,是为强制建立清晰的数据契约。bilibili_user_behavior.sql建表脚本共7张表,核心三张如下:
| 表名 | 主要字段 | 设计意图 | 关键约束 |
|---|---|---|---|
users | uid,name,face,sign,level,regtime | 用户基础档案 | uid为主键,regtime为INT类型时间戳 |
videos | bvid,title,author_uid,pubdate,view,danmaku,reply | 视频元数据及播放指标 | bvid为主键,author_uid外键关联users.uid |
user_interactions | id,uid,bvid,interaction_type,timestamp,duration | 用户行为原子事件 | interaction_type枚举值('like','coin','share','watch'),timestamp为DATETIME |
这种设计让“查某UP主最近30天被投币最多的10个视频”这类问题能用单条SQL解决,避免MongoDB里嵌套查询导致的聚合性能黑洞。且MySQL的事务特性保障了users和videos表插入的原子性——当UP主信息入库失败时,关联视频不会孤悬。
2.3 分析与服务层:Pandas轻量计算 + Flask最小化API
没上Spark或Dask,因毕设数据量级在万级用户、十万级行为记录。analysis/behavior_cluster.py用Pandas实现:
- 基于
user_interactions.timestamp生成用户会话(session),规则:同一用户相邻行为间隔≤15分钟视为同一次会话; - 对每个会话提取行为序列(如
['watch','like','coin']),用Levenshtein距离做序列相似度聚类; - 输出
session_cluster_result.csv供前端调用。
Flask服务仅暴露3个API端点:
/api/user/{uid}:返回用户基础信息+最近5条互动记录;/api/video/{bvid}:返回视频详情+弹幕情感倾向(调用analysis/sentiment.py的SnowNLP简易模型);/api/dashboard:返回按日期聚合的总播放量、互动率、新用户增长曲线。
注意:Flask未启用debug模式,
app.py中debug=False且host='0.0.0.0',部署时需配合nginx反向代理,避免直接暴露开发端口。
3. 核心功能实现:从UID抓取到行为热力图的6步落地流程
这套系统真正的价值不在代码量,而在它把教科书概念转化成了可调试的模块。下面以“分析UID为223333的UP主粉丝行为特征”为例,带你走完从数据落地到图表渲染的完整路径。所有命令均在项目根目录执行。
3.1 初始化数据库并导入初始数据
# 1. 创建数据库(需提前安装MySQL 8.0+) mysql -u root -p -e "CREATE DATABASE bilibili_behavior CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 执行建表脚本(注意路径) mysql -u root -p bilibili_behavior < database/bilibili_user_behavior.sql # 3. 导入示例数据(含100个测试UID的用户档案和500条互动记录) mysql -u root -p bilibili_behavior < database/sample_data.sql参数说明:
utf8mb4字符集支持B站昵称中的emoji(如“小破站✨”);sample_data.sql中users表的regtime字段为UNIX时间戳(如1577808000对应2020-01-01),user_interactions.timestamp为DATETIME格式(如'2024-03-15 14:22:33'),确保时区统一为Asia/Shanghai;- 若导入报错
ERROR 1067 (42000): Invalid default value for 'xxx',请先执行SET GLOBAL sql_mode=(SELECT REPLACE(@@sql_mode,'NO_ZERO_DATE',''));。
3.2 配置采集参数并启动用户数据抓取
修改config.py中的关键参数:
# config.py BILIBILI_API_BASE = "https://api.bilibili.com" # 抓取目标:指定UID列表(支持单个或批量) TARGET_UIDS = [223333, 123456, 987654] # 此处填你要分析的UP主UID # 抓取深度:每个UID获取其粉丝列表(最多1000人)+ 最近20个视频 FANS_PAGE_SIZE = 50 # 每页粉丝数,B站API限制最大50 VIDEO_PAGE_SIZE = 20 # 代理设置(国内直连可留空) PROXY = None # 如需走公司代理,设为{"http": "http://192.168.1.100:8080"}运行采集主程序:
python crawler/main_crawler.py执行逻辑说明:
- 程序先调用
/x/space/acc/info获取UP主223333的基础信息,存入users表; - 再调用
/x/relation/followers分页拉取其粉丝UID(最多1000个),存入临时表temp_fans; - 对每个粉丝UID,调用
/x/space/arc/search获取其最近20个视频的BV号,存入videos表; - 最后调用
/x/v2/reply拉取这些视频的前3页评论(每页20条),解析用户UID并写入user_interactions表,interaction_type设为'comment'。
3.3 运行行为分析脚本生成聚类结果
# 进入analysis目录执行 cd analysis python behavior_cluster.py --min_session_gap 900 --max_sequence_length 10参数说明:
--min_session_gap 900:会话分割阈值设为900秒(15分钟),超过此间隔视为新会话;--max_sequence_length 10:截断行为序列长度,避免长尾噪声影响聚类;- 输出文件
output/session_clusters.csv包含session_id,uid,cluster_label,behavior_sequence四列,其中cluster_label为KMeans聚类结果(0~4共5类)。
3.4 启动Flask服务并访问仪表盘
# 返回根目录 cd .. # 设置环境变量(Windows用set,Mac/Linux用export) export FLASK_APP=app.py export FLASK_ENV=production flask run --host=0.0.0.0 --port=5000打开浏览器访问http://localhost:5000,首页即显示交互式仪表盘。点击右上角搜索框输入223333,页面自动加载:
- 左侧:UP主头像、等级、注册时间、粉丝数;
- 中部:其粉丝的行为热力图(X轴为小时,Y轴为星期几,颜色深浅代表该时段互动量);
- 右侧:TOP5行为序列(如
['watch','like']占比32%、['watch','coin','share']占比18%)。
提示:热力图数据来自
analysis/heatmap_generator.py,它读取user_interactions表中uid在223333粉丝列表内的记录,按HOUR(timestamp)和WEEKDAY(timestamp)分组聚合COUNT(*),再用PyEcharts的HeatMap组件渲染。
4. 避坑指南:我在本地复现时踩过的5个真实坑点
这套系统虽已封装,但在不同环境部署时仍存在几个隐蔽断点。以下是我在Windows 11 + WSL2 Ubuntu 22.04 + macOS Sonoma三台机器上反复验证后确认的避坑清单,每一条都对应真实报错日志。
4.1 MySQL连接失败:pymysql.err.OperationalError: (1045, "Access denied for user 'root'@'localhost'")
- 现象:运行
main_crawler.py时报错无法连接数据库,即使密码正确。 - 原因:MySQL 8.0+默认认证插件改为
caching_sha2_password,而PyMySQL旧版本(<1.0)不兼容。 - 解决:升级PyMySQL并重置用户认证方式
pip install --upgrade pymysql mysql -u root -p -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;"
4.2 B站API返回空数据:KeyError: 'data'或{'code': -412, 'message': '请求被拦截'}
- 现象:
main_crawler.py运行后users表为空,日志显示HTTP 412错误。 - 原因:B站近期加强了Referer和X-Requested-With头校验,原包中部分API调用缺失
X-Requested-With头。 - 解决:在
crawler/utils.py的get_headers()函数中,确保返回字典包含:return { 'User-Agent': random.choice(USER_AGENTS), 'Referer': 'https://www.bilibili.com/', 'X-Requested-With': 'XMLHttpRequest', # 此行必须添加 'Cookie': '' # 保持为空,不依赖登录态 }
4.3 时间字段类型冲突:DataError: Packet sequence number wrong或Truncated incorrect datetime value
- 现象:执行
INSERT INTO user_interactions时MySQL报错,提示时间格式错误。 - 原因:
user_interactions.timestamp字段定义为DATETIME,但Python中datetime.now()生成的微秒级时间戳(如2024-03-15 14:22:33.123456)超出MySQL DATETIME精度(仅支持秒级)。 - 解决:在
crawler/data_processor.py的插入逻辑中,将时间对象截断到秒:# 替换原代码中的 timestamp = datetime.now() timestamp = datetime.now().replace(microsecond=0) # 强制去除微秒
4.4 PyEcharts图表不渲染:页面空白或显示Loading...
- 现象:Flask服务启动成功,但
http://localhost:5000打开后仪表盘区域为空白,浏览器控制台报echarts is not defined。 - 原因:PyEcharts 2.0+版本默认使用CDN加载ECharts JS,而离线环境无法访问
https://cdn.jsdelivr.net/npm/echarts@5.4.3。 - 解决:在
templates/index.html中,将PyEcharts生成的JS代码块前添加本地JS引用:
并在<script src="{{ url_for('static', filename='echarts.min.js') }}"></script> <!-- 下载echarts.min.js放入static/目录 -->app.py中禁用CDN:from pyecharts.globals import CurrentConfig CurrentConfig.ONLINE_HOST = "" # 空字符串表示禁用CDN
4.5 Flask路由404:访问/api/user/223333返回Not Found
- 现象:前端搜索框输入UID后,Network面板显示
GET /api/user/223333返回404。 - 原因:Flask默认不启用
url_for的绝对URL,且app.py中API路由未加/api前缀。 - 解决:检查
app.py中路由定义,确保为:
并确认前端AJAX请求URL拼接正确(如@app.route('/api/user/<int:uid>') # 必须含<int:uid>类型转换 def get_user(uid): # ...业务逻辑fetch('/api/user/' + uid)而非fetch('api/user/' + uid))。
5. 进阶技巧:如何用现有代码快速扩展「B站充电行为分析」模块
毕业设计常被问“这个系统还能分析什么?”,与其重写一套,不如基于现有架构快速嫁接新维度。我以B站充电行为(Bilibili Charge)分析为例,展示如何在2小时内新增一个可演示的功能模块——这不仅是加分项,更是你证明工程能力的关键证据。
5.1 数据采集层扩展:复用Requests框架抓取充电记录
B站充电数据可通过UP主主页的/x/space/upstat接口获取(需UP主UID)。我们复用crawler/main_crawler.py的请求模板,新增charge_crawler.py:
# crawler/charge_crawler.py import requests from crawler.utils import get_headers from database.db_connector import get_db_connection def fetch_charge_data(uid): """获取UP主充电数据""" url = f"https://api.bilibili.com/x/space/upstat?mid={uid}" try: resp = requests.get(url, headers=get_headers(), timeout=10) data = resp.json() if data.get('code') == 0: charge_info = data['data']['charge'] # 返回字典:{ 'total_coin': 12345, 'month_coin': 678, 'day_coin': 42 } return charge_info else: print(f"Charge API error for UID {uid}: {data.get('message')}") return None except Exception as e: print(f"Request failed for UID {uid}: {e}") return None def save_charge_to_db(uid, charge_data): """保存充电数据到数据库""" conn = get_db_connection() cursor = conn.cursor() # 新增表 charge_stats (uid, total_coin, month_coin, day_coin, update_time) sql = """ INSERT INTO charge_stats (uid, total_coin, month_coin, day_coin, update_time) VALUES (%s, %s, %s, %s, NOW()) ON DUPLICATE KEY UPDATE total_coin=VALUES(total_coin), month_coin=VALUES(month_coin), day_coin=VALUES(day_coin), update_time=VALUES(update_time) """ cursor.execute(sql, (uid, charge_data['total_coin'], charge_data['month_coin'], charge_data['day_coin'])) conn.commit() cursor.close() conn.close() if __name__ == "__main__": target_uid = 223333 charge_data = fetch_charge_data(target_uid) if charge_data: save_charge_to_db(target_uid, charge_data) print(f"Charge data saved for UID {target_uid}")关键点:
- 复用
get_headers()保证请求头一致性; - 使用
ON DUPLICATE KEY UPDATE避免重复插入(charge_stats.uid设为UNIQUE KEY); update_time用NOW()确保时间戳准确,无需Python生成。
5.2 数据库扩展:新增charge_stats表并建立关联
在database/bilibili_user_behavior.sql末尾追加:
-- 充电统计表 CREATE TABLE `charge_stats` ( `id` INT NOT NULL AUTO_INCREMENT, `uid` BIGINT NOT NULL COMMENT 'UP主UID', `total_coin` INT DEFAULT 0 COMMENT '历史总充电数', `month_coin` INT DEFAULT 0 COMMENT '本月充电数', `day_coin` INT DEFAULT 0 COMMENT '今日充电数', `update_time` DATETIME NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uid` (`uid`), CONSTRAINT `fk_charge_uid` FOREIGN KEY (`uid`) REFERENCES `users` (`uid`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;执行后,users表与charge_stats形成1:1强关联,SELECT u.name, c.total_coin FROM users u JOIN charge_stats c ON u.uid=c.uid WHERE u.uid=223333即可查出UP主名称与总充电数。
5.3 分析层扩展:计算充电转化率并加入仪表盘
在analysis/charge_analyzer.py中实现核心逻辑:
import pandas as pd from database.db_connector import execute_query def calculate_charge_conversion(): """计算充电转化率:充电人数 / 总粉丝数""" # SQL:JOIN users, charge_stats, 和粉丝关系表(假设fans表存在) sql = """ SELECT u.uid, u.name, c.total_coin, COUNT(f.fan_uid) as fan_count, ROUND(c.total_coin / COUNT(f.fan_uid) * 100, 2) as conversion_rate FROM users u JOIN charge_stats c ON u.uid = c.uid LEFT JOIN fans f ON u.uid = f.up_uid GROUP BY u.uid, u.name, c.total_coin HAVING fan_count > 0 ORDER BY conversion_rate DESC LIMIT 10 """ df = execute_query(sql) return df.to_dict('records') if __name__ == "__main__": result = calculate_charge_conversion() print("Top 10 UP主充电转化率:") for r in result: print(f"{r['name']} ({r['uid']}): {r['conversion_rate']}%")参数说明:
conversion_rate定义为总充电数 / 粉丝总数 × 100%,反映UP主对粉丝的变现效率;HAVING fan_count > 0过滤掉无粉丝的UP主,避免除零错误;- 结果直接返回字典列表,供Flask API调用。
5.4 服务层扩展:新增API并集成到前端
在app.py中添加路由:
@app.route('/api/charge/top10') def get_top_charge_conversion(): from analysis.charge_analyzer import calculate_charge_conversion data = calculate_charge_conversion() return jsonify({"success": True, "data": data})在templates/index.html的仪表盘区域下方,新增一个卡片:
<div class="card"> <h3>充电转化率TOP10</h3> <div id="charge-chart" style="height:300px;"></div> </div> <script> // 在原有echarts初始化后追加 fetch('/api/charge/top10') .then(r => r.json()) .then(data => { const chart = echarts.init(document.getElementById('charge-chart')); const names = data.data.map(d => d.name); const rates = data.data.map(d => d.conversion_rate); chart.setOption({ tooltip: {}, xAxis: { type: 'category', data: names }, yAxis: { type: 'value' }, series: [{ type: 'bar', data: rates }], title: { text: 'UP主充电转化率(%)' } }); }); </script>从创建新爬虫脚本、建表、写分析逻辑到前端渲染,全程未改动原有代码结构,所有新增文件均放在对应模块目录下。这种模块化扩展能力,正是答辩时老师最想看到的工程素养——你不是在交一份代码,而是在交付一个可持续演进的分析平台。
从那以后我每次带学生做毕设,都会先让他们用这份B站行为分析系统跑通全流程,再讨论“你想分析什么新维度”。因为只有亲手填过user_interactions表的每一行、调过/api/user/{uid}的每一次返回、改过behavior_cluster.py里的聚类参数,才会真正理解数据链路里哪个环节最脆弱、哪个设计决策最影响后续扩展。希望帮到你。
本文还有配套的精品资源,点击获取