以前想知道某個 YouTube 頻道有沒有開營利,有個很多教學都在教的做法:打開影片頁的原始碼,搜尋 is_monetization_enabled,看它是 true 還是 false。這招在 2023 年 11 月失效了,YouTube 把這個欄位從頁面上拿掉,之後沒有任何公開欄位直接寫出營利狀態。
欄位拿掉之後,從外部只能根據頻道公開露出的東西去推論。我們做 MonetizationKit 的檢測器時,把這套推論拆成五個訊號,每個訊號都會回傳通過、未通過或未知三種狀態,最後再組合成四種結論。這篇把資料從哪裡抓、每個訊號怎麼解析、各自會在哪裡誤判,以及規則本身怎麼讓整份資料集出現偏差,一次寫清楚。
先講結論:這些訊號能支持判斷,但不能確認頻道實際有沒有分潤;要確認,仍要看頻道主自己的 YouTube 工作室。
YouTube 合作夥伴計畫(YPP)目前有兩個級別,數字以 YouTube 說明中心為準(2026 年 11 月核對):
| 條件 | 粉絲贊助級 | 廣告分潤級 |
|---|---|---|
| 訂閱人數 | 500 | 1,000 |
| 過去 90 天公開上傳 | 3 部 | 不要求 |
| 過去 365 天有效公開長影片觀看時數 | 3,000 小時 | 4,000 小時 |
| 或:過去 90 天有效 Shorts 觀看次數 | 300 萬次 | 1,000 萬次 |
粉絲贊助級可以開頻道會員、超級留言、超級感謝等功能,也能連結商店;廣告分潤級才有廣告收益分成。另外,2027 年 2 月 1 日起,新申請廣告分潤級的頻道要改成 8,000 小時或 2,000 萬次 Shorts 觀看,訂閱人數仍是 1,000,粉絲贊助級不變。
這張表跟後面的判斷直接相關:會員按鈕和商店是 YPP 才有的功能,但從 500 訂閱的級別就能開,所以看到它們只能證明「在計畫裡」,不能證明「有拿廣告分潤」。
檢測器只抓三種頁面:
/watch?v=...:讀播放器設定裡的廣告位。這三種頁面的 HTML 都內嵌了一大段 JSON。頻道頁是 ytInitialData,觀看頁是 ytInitialPlayerResponse,前端用它來渲染畫面,我們用它來讀結構化資料,不需要去解析 DOM。
有兩條路我們刻意不走。第一是 /youtubei/ 這組內部 API,YouTube 的 robots.txt 對所有爬蟲都禁止這個路徑,我們的程式在每次請求和每一跳重新導向都會先比對一份抄下來的 robots.txt 禁止清單。第二是 YouTube Data API:我們讀 API 開發者政策的理解是,API 使用者不能再去抓 YouTube 頁面,而會員按鈕、商店、廣告這三個關鍵訊號只有頁面上才有,兩者沒辦法兼得,所以整個檢測流程不用官方 API。API 原本給的訂閱數本來就只到三位有效數字,About 頁上的精度一樣,換掉沒有損失。
請求帶的是可辨識的爬蟲 User-Agent,不登入、不偽裝成瀏覽器。
從頻道頁頭讀訂閱數,S1 看是否達到 1,000,S5 看是否達到 500。
可靠性:這是資格條件,不是結果。超過 1,000 訂閱卻沒加入計畫的頻道很多;反過來,早期加入的頻道後來可能掉粉,頁頭的數字就會低於門檻。所以 S1 只檢查目前訂閱數是否達到廣告分潤級的申請門檻,不能確認營利狀態。S5 比較有用的是它的反面:低於 500 且沒有其他正面訊號時,我們判為「可能未營利」,但仍保留頻道加入後才掉粉的可能。
要注意兩種「拿不到」:頁頭讀到了但沒有顯示訂閱數,和頁面根本沒讀到。前者是頁面本身沒給,後者是抓取失敗,兩者都記成未知,但原因碼分開記,後面統計失敗率時才不會混在一起。
在頻道頁的 JSON 裡找這幾個字串,任一個出現就算有:
id.sponsor_button
SPONSORSHIP_STAR
FLOW_TYPE_SPONSORSHIPS_PURCHASE
membershipButtonRenderer
sponsorButtonRenderer
可靠性:看到按鈕時是很強的證據,因為頻道會員只開放給 YPP 頻道。看不到時幾乎沒有意義,原因有三個:
第三點我們一開始沒處理,兒童頻道會被當成「沒有會員」而扣分。後來改成:如果頻道被判定為兒童頻道,S2 沒找到按鈕就記成未知,不記成未通過。
找 merchandiseShelfRenderer,或頻道分頁裡有沒有 Store 分頁。把商店連結到頻道需要 YPP,所以有的時候證據力和 S2 同級。沒有的時候一樣說明不了什麼,因為大部分頻道本來就沒開店,兒童頻道也一樣被關掉。
這是五個訊號裡工程量最大、坑也最多的一個。
取樣方式:從 Videos 頁拿最近的上傳,只留長度超過 4 分鐘的影片,取前 3 支,平行去抓觀看頁。片中廣告的資格線是 8 分鐘,這裡的 4 分鐘只是用來篩掉短片,讓樣本比較像「長影片」。還沒開播的首播沒有長度,自然會被濾掉。
解析時看 ytInitialPlayerResponse 底下三個欄位:
adPlacements、playerAds:實際下發的廣告位,S3 只認這兩個。adSlots:只代表「有投放機會」,不保證真的會播廣告。只有它、沒有前兩者的情況,我們另外標記成弱訊號,不算進 S3。順便可以從 adPlacements 裡讀出廣告種類(AD_PLACEMENT_KIND_START 是片頭、MILLISECONDS 是片中、END 是片尾)和片中廣告的時間點 adTimeOffsetStartMilliseconds。
簡化後的解析邏輯大概是這樣:
const player = extractAssignedJson(html, "ytInitialPlayerResponse");
const placements = (player.adPlacements ?? []) as unknown[];
const playerAds = (player.playerAds ?? []) as unknown[];
const slots = (player.adSlots ?? []) as unknown[];
const hasAds = placements.length > 0 || playerAds.length > 0;
const slotsOnly = !hasAds && slots.length > 0; // 弱訊號,不進 S3
const status = player.playabilityStatus?.status; // 只有 "OK" 才算有效樣本
可靠性:廣告不等於分潤。依 YouTube 的服務條款,平台可以在任何內容上投放廣告,美國從 2020 年 11 月、其他地區從 2021 年 6 月開始,沒加入 YPP 的頻道影片上也可能出現廣告,錢由 YouTube 收走。YouTube 說明中心的常見問題也直接寫了這一點。
不過實際抓下來,情況比「有廣告也不代表什麼」好一些。我們在調整規則前做過一輪實測:沒加入 YPP 的頻道,觀看頁幾乎不會出現 adPlacements,19 個樣本是 0 個;已加入的頻道,單次抓取大約有 6% 會漏掉廣告。也就是說,命中一次很有分量,沒命中一次反而說明不了什麼。這個不對稱直接決定了下面的判定方式。
超級感謝、超級留言這類功能也是 YPP 的一部分,但目前 MonetizationKit 的檢測器沒有把它們列入訊號,只用上面五個。
這一節是整個系統裡最重要的設計原則,也是最多 bug 的來源。
從資料中心 IP 抓觀看頁,很常拿到 playabilityStatus.status 是 LOGIN_REQUIRED、原因文字是「Sign in to confirm you're not a bot」的頁面。這種頁面沒有任何廣告欄位,如果直接拿 adPlacements 是否為空來判斷,就會得到一個假的「無廣告」。
麻煩的是 LOGIN_REQUIRED 同時也用在年齡限制和私人影片。所以我們只有在原因文字明確符合 /not a bot/i 時才歸類成「被擋」,其他歸類成「內容受限」。HTTP 429 也算被擋。完整的失敗分類是:
| 分類 | 意思 |
|---|---|
| blocked | 被機器人驗證或限流擋下,換時間、換出口 IP 可能就好 |
| restricted | 年齡限制、私人影片等內容本身的限制 |
| unavailable | 影片被刪或無法播放 |
| timeout / aborted | 單一請求逾時,或整次檢測到了總時限 |
| unreadable | 頁面結構認不出來 |
| error | 網路錯誤、5xx 等 |
規則是:只有 playabilityStatus 為 OK 的頁面才算有效樣本,失敗的樣本一律不進分母。3 支影片全部被擋時,結果會另外標示「這次 YouTube 不讓我們的伺服器讀影片頁」,讓使用者知道是我們這邊的問題,晚點再查可能會補上。
YouTube 改版後頁面一樣回 200,只是結構變了。這時找不到會員按鈕,只代表我們的解析器讀不懂,不代表頻道沒有。所以頻道頁必須先找到結構錨點(channelMetadataRenderer、pageHeaderViewModel,我們在幾個大小、類型不同的頻道上核對過,每個頻道頁都有),找不到錨點時 S2、S4 都記成未知並發出警報。
訂閱按鈕在概念上更接近會員按鈕,本來是更好的錨點,但未登入的回應裡沒有它,只能放棄。
要知道頻道是不是「為兒童打造」,我們用的是觀看頁播放器設定裡的迷你播放器模式:playabilityStatus.miniplayer.miniplayerRenderer.playbackMode 為 PLAYBACK_MODE_PAUSED_ONLY 時,代表這支影片的迷你播放器被關掉了。實測幾個兒童頻道都是 PAUSED_ONLY,一般頻道是 ALLOW。這個值不會隨介面語言改變,比抓顯示文字穩定。只有取樣到的影片全部是兒童影片,才把整個頻道當成兒童頻道。
這是做成資料集之後才發現的。2026 年 9 月 5 日初期抓約 900 個頻道時,爬蟲被限流,剛好在被限流的那幾個小時抓的國家,「無法判定」的比例就特別高。美國一度是 37.1%,放慢速度重抓之後,同一批樣本降到 14.3%。現在失敗率會長期記錄,限流的時段會在圖上變成一個尖峰,比較容易看出來。
每個訊號是三值的:pass、fail、unknown。組合時的核心原則是 unknown 不等於 fail。
因為上面那個不對稱(未入計畫幾乎不出廣告位、已入計畫偶爾漏抓),S3 的規則是看命中數:
function adSampleOutcome(withAds: number, sampled: number) {
if (sampled === 0) return "unknown";
if (withAds >= 2 || (withAds >= 1 && withAds === sampled)) return "pass";
if (withAds === 0 && sampled >= 3) return "fail"; // 唯一的陰性
return "unknown"; // 例如 1/3、0/2、1/2
}
舊版規則是「三分之二以上有廣告才算通過,否則不通過」,結果 1/3、0/1 都被當成陰性,進一步推出「可能未營利」。改成現在這樣以後,只有至少 3 支都成功抓到、而且全都沒廣告,才算陰性。
「10 萬訂閱」這一條是後來加的。原本只有會員按鈕能把頻道推到第一級,結果一些家喻戶曉、取樣影片每支都有廣告的大頻道,因為沒開會員就卡在第二級。YouTube 確實能在非合作夥伴頻道投廣告。我們仍把「10 萬訂閱且 S3 通過」列為升到第一級的另一項條件,這是我們選的判定門檻,不能據此確認頻道已加入 YPP。小頻道只有廣告時仍然停在第二級,因為對它們來說,廣告證明不了分潤。
另外注意第 3 條的寫法:只有「讀到了、而且是陰性」才算數。訂閱數讀不到、或 S3 被機器人驗證擋掉,都不算陰性。最後仍要看其他訊號:例如 S3 被擋但有會員按鈕時,會判為「或許已營利」;訂閱不到 500 且沒有正面訊號時,仍可能判為「可能未營利」。
我們把這套檢測跑在 2026 年 9 月的各國熱門頻道上,總共 3,192 個頻道,最後發布了檢測數至少 30 個的 50 個國家,共 3,080 個頻道。做各國排名時,踩到一個很典型的坑。
9 月的規則裡,「可能已營利」一定要有會員按鈕。而頻道會員只在部分地區開放。結果排名最後 8 名,剛好就是 50 國裡不在會員開放清單上的那 8 個國家:俄羅斯、斯里蘭卡、伊拉克、亞塞拜然、巴基斯坦、孟加拉、坦尚尼亞、哈薩克。
把國家分成兩組來看,有會員的 42 國,合併後「可能已營利」比例是 29.6%;沒會員的 8 國是 2.9%。光是「這個國家有沒有開會員」這一個是非題,就解釋了 50 國之間這個比例差異的 57%。這說明會員功能的地區限制會影響判定,但不能據此斷言那些國家的創作者比較少營利。這 8 國檢查的 480 個頻道裡,仍有 14 個被判為第一級。按舊規則,這代表它們都檢出了會員按鈕;為什麼這些頻道能提供會員功能,我們還沒查清楚。
規則改成會員、商店、10 萬訂閱三選一之後,10 月的表格把既有的檢測全部按新規則重算,同時也加入了 9 月之後使用者新查的頻道,所以不是每一列都是原樣本。伊拉克的「可能已營利」從 60 個頻道中的 2 個變成 40 個,坦尚尼亞從 60 個中的 0 個變成 35 個。在 10 月完整的 51 國表格裡,這 8 國排名介於第 6 到第 48 名,不再擠在最後。
從這件事得到的教訓是:一個訊號缺席,只有在它「本來可能出現」的地方才算證據。這次我們增加了商店和 10 萬訂閱兩條升到「可能已營利」的路,減少對會員按鈕的依賴,但目前的規則還沒有按各國的功能開放情況處理訊號缺席。會員、商店、甚至 YPP 本身都不是每個國家都有,跨國比較時仍要把這個限制算進去。
把這些寫出來,是因為做這類工具最容易犯的錯,就是把推論包裝成事實。
每個國家的數字、取樣範圍、9 月和 10 月兩套規則的差異,以及可以自己重跑的程式和套件版本,都整理在資料集背後的方法說明裡,資料以 CC BY 4.0 授權公開。
我在做 MonetizationKit,這篇寫的就是我們檢測器實際在用的方法。如果你也在做類似的爬蟲,或發現哪個欄位又被 YouTube 改掉了,歡迎留言交流。
—— Super Lede