资讯详情

资讯详情

Django音乐行为分析大屏:从爬虫到用户画像的毕设级数据链路

简介这是一套基于Python与Django开发的网易云音乐数据可视化分析系统面向计算机专业本科生、毕业设计学生及数据分析初学者聚焦用户画像构建与播放行为深度分析两大核心问题可直接用于毕设、课程设计或教学演示。资源包共54个文件含24个核心Python源码覆盖Scrapy爬虫、Django后端、ECharts前端集成、4个CSV原始数据集、3份Markdown说明文档、1份PDF设计报告及7张PNG可视化图表整体10.57MB结构清晰、模块解耦便于理解技术链路与二次开发。已有62人学习下载资源提供完整可运行代码、详细设计文档、环境配置指南及远程技术支持尤其适合希望从数据采集热歌榜爬取、MySQL存储、到Django大屏展示全流程实践的学习者是少有的涵盖“爬—存—析—展”闭环的轻量级教学级项目。1. 为什么用 Django 做音乐行为大屏而不是 Flask 或 Streamlit这不是一个“Python 可视化项目”那么简单——它要解决的是如何让一份毕设/课设在答辩现场30 秒内让导师看懂你干了什么、数据从哪来、结论靠不靠谱。我带过三届某高校计算机系的课程设计指导最常看到的翻车现场是学生花两周爬完数据用 Matplotlib 画了 8 张图答辩时被问“这张热力图的横轴单位是什么原始数据清洗逻辑在哪”当场卡壳。而这个网易云音乐可视化项目从一开始就把“可解释性”和“可交付性”钉死在架构里用 Scrapy 做强可控的定向爬虫不是 requests BeautifulSoup 那种玄学翻页MySQL 存结构化行为日志播放时间戳、歌单收藏动作、用户标签权重Django 后端暴露 RESTful 接口供大屏调用不是把所有逻辑塞进前端 JS再用 ECharts 封装成响应式大屏页面。它不追求“实时千万级并发”但保证每一行代码都能对应到毕业论文里的“第三章 系统设计”它不堆砌 AI 模型但用户画像模块用的是真实可复现的 RFM标签聚类最近一次播放、频率、总时长 歌单偏好关键词 TF-IDF不是“根据喜好推荐”的黑匣子话术。适合两类人一是需要交一份有完整数据链路、有文档、能跑通、能讲清技术选型理由的毕设/课设的学生二是想快速搭一个带用户分群、行为路径、热度趋势三要素的分析原型的从业者。如果你的课题是“基于 Web 的音乐平台数据分析系统”这份资源就是你答辩 PPT 里那张“系统架构图”的实体版本。2. 总体架构设计四层结构与五个核心模块的职责边界这个项目不是“爬虫 画图”的缝合怪而是按工业级数据产品思路拆解为四层数据采集层 → 数据存储层 → 业务逻辑层 → 可视化呈现层。每层之间通过明确接口契约通信避免后期改一个图表就全盘崩坏。下面直接给出我在某跨平台系统中实际落地的模块划分逻辑和本项目完全对齐2.1 数据采集层Scrapy 爬虫的可控性设计项目里CloudMusic-Crawler目录下的爬虫不是简单抓热歌榜。它做了三件事反爬绕过策略收敛网易云音乐对未登录用户限制榜单访问频次爬虫通过scrapy-rotating-proxies轮换 User-Agent 和 Referer非 IP 代理并在settings.py中配置DOWNLOAD_DELAY 3RANDOMIZE_DOWNLOAD_DELAY True模拟真实用户节奏结构化字段提取不只抓歌名、歌手还解析span classtxt里的播放量文本用正则r(\d\.?\d*)\s*(万|亿)统一转为数值型歌单页额外提取“创建者 ID”“标签列表”“收藏数”为后续用户画像打基础增量更新机制每次运行前检查 MySQL 中song_list表的update_time字段只爬取last_update NOW() - INTERVAL 1 DAY的榜单避免重复入库。提示requirements.txt里scrapy2.6.2是关键版本——新版 Scrapy 3.x 对CrawlSpider的 rule 解析逻辑有变更会导致部分规则失效。若你本地环境是 Python 3.10务必先pip install scrapy2.6.2再启动爬虫。2.2 数据存储层MySQL 表结构与字段语义设计项目用 MySQL 而非 SQLite 或 CSV是因为用户行为分析必须支持多表关联查询比如“找出近7天收藏某类歌单的用户中播放周杰伦歌曲频次最高的 Top10”。以下是核心三张表的实际 DDL来自设计报告.pdf第 4.2 节-- 用户基础表非网易云真实账号是爬虫生成的虚拟ID CREATE TABLE user_profile ( id INT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(32) NOT NULL COMMENT 虚拟用户唯一标识, gender TINYINT DEFAULT 0 COMMENT 0:未知,1:男,2:女, age_group VARCHAR(10) DEFAULT unknown COMMENT 年龄段分组, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 行为日志表关键记录每一次可量化动作 CREATE TABLE user_behavior_log ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(32) NOT NULL, song_id VARCHAR(32) NOT NULL, behavior_type ENUM(play,collect,share,comment) NOT NULL, timestamp DATETIME NOT NULL, duration_sec INT DEFAULT 0 COMMENT 播放时长秒, song_popularity INT DEFAULT 0 COMMENT 歌曲热度值归一化后, INDEX idx_user_time (user_id, timestamp), INDEX idx_song_time (song_id, timestamp) ); -- 歌单元数据表支撑“歌单分析可视化系统” CREATE TABLE playlist_info ( id INT PRIMARY KEY AUTO_INCREMENT, playlist_id VARCHAR(32) NOT NULL, title VARCHAR(255) NOT NULL, creator_id VARCHAR(32) NOT NULL, tag_list JSON COMMENT JSON数组如[古风,治愈], collect_count INT DEFAULT 0, play_count INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );注意user_behavior_log表的两个复合索引idx_user_time支撑“某用户近N天行为路径”查询idx_song_time支撑“某歌曲在各时段播放热度”统计。没有这两个索引Django Admin 里查一周数据可能卡顿 20 秒以上。2.3 业务逻辑层Django 的 ViewSet 与数据聚合策略Django 不是用来写 HTML 的而是做数据管道的编排中枢。项目中NeteaseCloudMusicDataAnalysis目录下的views.py定义了 5 个核心 API 端点全部继承viewsets.ModelViewSet但关键在get_queryset()的重写逻辑# api/views.py from django.db.models import Count, Avg, Sum, Q from rest_framework import viewsets from .models import UserBehaviorLog, UserProfile, PlaylistInfo class UserPortraitViewSet(viewsets.ModelViewSet): serializer_class UserPortraitSerializer def get_queryset(self): # RFM 模型计算Recency最近播放、Frequency频次、Monetary播放时长总和 today timezone.now().date() return UserProfile.objects.annotate( recencyMax(userbehaviorlog__timestamp, filterQ(userbehaviorlog__behavior_typeplay)), frequencyCount(userbehaviorlog, filterQ(userbehaviorlog__behavior_typeplay)), monetarySum(userbehaviorlog__duration_sec, filterQ(userbehaviorlog__behavior_typeplay)) ).filter( recency__gtetoday - timedelta(days7) # 近7天活跃用户 )这里没用 Pandas 做聚合因为1Django ORM 的annotatefilter在百万级数据下比pandas.read_sql()快 3 倍以上2所有计算逻辑可直接映射到论文“第四章 数据分析方法”中的公式。UserPortraitSerializer则负责把recency转为“距今X天”frequency分档为“高频/中频/低频”这才是用户画像能落地的关键——不是输出一堆数字而是输出可运营的标签。2.4 可视化呈现层ECharts 大屏的响应式适配方案Django大屏目录下的templates/dashboard/index.html不是静态 HTML。它通过{% url api:user-portrait-list %}动态获取 API 数据并用 ECharts 的setOption()实现按需渲染。重点在于两点适配分辨率自适应监听window.onresize事件但不用myChart.resize()简单粗暴调用而是先clear()再setOption(option, {notMerge: true})避免图表元素错位数据懒加载大屏首次加载只请求“今日播放热度 TOP10”和“用户地域分布”点击“详情”按钮才触发fetch(/api/behavior-trend/?days30)加载 30 天趋势数据防止首屏白屏超 3 秒。项目里static/js/charts.js的initSongHeatMap()函数用echarts.init(dom, null, {renderer: canvas})显式指定 canvas 渲染器——这是为了兼容老旧浏览器实测在 IE11 下仍能显示基础热力图虽然不支持动画。3. 核心功能实现用户画像建模与播放行为路径还原用户画像不是给用户打“喜欢周杰伦”这种标签而是构建可验证的行为特征向量。本项目采用RFM 标签权重加权法所有计算逻辑都在 Django Model 层完成确保论文里“用户画像构建流程图”的每一步都有代码对应。3.1 RFM 模型的 Django 实现与阈值设定RFM 中的 MMonetary在音乐场景下不能直接用消费金额而是用有效播放时长替代。项目定义“有效播放”为duration_sec 60播放满1分钟。UserProfile模型中新增三个字段# models.py class UserProfile(models.Model): # ... 其他字段 r_score models.SmallIntegerField(default0) # Recency 得分1-5 f_score models.SmallIntegerField(default0) # Frequency 得分1-5 m_score models.SmallIntegerField(default0) # Monetary 得分1-5 rfm_segment models.CharField(max_length20, defaultother) # 最终分群标签 def calculate_rfm(self): 在 save() 前调用更新 RFM 得分 from django.db.models import Max, Count, Sum, Q from datetime import timedelta # R最近一次播放距今天数越小得分越高 last_play self.userbehaviorlog_set.filter( behavior_typeplay ).aggregate(lastMax(timestamp))[last] if last_play: days_since (timezone.now().date() - last_play.date()).days self.r_score max(1, 5 - min(days_since // 3, 4)) # 0-2天5分3-5天4分... # F近30天播放次数 play_count self.userbehaviorlog_set.filter( behavior_typeplay, timestamp__gtetimezone.now() - timedelta(days30) ).count() self.f_score 1 if play_count 0 else min(5, (play_count // 10) 1) # 0次1分10-19次2分... # M近30天有效播放总时长秒 total_duration self.userbehaviorlog_set.filter( behavior_typeplay, duration_sec__gte60, timestamp__gtetimezone.now() - timedelta(days30) ).aggregate(totalSum(duration_sec))[total] or 0 self.m_score 1 if total_duration 0 else min(5, (total_duration // 3600) 1) # 每小时1分 # 综合分群RFM 三维度组合共125种但项目只保留8类高价值分群 self.rfm_segment self._get_segment_label() def _get_segment_label(self): 根据 RFM 得分返回业务可理解的标签 if self.r_score 4 and self.f_score 4 and self.m_score 4: return high_value elif self.r_score 4 and self.f_score 3: return at_risk elif self.r_score 3 and self.f_score 4: return hibernating # ... 其他逻辑详见 design_report.pdf 第 5.3 节 return other注意calculate_rfm()方法在UserProfile.save()时自动触发通过pre_save信号或重写save()确保每次用户行为更新后画像实时刷新。这比定时任务更精准也避免答辩时被问“画像多久更新一次”。3.2 播放行为路径的图谱化还原行为路径不是简单画“播放→收藏→分享”流程图而是用Neo4j 图数据库思维在 MySQL 中模拟。项目在user_behavior_log表上建立“行为序列”视图-- 创建行为序列视图MySQL 5.7 CREATE VIEW user_behavior_sequence AS SELECT a.user_id, a.song_id as current_song, b.song_id as next_song, a.timestamp as current_time, b.timestamp as next_time, TIMESTAMPDIFF(SECOND, a.timestamp, b.timestamp) as time_gap_sec FROM user_behavior_log a JOIN user_behavior_log b ON a.user_id b.user_id AND a.timestamp b.timestamp AND b.timestamp DATE_ADD(a.timestamp, INTERVAL 1 HOUR) WHERE a.behavior_type play AND b.behavior_type play ORDER BY a.user_id, a.timestamp;这个视图的意义在于它把用户播放行为抽象为(user_id, current_song, next_song, time_gap)四元组为后续分析“听周杰伦后 30 分钟内最常跳转到哪位歌手”提供原子数据。Django 中通过raw()查询调用该视图# analytics/utils.py def get_transition_matrix(song_id, hours1): 获取某歌曲后的跳转概率矩阵 with connection.cursor() as cursor: cursor.execute( SELECT next_song, COUNT(*) as cnt FROM user_behavior_sequence WHERE current_song %s AND time_gap_sec %s*3600 GROUP BY next_song ORDER BY cnt DESC LIMIT 10 , [song_id, hours]) return cursor.fetchall()这就是答辩时能说清的“行为路径分析方法”不是靠感觉而是用时间窗口约束的跳转频次统计。3.3 歌单标签体系的 TF-IDF 构建歌单标题和描述文本稀疏直接用关键词匹配不准。项目用sklearn.feature_extraction.text.TfidfVectorizer构建轻量级标签模型# analytics/tfidf_builder.py from sklearn.feature_extraction.text import TfidfVectorizer import jieba def build_playlist_tfidf(): # 从 playlist_info 表读取所有歌单标题标签字符串 playlists PlaylistInfo.objects.values_list(title, tag_list) corpus [] for title, tag_json in playlists: tags json.loads(tag_json) if tag_json else [] # 拼接标题标签用结巴分词 text title .join(tags) words jieba.lcut(text) corpus.append( .join(words)) # 构建 TF-IDF 矩阵max_features1000避免维度爆炸 vectorizer TfidfVectorizer(max_features1000, ngram_range(1,2)) tfidf_matrix vectorizer.fit_transform(corpus) # 保存向量器和特征名供在线预测用 joblib.dump(vectorizer, tfidf_vectorizer.pkl) return vectorizer.get_feature_names_out() # 使用示例给新歌单打标签 def predict_playlist_tags(new_title, new_tags): vectorizer joblib.load(tfidf_vectorizer.pkl) text new_title .join(new_tags) words jieba.lcut(text) vec vectorizer.transform([ .join(words)]) # 返回 TF-IDF 值最高的前3个词 feature_array vectorizer.get_feature_names_out() tfidf_sort vec.toarray()[0].argsort()[-3:][::-1] return [feature_array[i] for i in tfidf_sort]这个模块的价值在于它让“歌单分析可视化系统”里的“相似歌单推荐”有据可依——不是随机推荐而是基于文本向量余弦相似度。design_report.pdf第 6.1 节的“标签体系构建流程图”就是这段代码的图形化表达。4. 关键避坑指南部署与调试中最容易翻车的 4 个点这套方案在某高校实验室部署过 12 次学生踩过的坑我都记在本子上。以下 4 条是答辩前夜最高频的崩溃现场按“现象→原因→解决”给出可立即执行的方案4.1 现象Scrapy 爬虫运行后 MySQL 里只有空表日志显示200 OK但无数据入库原因网易云音乐热歌榜 HTML 结构在 2023 年底更新原爬虫 XPath//ul[classf-hide]/li/a不再匹配新 DOM同时settings.py中ROBOTSTXT_OBEY True导致爬虫遵守 robots.txt 被拒。解决用 Chrome 打开https://music.163.com/#/discover/toplist?id3778678F12 查看新榜单li的 class 名实测为j-flag修改spiders/toplist_spider.py中的parse()方法# 原代码失效 # songs response.xpath(//ul[classf-hide]/li/a/href).getall() # 新代码2024年实测有效 songs response.xpath(//div[classj-flag]//a[contains(href,/song?id)]/href).getall()在settings.py中设ROBOTSTXT_OBEY False并添加DEFAULT_REQUEST_HEADERS模拟浏览器DEFAULT_REQUEST_HEADERS { Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 }4.2 现象Django 启动时报错django.core.exceptions.ImproperlyConfigured: Requested setting DATABASES, but settings are not configured原因.env文件未创建或settings.py中DATABASES配置依赖os.getenv()读取环境变量失败常见于 Windows 系统未安装python-decouple库。解决在项目根目录创建.env文件内容为DB_NAMEcloudmusic_db DB_USERroot DB_PASSWORDyour_password DB_HOST127.0.0.1 DB_PORT3306确保requirements.txt包含python-decouple3.8并执行pip install -r requirements.txt检查settings.py开头是否有from decouple import config DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: config(DB_NAME), USER: config(DB_USER), PASSWORD: config(DB_PASSWORD), HOST: config(DB_HOST, default127.0.0.1), PORT: config(DB_PORT, default3306), } }4.3 现象大屏页面加载后图表空白浏览器控制台报Uncaught ReferenceError: echarts is not defined原因static/js/echarts.min.js文件路径错误或index.html中script标签顺序不对ECharts 必须在charts.js之前加载。解决确认static/js/目录下存在echarts.min.js项目包里是echarts5.4.3版本检查templates/dashboard/index.html中 script 标签顺序!-- 必须第一 -- script src{% static js/echarts.min.js %}/script !-- 必须第二 -- script src{% static js/charts.js %}/script !-- 不能颠倒 --若用 Nginx 部署确认nginx.conf中location /static/指向正确路径且alias末尾有/location /static/ { alias /path/to/your/project/static/; }4.4 现象用户画像 API 返回空数据但 MySQL 中user_behavior_log表有百万条记录原因Django 默认开启ATOMIC_REQUESTS当user_behavior_log表数据量过大50 万行时annotate()查询会因锁表超时同时UserProfile表未建立user_id索引导致 JOIN 效率骤降。解决在models.py中为UserProfile.user_id添加数据库索引class UserProfile(models.Model): user_id models.CharField(max_length32, db_indexTrue) # 关键 # ... 其他字段运行python manage.py makemigrations python manage.py migrate生效在settings.py中调整数据库连接超时DATABASES { default: { # ... 其他配置 OPTIONS: { init_command: SET SESSION wait_timeout28800;, }, CONN_MAX_AGE: 600, # 连接池最大存活时间秒 } }5. 毕业答辩实战如何用 5 分钟讲清你的技术深度答辩不是代码朗诵而是用评委能听懂的语言把技术决策背后的思考链展示出来。我带过的 A 同学去年用这个项目答辩被问“为什么不用 Elasticsearch 做行为日志检索”他没背概念而是打开design_report.pdf第 3.2 节指着一张对比表格说“老师我试过 ES但发现两个问题第一我们日志写入是批量 INSERTES 的 bulk API 在千条/秒写入时 CPU 占用 90%而 MySQL 批量插入稳定在 30%第二ES 的聚合查询结果无法像 Django ORM 那样直接映射到 RFM 模型的annotate()字段得额外写一层转换逻辑——这会让论文‘第四章 系统实现’的代码截图失去连贯性。” 评委当场点头。这就是你要的效果。5.1 答辩 PPT 的三页黄金结构不要超过 12 页 PPT核心是这三页页码内容要点设计逻辑第 3 页系统架构图用 Visio 或 draw.io 重绘四层架构在每层旁手写标注 1 个技术选型理由- 采集层Scrapy→ “Rule 语法天然支持多级页面跳转”- 存储层MySQL→ “事务一致性保障 RFM 计算原子性”- 逻辑层Django REST→ “Serializer 自动校验字段类型避免前端传参错误”- 呈现层ECharts→ “Canvas 渲染器兼容答辩教室老旧投影仪”评委扫一眼就知道你不是抄的每个箭头都有思考第 6 页数据链路截图截三张图拼成一行1.scrapy crawl toplist -o data.json控制台输出证明爬到了2.mysql SELECT COUNT(*) FROM user_behavior_log;返回128432证明存进了3.http://localhost:8000/api/user-portrait/返回 JSON 中rfm_segment: high_value证明算对了用证据链代替口头承诺比说一百遍“已实现”都管用第 9 页创新点提炼表表格列传统做法 / 本项目做法 / 优势例“用户画像标签” → “人工定义关键词” / “TF-IDF 自动提取歌单文本特征” / “标签覆盖率提升 37%见附录测试报告”把详细说明文档.md里的测试数据转化成对比语言5.2 被问“你这个系统怎么保证数据真实性”的应答脚本别慌这个问题本质是问“你有没有伪造数据”。直接打开data_apply/README.md指向其中一段“所有原始数据均来自网易云音乐公开榜单URL: https://music.163.com/#/discover/toplist?id3778678爬虫日志已存档于data/raw_logs/20240512_toplist.log。为验证数据有效性我们抽样比对了 100 首歌曲的播放量爬虫抓取值与网页显示值误差 ≤ 0.3%符合学术研究容错范围。”然后补充一句“老师如果您需要我可以现场演示爬虫重新运行一次从榜单抓取到入库再到大屏刷新全程 3 分钟。” —— 这句话的杀伤力远超背十页技术原理。5.3 答辩前必做的三件事清单环境复位测试在一台全新安装的 Windows 10 虚拟机中从requirements.txt开始重装所有依赖执行python manage.py runserver确认大屏能打开、API 能返回数据、爬虫能成功入库。这是防止答辩当天因环境差异翻车的后悔药。文档交叉核对打开设计报告.pdf找到“第五章 系统测试”中的测试用例表随机挑 3 个用 Postman 调用对应 API截图保存。例如测试用例 TC-07“输入用户 ID返回其近 7 天播放歌曲 TOP5”你就调GET /api/user-behavior/?user_idU12345days7截图响应体。代码注释强化在views.py的UserPortraitViewSet.get_queryset()方法上方加一段中文注释# 【答辩重点】RFM 模型实现说明 # RRecency取 user_behavior_log 表中 behavior_typeplay 的最大 timestamp # FFrequency统计近30天 behavior_typeplay 的记录数 # MMonetary求和近30天 duration_sec60 的记录 duration_sec 字段 # 评分标准见 design_report.pdf 第 5.2 节表 5-1这样评委翻你代码时一眼就能定位到核心逻辑不会在几百行里迷失。从那以后我每次带学生做毕设都强制他们在答辩前一周把这三件事做完并邮件发给我确认。不是怕他们做不好而是怕他们把时间花在美化 PPT 上却忘了最该加固的是数据链路的真实性。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →