Python音乐数据分析实战:爬虫+SQLite+可视化全流程 📅 发布时间:2026/9/15 0:57:56 👁 浏览次数: 简介这是一份面向高校Python课程学习者与初阶开发者的综合性大作业项目聚焦音乐播放软件的全栈实现覆盖网络爬虫获取音乐数据、SQLite数据库存储、Matplotlib/PyEcharts可视化分析及GUI界面开发四大核心能力适合作为高分课程设计或期末大作业参考。资源包共6个文件含1个主程序py文件含完整注释逻辑清晰易读、1个SQL数据库脚本建表与示例数据、1个Word手册含功能说明与运行指南、2张界面效果图展示UI布局与交互效果及1个ICO图标文件整体仅556KB轻量便携解压即用。已有279人学习下载项目代码结构规范、模块分工明确小白可快速上手理解进阶者亦可基于现有框架拓展在线搜索、播放列表持久化或API对接等二开功能是兼具教学性、实用性与延展性的优质Python实践案例。1. 用 Python 搭建一个能“听懂数据”的音乐播放软件从爬取歌单到生成听歌报告这不是一个带 UI 的播放器 Demo而是一套闭环的数据驱动型音乐工具链——它自动抓取主流平台公开的歌单与歌曲元数据不涉及用户登录态或私有接口把原始音频信息存进 SQLite 或 MySQL用 Pandas 清洗出播放趋势、歌手热度、风格分布再用 Matplotlib Plotly 输出交互式图表最后支持按「最近 7 天高频词」生成词云、「收藏量 Top20 歌单」导出 Excel、「某歌手作品时长分布」直方图等可落地的分析结果。适合课程设计、毕设选题、数据分析入门实战也适合作为爬虫数据库可视化三模块联动的标准化练手项目。你不需要会前端框架但需要理解 HTTP 请求边界、SQL 建模逻辑、图表坐标轴含义如果你刚学完 requests 和 pandas这个项目就是你第一次把“代码跑通”升级为“数据说话”的临界点。2. 爬虫模块用 requests BeautifulSoup 抓取网易云音乐公开歌单页非登录态2.1 为什么选网易云音乐公开榜单而非 QQ 音乐或酷狗网易云音乐对未登录用户的歌单列表页如https://music.163.com/#/discover/toplist?id3779629采用静态 HTML 渲染关键字段歌名、歌手、播放量、收藏数直接存在于ul classf-hide下的a标签中无需逆向 JS 加密或模拟点击。相较之下QQ 音乐榜单页依赖 Ajax 动态加载且返回加密 JSON酷狗则对 User-Agent 和 Referer 做强校验。本项目定位是教学级可复现性因此放弃高并发、高隐蔽性方案聚焦“最小可行抓取路径”。2.2 构建可重试、带基础反爬的请求会话import requests from bs4 import BeautifulSoup import time import random # 设置会话对象复用连接并携带必要头信息 session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://music.163.com/ }) def fetch_playlist_page(playlist_id: str, max_retries: int 3) - BeautifulSoup: url fhttps://music.163.com/playlist?id{playlist_id} for attempt in range(max_retries): try: response session.get(url, timeout10) response.raise_for_status() # 网易云音乐页面实际内容在 iframe 内需解析 body 中的 #content-frame # 但公开榜单页如 top list可直接解析主 HTML此处以榜单页为例 soup BeautifulSoup(response.text, html.parser) return soup except requests.exceptions.RequestException as e: print(f第 {attempt 1} 次请求失败: {e}) if attempt max_retries - 1: time.sleep(random.uniform(1, 3)) # 指数退避式等待 else: raise return None提示此代码不使用 Selenium 或 Playwright因目标页面无 JavaScript 渲染依赖若后续扩展至需登录的私人歌单则需切换为无头浏览器方案但本项目严格限定在公开数据范围内避免引入复杂依赖。2.3 解析歌单页并提取结构化字段网易云音乐公开榜单页中每首歌曲信息嵌套在li标签内典型结构如下li div classhd span classnum1/span /div div classbd div classf-thidea href/song?id1816121162起风了/a/div div classtext span classtxta href/artist?id12116112买辣椒也用券/a/span span classsep//span span classtxta href/album?id3779629起风了/a/span /div /div /li对应解析逻辑def parse_songs_from_soup(soup: BeautifulSoup) - list[dict]: songs [] # 定位所有歌曲 li 元素 song_items soup.select(ul.f-hide li) for item in song_items: try: title_tag item.select_one(a[href^/song?id]) artist_tag item.select_one(a[href^/artist?id]) if not title_tag or not artist_tag: continue song_id title_tag[href].split()[-1] title title_tag.get_text(stripTrue) artist artist_tag.get_text(stripTrue) # 播放量和收藏数需另发请求获取因不在当前页面此处设为占位符 # 实际项目中可调用 /song/detail 接口需构造合法 referer songs.append({ song_id: song_id, title: title, artist: artist, album: , # 可通过 album tag 补充 play_count: 0, favorite_count: 0, crawl_time: time.strftime(%Y-%m-%d %H:%M:%S) }) except Exception as e: print(f解析单条歌曲失败: {e}) continue return songs # 示例调用 soup fetch_playlist_page(3779629) # 热歌榜 ID songs parse_songs_from_soup(soup) print(f成功提取 {len(songs)} 首歌曲)2.3.1 关键参数说明song_id: 从/song?idxxx中提取作为后续关联数据库主键及去重依据title/artist: 使用.get_text(stripTrue)去除换行与空格避免入库时字段污染crawl_time: 记录抓取时间戳支撑后续「时间维度分析」如周环比播放量变化play_count和favorite_count当前设为 0因公开页不展示该数据——这是本项目明确的能力边界声明不伪造、不猜测、不调用未授权接口。3. 数据库模块SQLite 建模与批量写入兼顾课程设计可演示性与生产可迁移性3.1 为什么首选 SQLite 而非 MySQL 或 PostgreSQL课程设计场景下SQLite 具备零配置、单文件、Python 内置支持三大优势无需安装服务端、无需管理用户权限、import sqlite3即可用。更重要的是其 SQL 语法与 MySQL 高度兼容如CREATE TABLE,INSERT OR REPLACE,JOIN学生完成作业后只需修改连接字符串即可迁移到 MySQL符合“先跑通、再升级”的教学逻辑。本项目数据库设计遵循第三范式但允许适度冗余如歌手名重复存储以降低 JOIN 复杂度平衡可读性与性能。3.2 创建四张核心表并定义外键约束import sqlite3 def init_database(db_path: str music.db): conn sqlite3.connect(db_path) cursor conn.cursor() # 歌曲主表含唯一 song_id cursor.execute( CREATE TABLE IF NOT EXISTS songs ( id INTEGER PRIMARY KEY AUTOINCREMENT, song_id TEXT UNIQUE NOT NULL, title TEXT NOT NULL, artist TEXT NOT NULL, album TEXT, duration_sec INTEGER DEFAULT 0, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) # 歌单关联表解决多对多关系 cursor.execute( CREATE TABLE IF NOT EXISTS playlists ( id INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id TEXT UNIQUE NOT NULL, name TEXT NOT NULL, creator TEXT, description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) # 歌曲-歌单关联表记录某首歌属于哪些公开歌单 cursor.execute( CREATE TABLE IF NOT EXISTS song_playlist_map ( id INTEGER PRIMARY KEY AUTOINCREMENT, song_id TEXT NOT NULL, playlist_id TEXT NOT NULL, position INTEGER, -- 在歌单中的序号 FOREIGN KEY (song_id) REFERENCES songs(song_id), FOREIGN KEY (playlist_id) REFERENCES playlists(playlist_id), UNIQUE(song_id, playlist_id) ) ) # 分析快照表存储每次运行后的统计结果供可视化读取 cursor.execute( CREATE TABLE IF NOT EXISTS analysis_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, snapshot_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, top_artists TEXT, -- JSON 字符串如 [{name:周杰伦,count:12}] genre_distribution TEXT, -- 同上 avg_duration REAL, total_songs INTEGER ) ) conn.commit() conn.close() init_database()3.3 批量插入歌曲数据并处理重复主键def insert_songs_to_db(songs: list[dict], db_path: str music.db): conn sqlite3.connect(db_path) cursor conn.cursor() # 使用 executemany 提升性能避免逐条 INSERT insert_sql INSERT OR REPLACE INTO songs (song_id, title, artist, album, duration_sec, crawl_time) VALUES (?, ?, ?, ?, ?, ?) values [ ( s[song_id], s[title], s[artist], s.get(album, ), s.get(duration_sec, 0), s[crawl_time] ) for s in songs ] try: cursor.executemany(insert_sql, values) conn.commit() print(f成功写入 {len(values)} 条歌曲记录含更新) except sqlite3.IntegrityError as e: print(f数据库写入异常: {e}) finally: conn.close() # 调用示例 insert_songs_to_db(songs)3.3.1 参数设计深意INSERT OR REPLACE: 替代INSERT IGNOREMySQL 语法当song_id冲突时自动覆盖旧记录适应爬虫增量更新场景duration_sec字段预留但暂不填充因公开页面无时长信息——后续若接入第三方 API如 NCM API 公开端点可在此字段补全analysis_snapshots表不存原始数据只存聚合结果隔离分析层与原始层避免可视化查询拖慢主业务表。4. 数据分析与可视化模块用 Pandas 统计歌手热度用 Plotly 输出交互式柱状图4.1 用 Pandas 从 SQLite 提取并聚合歌手出现频次import pandas as pd import sqlite3 def get_artist_frequency(db_path: str music.db) - pd.DataFrame: query SELECT artist, COUNT(*) as frequency FROM songs GROUP BY artist ORDER BY frequency DESC LIMIT 20 conn sqlite3.connect(db_path) df pd.read_sql_query(query, conn) conn.close() return df # 执行分析 df_artist get_artist_frequency() print(df_artist.head(10))4.2 用 Plotly Express 绘制可缩放、可下载的 Top20 歌手柱状图import plotly.express as px def plot_top_artists(df: pd.DataFrame, output_html: str top_artists.html): # 设置中文字体需系统已安装 SimHei 或 Noto Sans CJK fig px.bar( df, xartist, yfrequency, titleTop 20 歌手出现频次基于爬取的公开歌单, labels{artist: 歌手, frequency: 出现次数}, colorfrequency, color_continuous_scaleBlues ) # 强制 X 轴标签垂直显示避免重叠 fig.update_layout( xaxis_tickangle-45, height600, fontdict(size14), title_font_size18, showlegendFalse ) # 导出为独立 HTML 文件双击即可打开交互 fig.write_html(output_html) print(f图表已保存至 {output_html}) plot_top_artists(df_artist)注意Plotly 默认使用英文若图表中文乱码请确保系统字体支持并在代码开头添加import matplotlib.pyplot as pltplt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS]此设置影响 MatplotlibPlotly 需依赖系统字体Windows 一般自带 SimHei4.3 生成词云基于所有歌名提取高频词from wordcloud import WordCloud import jieba import matplotlib.pyplot as plt def generate_title_wordcloud(db_path: str music.db, output_path: str title_wordcloud.png): conn sqlite3.connect(db_path) titles pd.read_sql_query(SELECT title FROM songs, conn)[title].tolist() conn.close() # 中文分词 text .join(titles) words jieba.lcut(text) # 过滤停用词简易版实际项目应加载外部停用词表 stopwords {的, 了, 在, 是, 我, 有, 和, 就, 不, 人, 都, 一, 一个} filtered_words [w for w in words if len(w) 1 and w not in stopwords] # 生成词云 wc WordCloud( font_pathsimhei.ttf, # Windows 系统字体路径macOS 用 /System/Library/Fonts/PingFang.ttc width1200, height800, background_colorwhite, max_words100 ).generate( .join(filtered_words)) plt.figure(figsize(10, 8)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.savefig(output_path, dpi300, bbox_inchestight) plt.show() print(f词云已保存至 {output_path}) generate_title_wordcloud()4.3.1 词云关键参数说明font_path: 必须指定中文字体路径否则显示方块Windows 默认simhei.ttf位于C:\Windows\Fonts\max_words100: 控制词云最大词汇量避免低频词干扰视觉焦点background_colorwhite: 适配导出 PNG 场景如需嵌入网页可改为transparent。5. 项目整合与调试技巧如何快速验证每个模块是否正常工作5.1 三步验证法从爬虫输出 → 数据库记录 → 图表渲染不要等到全部写完才测试。按顺序执行以下命令任一环节失败立即定位验证爬虫是否拿到数据python -c from crawler import fetch_playlist_page, parse_songs_from_soup soup fetch_playlist_page(3779629) songs parse_songs_from_soup(soup) print(f抓取到 {len(songs)} 首歌示例{songs[0] if songs else 空}) ✅ 预期输出抓取到 50 首歌示例{song_id: 1816121162, title: 起风了, ...}验证数据库是否写入sqlite3 music.db SELECT COUNT(*) FROM songs;✅ 预期输出50与上步数量一致验证可视化是否生成文件ls -lh top_artists.html title_wordcloud.png✅ 预期输出两个非零字节文件存在5.2 常见报错与精准修复方案报错现象根本原因修复命令/代码requests.exceptions.ConnectionError网易云音乐临时封禁 IP 或域名解析失败在fetch_playlist_page中增加session.get(..., proxies{http: http://127.0.0.1:8080})测试代理连通性或更换 User-Agent 字符串sqlite3.OperationalError: no such table: songsinit_database()未执行或路径错误运行python -c from database import init_database; init_database()确认music.db文件生成ValueError: max() arg is an empty sequence词云报错titles列表为空即数据库无数据执行sqlite3 music.db SELECT COUNT(*) FROM songs;若为 0 则回溯爬虫模块词云中文显示为方块font_path路径错误或字体文件缺失Windows 下运行dir C:\Windows\Fonts\simhei*确认文件存在macOS 下用fc-list :langzh查看可用中文字体5.3 一键运行脚本整合全流程main.py# main.py if __name__ __main__: print( 开始执行音乐数据分析全流程 \n) # 步骤1爬取热歌榜 print(1. 抓取网易云热歌榜...) from crawler import fetch_playlist_page, parse_songs_from_soup soup fetch_playlist_page(3779629) songs parse_songs_from_soup(soup) # 步骤2初始化并写入数据库 print(2. 初始化数据库并写入数据...) from database import init_database, insert_songs_to_db init_database() insert_songs_to_db(songs) # 步骤3生成分析图表 print(3. 生成歌手热度图与词云...) from visualization import plot_top_artists, generate_title_wordcloud import pandas as pd df pd.read_sql_query(SELECT artist, COUNT(*) c FROM songs GROUP BY artist ORDER BY c DESC LIMIT 20, sqlite:///music.db) plot_top_artists(df) generate_title_wordcloud() print(\n✅ 全流程执行完毕查看 top_artists.html 和 title_wordcloud.png)运行python main.py全程无交互、无报错、输出两个可视化文件——这就是本项目交付的最小完整形态。本文还有配套的精品资源点击获取