昨天完成 YouTube Music Takeout 的匯入器後,今天終於把自己的真實資料丟進去驗收。
要做自己的 Music Recap,不能只做到「資料讀得進來」。
真正重要的是:
這些資料到底能不能相信?
哪些欄位可以拿來排名?
哪些欄位有缺?
哪些數字看起來可以算,但其實不能亂算?
所以今天的目標,就是拿真正的 Google Takeout 觀看紀錄完整跑一次,確認目前的資料管線到底能不能支撐後面的 Recap。
結果第一次執行,就直接出事。
我的 YouTube Music 紀錄總共有:
15,403 筆
成功匯入:
0 筆
15,403 筆全部失敗。
這也讓我真正體會到,合成測試通過,不代表真實世界就會乖乖照你的格式走。
這次拿來驗證的是真正從 Google Takeout 匯出的 YouTube 觀看紀錄。
整份資料共有:
總活動紀錄:26,900
YouTube Music:15,403
一般 YouTube:11,497
這和 Day 1 當時初步分析得到的數量一致。
但第一版匯入器實際處理後,結果卻是:
成功匯入 YouTube Music:0
一般 YouTube:11,497
失敗:15,403
也就是說,程式其實知道哪些紀錄是 YouTube Music。
真正出問題的是時間。
我的 Takeout 時間長這樣:
2026年9月15日 下午6:52:27 CST
原本的時間處理沒有支援這種格式。
更麻煩的是,CST 本身並不是全球唯一的時區名稱。
不同地區可能用 CST 表示不同的時差。
所以不能直接看到 CST 就武斷認定:
CST = UTC+8
這次我的資料依照實際使用情境,以 +08:00 解讀。
但這個設定必須被明確記錄,而不是藏在程式裡偷偷假設。
這件事看起來只是時間格式問題,但對 Recap 其實很重要。
因為後面很多統計都跟時間直接相關,例如:
如果時區一開始就錯了,後面的圖表再漂亮也沒有意義。
解掉時區之後,還有下一層問題。
真實 Takeout 裡會出現:
凌晨
清晨
上午
中午
下午
晚上
例如:
凌晨12:30
清晨5:41
中午12:35
晚上7:43
在 15,403 筆 YouTube Music 活動中,這些時段的分布是:
凌晨:5,058
清晨:1,353
上午:1,211
中午:253
下午:3,374
晚上:4,154
原本沒有完整支援的「凌晨、清晨、中午」,加起來就有 6,664 筆。
所以今天也把這些繁體中文時間格式補完整。
例如:
凌晨12:30 → 00:30
中午12:30 → 12:30
晚上9:30 → 21:30
處理完之後,再重新跑完整份真實資料。
修正時間問題後,結果變成:
| 項目 | 結果 |
|---|---|
| 所有活動紀錄 | 26,900 |
| YouTube Music | 15,403 |
| 一般 YouTube | 11,497 |
| 無法解析 | 0 |
| 不同 Video ID | 1,197 |
這次 15,403 筆 YouTube Music 活動全部成功保留下來。
目前這批音樂紀錄的時間範圍是:
2026-01-31 ~ 2026-09-15
這也代表目前已經有一份真正經過完整驗收的 YouTube Music 活動資料,可以繼續拿來做後面的統計。
不過其中有一個很重要的限制:
1,197 個 Video ID
不代表:
1,197 首不同歌曲
因為同一首歌可能有很多不同影片,例如:
所以目前只能說有 1,197 個不同的影片識別碼。
「這些影片哪些其實代表同一首歌」,會是後面歌曲 Identity 要解決的問題。
真實資料也讓我發現 metadata 並不是每筆都完整。
15,403 筆音樂活動裡,有:
114 筆
沒有正常的頻道名稱。
目前採用的原則很保守。
如果同一個 Video ID 的其他活動中,只有一個一致而且可用的頻道名稱,才允許補值。
最後結果:
原始缺頻道:114
成功補回:87
仍然未知:27
剩下的 27 筆集中在兩個 Video ID。
其中一組有 26 筆,另一組只有 1 筆。
這兩組都找不到可靠的頻道名稱可以提供證據,所以最後沒有硬猜。
資料不知道,就是保持不知道。
比填入一個錯誤答案安全得多。
這 114 筆特殊紀錄還出現另一個有趣的問題。
它們的標題欄位並不是空值。
但內容其實只是:
https://music.youtube.com/watch?v=...
也就是影片網址。
如果只檢查:
標題是不是空的?
那它們全部會被算成「有標題」。
可是網址顯然不能當成正常歌曲名稱。
所以現在會保留原始內容,但把這種情況標記成 URL placeholder,不把它計算成可用標題。
目前真實資料的品質大概是:
| 欄位 | 覆蓋率 |
|---|---|
| 核心事件資料 | 15,403 / 15,403,100% |
| 可用原始標題 | 15,289 / 15,403,99.26% |
| 可用原始頻道 | 15,289 / 15,403,99.26% |
| 補值後可用頻道 | 15,376 / 15,403,99.82% |
| 實際播放時間 | 0 / 15,403,0% |
這也是我今天很想確認的一件事。
資料品質不能只寫一句:
完整度 99%
因為不同欄位的完整度完全不同。
真正有意義的是:
哪個欄位?
分子是多少?
分母是多少?
這次完整驗收後,最重要的結果其實不是 15,403 筆成功匯入。
而是確認:
15,403 / 15,403
都沒有實際播放秒數
也就是每一筆紀錄都知道:
但不知道:
這一次到底實際聽了幾秒?
這件事會直接影響後面的 Recap。
因為不能把:
一筆活動紀錄
當成:
完整播放一次
也不能用:
歌曲完整長度 × 出現次數
就宣稱是實際聆聽時間。
更不能因為兩筆紀錄相差四分鐘,就假設上一首歌真的完整播放了四分鐘。
那些都只是推測。
這裡最後決定先不要自己發明播放時間。
YouTube Music 官方 Recap 本身會顯示總聆聽時間。
所以第一版會直接把這個數字當成:
platform_provided
也就是:
YouTube Music 官方提供的總聆聽時間
它會和我們自己從 Takeout 算出的統計分開。
例如:
第一版暫時不做:
單曲推估聆聽時間
歌曲長度乘播放次數
自行猜測每次播放比例
缺的資料就保持缺失。
這樣至少不會為了讓 Recap 看起來更完整,反而製造一個不存在的精準數字。
經過今天這次驗收後,目前已經可以比較確定哪些統計是安全的。
例如:
可以依照活動次數排序。
但這個數字要稱為:
activity_count
不能直接叫完整播放次數。
可以統計。
但頻道名稱目前還不能直接等於歌手名稱。
例如:
YOASOBI - Topic
很接近歌手概念。
但:
THE FIRST TAKE
或其他上傳頻道就不一定代表歌曲真正的 Artist。
所以「頻道排行」和「歌手排行」也必須分開。
這個現在已經可以算。
因為時間格式已經經過真實資料驗證。
未來可以分析:
這部分不需要播放秒數,也能產生滿有意思的 Recap。
一開始看起來,今天好像只是在修時間格式。
但真正完成的事情其實是:
我終於知道這 15,403 筆資料哪些能信,哪些不能信。
現在已經可以明確知道:
事件時間 → 可用
Video ID → 可用
活動次數 → 可用
頻道 → 大部分可用
標題 → 大部分可用
實際播放秒數 → 不可用
歌曲 Identity → 尚未完成
Artist Identity → 尚未完成
這會直接決定之後 Recap 的每一個指標到底能不能顯示。
否則很容易做出一個看起來很厲害的年度回顧,結果底下其實混著錯誤假設。
今天真正完成的是:
YouTube Music 真實 Takeout
↓
26,900 筆活動完整解析
↓
15,403 筆音樂活動成功保留
↓
確認 metadata 缺口
↓
有證據的資料才補值
↓
確認沒有逐筆實際播放時間
↓
知道哪些資料可以拿去做 Recap
所以現在不只是「我可以讀 YouTube Music」。
而是開始建立一個更重要的東西:
每個 Recap 數字,都要知道自己是怎麼來的。
下一步,就是把這些規則真正變成 Recap 指標。
例如一個排名到底是:
activity_count
還是:
observed_played_ms
或是:
platform_provided
等這些統計口徑定下來後,就可以開始做第一份真正屬於自己的 YouTube Music Recap。
另外回覆前一篇的留言
Q:
把「不知道播放多久」和「播放零秒」分開,這個細節很容易被忽略。好奇之後產生 Music Recap 時,會不會同時顯示資料覆蓋率或可信程度?不然漂亮的排行可能會讓人忘記原始資料其實有缺口。
A:
有,這個問題我也有考慮到,所以目前資料模型除了把 0 和 unknown 分開,也會計算 duration_coverage。之後做 Recap 時,像 YouTube Music 這種沒有每筆播放秒數的來源且之後沒有解決的辦法的話,排行榜會明確標示是依「活動次數」計算,不會包裝成完整播放次數或真實收聽時間。後面做 Web App 時,我也打算把資料覆蓋率與可信度一起呈現,避免只給一個很漂亮但來源其實有缺口的排行。