前兩天發現 HTML5 FUN Audio Player 怎麼樣都抓不到 YouTube 字幕;查了一下,看來多變的 YouTube 又出招了,讓原本由影片頁面原始碼中擷取到的字幕網址失效了,只能抓到空白的內容。研究了大半天,找到了另一個方法 (有興趣可以參考 jdepoix 的 youtube-transcript-api ),主要就是先由影片的頁面原始碼中找到「INNERTUBE_API_KEY」,然後再利用 YouTube 的 API ,以 POST 的方式取得影片資訊,如果成功,就能拿到影片字幕軌資料的網址。
以 JavaScript 使用新方法來擷取影片資訊的第一關會是在 POST 時的 CORS 問題,如果透過 CORS proxy 當然是可行的方法,一般來說,利用 Google Apps Script 也能寫出 CORS proxy,不過 YouTube 的 API 有在擋 bot,Google Apps Script 直接就被封殺了,網路上其它 CORS proxy 服務也沒好到哪裡去,多玩個幾次,就只能得到這樣的訊息了:
status: 'LOGIN_REQUIRED',
reason: 'Sign in to confirm you’re not a bot'
想要的資料當然就撈不到了。
後來發現,原來在瀏覽器中用 fetch ,不透過 CORS proxy 來 POST ,就能使用 YouTube API 了,倒是有個小地方要注意,不然就會一直出現:
Access to fetch at 'https://.......' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
裡面最重要的關鍵字是「preflight」,當 Content-Type 是 application/json 時,瀏覽器在未傳送資料給遠端前,會先自我審查是否違反 CORS,難怪我一直看不到 YouTube API 的回應。要怎麼改呢?
只要在 fetch 參數中的 headers 指定 Content-Type 為 text/plain ,它就會使用 simple header ,然後可以避免 preflight,加入:
headers: {
'Content-Type': 'text/plain',
}
不透過 CORS proxy 來呼叫 YouTube API 的好處是,比較不會動不動就被 YouTube 判定為 bot 而抓不到資料,是不是很棒?!
不過,當我很開心地將改良過的程式應用到 Blogger 中的 Audio Player 時,居然還是出現違反 CORS 的訊息,為什麼?
底下兩張截圖來探討原因,關鍵已經用箭頭標示出來了:
![]() |
[圖1] origin 為 blog 的網域 |
![]() |
[圖2] origin 為 null |
如 [圖1] 所示,當程式在 Blogger 中執行時,origin 被設定為 Blogger 所在的網域,瀏覽器送給 YouTube API ,因為兩者在不同的網域,這樣一來,就會因為 CORS 的問題,無法抓到資料了。同樣的程式碼,由本機載入並執行時,會像 [圖2] 一樣,origin 被設定為 null,YouTube API 接受了,會回傳資料 (這不知道是故意的,還是 API 的 bug,哈!但是我喜歡它現在的狀況)。
本來在研究 YouTube embed 時的 iframe 結構,看看能不能從中挖出字幕,一份資料中提到如果使用 base64 編碼後的 data URI 當 iframe 的 src ,會導致 origin 被設定為 null,就會讓它變成與頁面不同源,而無法存取頁面中的資料 (這一點其實是可以利用 postMessage 來交換資料) ......。
綜合前面看起來像問題的點,因為一個關鍵字「null」,讓我兜出了一個可行的方案。
如果產生一個只用來透過 YouTube API 抓影片資訊的 iframe,而且程式碼用 base64 編碼以後,設定為 iframe 的 src,利用 postMessage 傳影片的 id 與 api key 給 iframe, iframe 收到後,執行抓資料的程序,再使用 postMessage 回傳影片資料給 Audio Player。
哈!就這樣子,Audio Player 又能順利抓到 YouTube 的字幕了!
▋ 相關連結
沒有留言:
張貼留言