據(jù)分析實戰(zhàn):網(wǎng)易云歌單爬取與可視化分析系統(tǒng))
簡介一份Python數(shù)據(jù)分析初探項目基于數(shù)據(jù)可視化技術實現(xiàn)網(wǎng)易云音樂歌單分析適合高校學生完成期末大作業(yè)也適合數(shù)據(jù)分析初學者作為課程設計參考。項目使用requests與bs4獲取歌單數(shù)據(jù)經(jīng)pandas清洗后利用matplotlib和squarify繪制評論、收藏、播放、貢獻分布圖并結合jieba分詞與wordcloud生成詞云最后從數(shù)據(jù)角度提出歌單優(yōu)化建議完整覆蓋數(shù)據(jù)采集、清洗、分析、可視化呈現(xiàn)全流程便于學習者逐步復現(xiàn)和驗證。壓縮包共38個文件以12個py源碼為核心輔以11個pyc編譯文件、7個png可視化成圖、3個csv數(shù)據(jù)集另含pdf、docx、md格式的項目說明、Markdown筆記和ttf字體文件整體約12.23MB目錄層級清楚配合文檔可快速掌握項目脈絡。已有785人學習下載既能鞏固Python語法和常用第三方庫協(xié)作方式也為后續(xù)數(shù)據(jù)處理項目提供了可復用模板對完成期末設計或入門數(shù)據(jù)分析均有參考價值。1. 網(wǎng)易云歌單分析系統(tǒng)期末高分項目的真實落點判斷一張網(wǎng)易云歌單有沒有潛力播放量不是最準的指標收藏比播放更說明問題。這套基于 Python 數(shù)據(jù)可視化的網(wǎng)易云音樂歌單分析系統(tǒng)抓取歌單真實數(shù)據(jù)后用 pandas 和 matplotlib 把評論數(shù)、收藏數(shù)、播放量分布圖和詞云逐一畫出來再從圖表里反推優(yōu)化方向。對要交 Python 數(shù)據(jù)分析期末大作業(yè)的人來說這套源碼加文檔的最大價值是把“爬蟲→清洗→可視化→結論”整條鏈路完整跑通每一步都有真實代碼對應不是那種只有 PPT 和截圖的水作業(yè)。它適合三類人第一次做數(shù)據(jù)分析項目的在校生、想快速上手 requestspandas 全流程的初學者、需要高完成度參考項目的課程設計選手。文檔包里附帶的手冊.docx 和結課說明 PDF 可以直接對照著看代碼怎么組織省去自己猜結構的時間。2. 數(shù)據(jù)從哪里來requestsbs4 爬取網(wǎng)易云歌單的完整流程2.1 為什么選 requestsbs4 而不是 Selenium第一次拿到這個題目的人多半會想用 Selenium 去無頭瀏覽器模擬點擊把頁面渲染出來再抓。我的觀點是期末大作業(yè)別這么干Selenium 慢、吃內存而且一旦頁面改版一堆 XPath 全得重寫。網(wǎng)易云歌單廣場本身有服務端渲染的頁面用 requests 直接拿 HTML 再用 BeautifulSoup 解析速度和穩(wěn)定性都好很多。這個項目里用的也是這套方案requests 負責請求bs4 負責解析。當然requests 方案的前提是頁面關鍵數(shù)據(jù)不是異步加載的歌單廣場目前符合這個條件這也是 Python 爬蟲入門階段性價比最高的方案。2.2 初始化請求session、headers 與翻頁參數(shù)爬蟲的第一步是把請求偽裝成正常瀏覽器否則網(wǎng)易云會返回空頁面或者驗證碼頁。這里有個容易被忽略的細節(jié)requests.get 拿到歌單首頁時最好把響應保存到 session 里后面的請求統(tǒng)一用同一個 session保持 Cookie 連續(xù)。import requests import time import random session requests.Session() # 第一次訪問主頁目的是讓 session 拿到基礎 Cookie session.get(https://music.163.com/, timeout10) 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://music.163.com/, } def fetch_playlist_page(offset0): params { order: hot, # 按熱度排序 cat: 全部, # 歌單分類 limit: 36, # 每頁數(shù)量最大 36 offset: offset # 偏移量第 2 頁就是 36 } resp session.get( https://music.163.com/discover/playlist, paramsparams, headersheaders, timeout15 ) resp.encoding utf-8 print(狀態(tài)碼:, resp.status_code, | 偏移量:, offset) return resp.text這里的三個參數(shù)在調試時最常調整limit 超過 36 會被服務端忽略offset 用來翻頁cat 可以換成“華語”“純音樂”等具體分類去抓不同領域。Referer 頭一定要帶這是網(wǎng)易云反爬校驗的一個常規(guī)檢查項。打印狀態(tài)碼是為了第一時間發(fā)現(xiàn)問題比如 418 或 403 都說明頭部信息沒偽裝到位。2.3 頁面解析把 HTML 里的歌單字段提取出來拿到 HTML 之后用 BeautifulSoup 的選擇器去定位歌單卡片。每一張歌單卡片里包含標題、播放量、貢獻者和歌單 id。選擇器在網(wǎng)易云不同版本里會有變化所以寫代碼前先在瀏覽器開發(fā)者工具里按 F12 確認一次實際 class 名花不了兩分鐘卻能省掉一整晚的調試。from bs4 import BeautifulSoup def parse_playlist(html): soup BeautifulSoup(html, html.parser) rows soup.select(div.u-mhplay) records [] for row in rows: title_node row.select_one(a.title) play_node row.select_one(span.nb) if not title_node or not play_node: continue # 歌單詳情頁鏈接形如 /playlist?idxxxid 用來后續(xù)去重 detail_link title_node.get(href, ) playlist_id detail_link.split(id)[-1] if id in detail_link else records.append({ title: title_node.get(title) or title_node.get_text().strip(), play_count: play_node.get_text().strip(), playlist_id: playlist_id, }) # 單條歌單解析完成后稍作停頓拉長請求間隔 time.sleep(random.uniform(0.3, 0.8)) return recordstitle_node.get(title) 取的是懸停提示里的完整歌單名比 get_text() 更可靠因為歌單名太長時頁面顯示是被截斷的。播放量字段拿到的是格式化文本可能是“12.3萬”也可能是“87654”這個不一致問題會帶到數(shù)據(jù)清洗階段集中處理。解析每條之后停頓 0.3 到 0.8 秒是避免短時間內請求太密集把自己 IP 送進風控名單的土辦法簡單但有效。如果后續(xù)想抓評論數(shù)和收藏數(shù)在歌單詳情頁里同樣能找到對應節(jié)點字段結構不同但思路一致。2.4 落盤 CSV為 pandas 清洗做準備解析結果最好先落到 CSV 再交給 pandas而不是直接傳給下一個函數(shù)。原因有兩個一是爬蟲中途斷掉時CSV 里已經(jīng)有的數(shù)據(jù)不會丟二是數(shù)據(jù)清洗時反復讀文件比反復跑網(wǎng)絡請求快得多。這也是我處理所有爬蟲項目的一致習慣。import pandas as pd all_records [] for offset in range(0, 108, 36): # 抓 3 頁共 108 張歌單 html fetch_playlist_page(offset) records parse_playlist(html) all_records.extend(records) df pd.DataFrame(all_records) df.to_csv(netease_playlist.csv, indexFalse, encodingutf-8-sig) print(抓取完成共, len(df), 條記錄)保存時用 utf-8-sig 而不是 utf-8是給 Windows 用戶的老經(jīng)驗Excel 直接打開無 BOM 的 UTF-8 CSV 會把中文全部顯示成亂碼加 BOM 之后雙擊就能正???。到這里數(shù)據(jù)分析的第一步數(shù)據(jù)采集就算完成了接下來進入清洗階段。3. 數(shù)據(jù)清洗與預處理pandas 把臟數(shù)據(jù)變成可分析的表格3.1 先摸清字段現(xiàn)狀info() 和 head() 的必要性爬下來的數(shù)據(jù)不能直接畫圖因為歌單頁面的字段格式是為“人看”設計的不是為“統(tǒng)計”設計的。先用 pandas 把這些記錄讀進來看一遍字段類型和缺失情況再決定怎么處理。import pandas as pd import numpy as np df pd.read_csv(netease_playlist.csv, encodingutf-8-sig) print(df.info()) print(df.head(10)) print(去重前條數(shù):, len(df))df.info() 可以看到每一列的非空數(shù)量列里如果有 object 類型說明是文本int 或 float 才是數(shù)值。這一步能快速發(fā)現(xiàn)兩類問題play_count 列其實是字符串因為里面有“萬”playlist_id 列可能有空值或重復值這直接決定后面用什么字段做去重依據(jù)。3.2 數(shù)量字段規(guī)范化把“萬”“億”換算成統(tǒng)一數(shù)值播放量字段常見的格式有“87654”和“12.3萬”兩種混著出現(xiàn)如果直接畫圖matplotlib 會把“12.3萬”當成字符串處理直方圖直接報錯。清洗邏輯很簡單把單位換算成純數(shù)字即可。評論數(shù)、收藏數(shù)字段如果也帶單位后綴同樣復用這個函數(shù)。def clean_count(value): if pd.isna(value): return np.nan value str(value).replace(,, ).strip() if 億 in value: return float(value.replace(億, )) * 100000000 if 萬 in value: return float(value.replace(萬, )) * 10000 try: return float(value) except ValueError: return np.nan df[play_count_num] df[play_count].apply(clean_count)這里先判斷缺失值再格式化避免把 NaN 字符串化。replace(,, ) 是為了兼容“12,345”這種千分位寫法。單位判斷要從“億”開始因為“1.2億”里也包含“萬”字先處理億再處理萬才不會算錯十倍。后面如果抓了評論數(shù)和收藏數(shù)對對應列執(zhí)行 df[評論列].apply(clean_count) 即可。3.3 去重與缺失值處理以歌單 id 為準同一份歌單可能在推薦位和排行榜里重復出現(xiàn)需要去掉。最穩(wěn)的去重字段是 playlist_id而不是標題因為同名歌單可能指向不同內容。去重之后再看關鍵字段的缺失情況標題為空或播放量為空的行直接丟棄因為這兩列是后續(xù)可視化的核心字段。df df.drop_duplicates(subset[playlist_id], keepfirst) df df.dropna(subset[title, play_count_num]) print(去重后條數(shù):, len(df)) print(播放量描述統(tǒng)計:\n, df[play_count_num].describe())drop_duplicates 的 keepfirst 表示遇到重復 id 時保留第一次出現(xiàn)的記錄。dropna 之后再用 describe() 看播放量的均值、中位數(shù)和分位數(shù)這一步可以提前發(fā)現(xiàn)異常值比如某個歌單播放量是幾十億明顯是測試數(shù)據(jù)后面畫圖時可以單獨過濾掉。3.4 給歌單補一個分類字段關鍵詞映射的土辦法歌單廣場的“全部”分類頁面拿不到每條歌單的準確分類標簽因為分類信息在詳情頁里逐條訪問詳情頁又會讓爬蟲工作量翻倍。期末作業(yè)階段沒有必要搞那么重我一般直接用標題關鍵詞做粗分類覆蓋主流歌單類型就夠用。def guess_category(title): title str(title) rules { 華語: [華語, 國語, 中文], 粵語: [粵語, 港樂], 純音樂: [純音樂, 鋼琴, 輕音樂, 治愈], 民謠: [民謠, 吉他], 搖滾: [搖滾, 樂隊, 金屬], 電子: [電子, 電音, DJ], 影視: [影視, OST, 電影, 電視劇], } for category, keywords in rules.items(): for keyword in keywords: if keyword in title: return category return 其他 df[category] df[title].apply(guess_category) print(df[category].value_counts())分類規(guī)則表寫在函數(shù)外面方便隨時增刪關鍵詞。這個步驟的價值不在于分類多精準而在于后續(xù)可以按分類做組間對比比如比較“純音樂”和“華語”兩個分類的平均收藏數(shù)。分析報告里能拿出這種對比結論比單純貼一張圖表要有說服力得多。4. 數(shù)據(jù)可視化落地matplotlib 畫出播放量分布與分類占比4.1 先把中文字體問題解決掉否則后面全白干matplotlib 默認字體是英文字體直接用中文標題會顯示成一個個方塊。這個問題在 Windows 上處理起來很簡單指定黑體 SimHei 即可。Linux 服務器上如果沒有 SimHei就要手動下載字體注冊到 matplotlib后面避坑章節(jié)會單獨講。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False這兩行配置要放在所有畫圖代碼之前。axes.unicode_minus 設置為 False 是為了解決坐標軸上的負號顯示成方塊的問題。之后所有 plt.title()、plt.xlabel() 里的中文都能正常渲染。如果運行環(huán)境是 macOS把 SimHei 換成 Arial Unicode MS 同樣有效。4.2 播放量分布直方圖長尾效應一眼可見清洗完的數(shù)據(jù)里有一條很明顯的規(guī)律大部分歌單播放量集中在較低區(qū)間少數(shù)頭部歌單播放量極高。直接畫全量數(shù)據(jù)橫軸會被極端值拉得很寬低區(qū)間的分布細節(jié)完全看不清。常見做法是先用 describe() 看一下分位數(shù)把 99 分位以上的極端值暫時濾掉再畫圖。limit_value df[play_count_num].quantile(0.99) filtered df[df[play_count_num] limit_value] fig, ax plt.subplots(figsize(10, 6)) ax.hist(filtered[play_count_num], bins40, color#128C7E, edgecolorwhite) ax.set_xlabel(播放量) ax.set_ylabel(歌單數(shù)量) ax.set_title(歌單播放量分布圖去掉前 1% 極端值) plt.tight_layout() plt.savefig(play_count_dist.png, dpi150) plt.show()bins 設為 40讓直方圖的粒度既不至于太粗看不出形狀也不至于太細產(chǎn)生大量空柱子。標題里注明“去掉前 1% 極端值”是給看報告的人一個數(shù)據(jù)口徑交代這也是期末大作業(yè)答辯時老師喜歡看到的細節(jié)——你能說清楚圖里每一層處理邏輯是什么。運行環(huán)境沒圖形界面時去掉 plt.show() 只保留 plt.savefig() 即可。4.3 播放量與收藏、評論的散點關系找正相關還是弱相關只畫單變量分布不足以支撐“歌單優(yōu)化建議”這個結論。播放、收藏、評論三個數(shù)字之間有沒有關聯(lián)才是分析的重點。散點圖加一條趨勢線是最直觀的呈現(xiàn)方式。這里以播放量和評論數(shù)為例如果爬蟲階段抓到了收藏數(shù)字段把 y 換成收藏列即可。x df[play_count_num] y df[comment_num] # 清洗階段處理過的評論數(shù)字段 valid (x 0) (y 0) x x[valid] y y[valid] fig, ax plt.subplots(figsize(10, 6)) ax.scatter(x, y, alpha0.4, s8, color#FF5C5C) ax.set_xscale(log) ax.set_yscale(log) ax.set_xlabel(播放量對數(shù)坐標) ax.set_ylabel(評論數(shù)對數(shù)坐標) ax.set_title(播放量與評論數(shù)關系散點圖) plt.tight_layout() plt.savefig(play_comment_scatter.png, dpi150) plt.show()這里采用了 log-log 坐標因為播放量和評論數(shù)都跨越了好幾個數(shù)量級線性坐標下絕大多數(shù)點會擠在左下角什么都看不出來。valid 過濾條件把評論數(shù)為 0 的行拿掉否則取對數(shù)會變成負無窮圖上直接缺一塊。alpha0.4 控制點的透明度點重疊嚴重時能看到密度差異。如果散點整體呈正相關趨勢說明評論數(shù)隨播放量同步增長歌單運營的重點是拉播放如果中高播放量區(qū)間的評論數(shù)偏低說明歌單內容雖然被聽但缺少互動引導優(yōu)化建議就要往評論區(qū)運營方向寫。4.4 矩形樹圖歌單分類的占比關系一眼抓住分類占比用餅圖畫當然可以但在分類多、名稱長時餅圖的標簽容易擠成一團。squarify 生成的矩形樹圖面積代表數(shù)量、標簽放在矩形內部信息密度更高也更適合放進期末報告里當亮點。import squarify category_counts df[category].value_counts().head(8) sizes category_counts.values labels [f{idx}\n{val}張 for idx, val in category_counts.items()] colors plt.cm.tab20.colors[:len(sizes)] fig, ax plt.subplots(figsize(10, 6)) squarify.plot(sizessizes, labellabels, colorcolors, axax, alpha0.85) ax.axis(off) ax.set_title(歌單分類數(shù)量占比矩形樹圖) plt.tight_layout() plt.savefig(category_treemap.png, dpi150) plt.show()value_counts().head(8) 只取數(shù)量最多的 8 個分類剩下的“其他”類別因為數(shù)量分散畫進去會讓圖面碎掉。label 里的 val 是每類數(shù)量直接把數(shù)字放進圖里報告里引用時不用再回去翻代碼統(tǒng)計。color 用 tab20 的顏色映射保證顏色不重復也不需要額外引入 seaborn 依賴。5. 高頻踩坑排查這份代碼最容易翻車的四個位置5.1 帶 # 的鏈接抓回來是空殼頁面現(xiàn)象requests 請求 https://music.163.com/#/playlist?idxxx返回的 HTML 里找不到任何歌單內容。原因網(wǎng)易云前端是單頁應用URL 里 # 后面的路由由瀏覽器端 JavaScript 解析服務端只返回空的頁面框架。requests 不會執(zhí)行 JS自然拿不到內容。解決去掉 #直接請求 https://music.163.com/playlist?idxxx或者在歌單廣場翻頁用 discover/playlist 這種服務端渲染的列表頁。5.2 播放量單位混在一起直方圖報錯現(xiàn)象繪圖時 ValueError提示無法將字符串轉換為 float檢查數(shù)據(jù)發(fā)現(xiàn)播放量列同時存在“5.3萬”和“123456”。原因頁面不同位置的格式化規(guī)則不一致有的用單位縮寫有的用純數(shù)字。解決在 3.2 的 clean_count 函數(shù)里統(tǒng)一處理先判斷“億”再判斷“萬”最后轉 float。處理完重新檢查 df.dtypes確保 play_count_num 是 float64 再畫圖。這個坑幾乎每個做中文數(shù)據(jù)可視化的人都會踩一次清洗步驟千萬別省。5.3 中文字體方塊與負號異常現(xiàn)象plt.title(播放量分布) 出來的圖標題是□□□□□坐標軸負號變成豎線。原因matplotlib 默認字體 DejaVu Sans 不包含中文字形負號渲染也依賴字體。解決Windows 下配置 plt.rcParams[font.sans-serif] [SimHei]Linux 環(huán)境下先確認系統(tǒng)有沒有中文字體沒有就通過 font_manager.addfont() 注冊自己下載的字體文件然后清掉 matplotlib 的字體緩存重新運行。處理完別再手動給每張圖傳字體路徑統(tǒng)一配置就行。5.4 單一頁面反復請求被觸發(fā)驗證碼現(xiàn)象爬著爬著狀態(tài)碼突然變成 418或者偶爾返回一個需要點擊驗證的頁面。原因同一 IP 的請求頻率過高觸發(fā)了網(wǎng)易云的風控策略。解決每個請求之間插入 time.sleep(random.uniform(0.3, 0.8))降低請求密度同時固定維持同一個 session避免每次請求都重新握手。被抓了不要硬剛把已抓數(shù)據(jù)先落盤停幾分鐘再繼續(xù)。如果你是校園網(wǎng)出口同一個 IP 后面可能還有別人在爬頻率要比正常情況再低一檔。5.5 pandas 讀取 CSV 時中文列名變成亂碼現(xiàn)象用 pandas 讀 CSV列名和內容全是亂碼但記事本打開正常。原因CSV 保存時用了 GBK 編碼pandas 默認按 UTF-8 讀取。解決讀取時指定 encodinggbk或者保存時就統(tǒng)一用 utf-8-sig。這個項目里統(tǒng)一用 utf-8-sig 保存Windows 下 Excel 和 pandas 都能正常打開不需要來回切換編碼。6. 從“能跑”到“能答辯”詞云進階與驗證收尾6.1 jieba 分詞 wordcloud 生成歌單標題詞云歌單標題是最能反映平臺熱門趨勢的文本數(shù)據(jù)。用 jieba 分詞拆出關鍵詞再過濾掉“歌單”“精選”這類高頻但無區(qū)分度的詞最后交給 wordcloud 生成詞云。這里最關鍵的一個參數(shù)是 font_path不指定中文字體詞云里所有文字會變成豆腐塊。import jieba from wordcloud import WordCloud stopwords {歌單, 精選, 熱門, 經(jīng)典, 推薦, 合集} text .join(df[title].astype(str).tolist()) words jieba.cut(text, cut_allFalse) filtered [w for w in words if len(w) 1 and w not in stopwords] wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, # Linux 下?lián)Q成實際字體路徑 width800, height600, background_colorwhite, max_words150, collocationsFalse ).generate( .join(filtered)) wc.to_file(title_wordcloud.png)max_words 控制詞云顯示的詞語數(shù)量設置太大圖會顯得雜亂collocationsFalse 避免 wordcloud 把相鄰詞合成新詞組對中文文本基本是必選項。stopwords 集合可以按你實際抓到的標題內容擴充多過濾幾個高頻無意義詞詞云的可讀性會明顯提升。6.2 從圖表到結論三句可以寫進報告的分析判斷圖表畫完不算結束報告里要有能落地的分析結論?;谶@個項目的數(shù)據(jù)我通常提煉三條判斷。第一播放量分布嚴重長尾頭部歌單拿走大部分流量新歌單需要走差異化定位而不是跟頭部直接競爭。第二播放量與評論數(shù)呈正相關但高播放區(qū)間的評論密度明顯低于低區(qū)間說明大歌單缺少互動轉化設計運營重點應該放在評論區(qū)引導。第三分類矩形樹圖里“純音樂”和“華語”占比較高與平臺用戶收聽習慣一致做歌單可以優(yōu)先布局這兩類。每一條都要帶上你實際算出的數(shù)字比如“前 5% 的歌單占了總播放量 62%”而不是空寫“數(shù)據(jù)顯示”。6.3 答辯之前的三步驗證清單我每次交這類數(shù)據(jù)作業(yè)前都會把下面三步走一遍抽 5 張歌單去網(wǎng)易云頁面人工核對播放量防止爬蟲解析誤差把清洗后的數(shù)據(jù)重新 describe 一次確認沒有負數(shù)和離譜的極端值檢查每張圖的標題、坐標軸標簽和單位是否齊全圖表里所有中文是否正常渲染。這三步全過作品基本不會有低級翻車。語法層面的運行問題按第 5 章逐條對照排查即可。從那以后我每做一個 Python 數(shù)據(jù)分析項目都強制先驗證字體、單位一致性和樣本量再談畫圖和分析這套習慣幫我少熬了很多夜希望幫到你。本文還有配套的精品資源點擊獲取