2025年11月10日 星期一

Python 學習筆記 :用 pytubefix 下載 Youtube 音訊

以前若要從 Youtube 擷取音訊為 mp3 檔最快的方法是將 YT 影片網址中的 ube 去掉就可透過  https://yout.com 提供的線上服務直接下載 mp3 檔, 但是該服務目前對於未註冊用戶限制每天只能下載一個 mp3, 超不方便, 參考 :


最近在讀市圖借的 "ChatGPT 網路行銷" 這本書時得知 pytube 這套件, 可用來下載 YouTube 影片, 音訊, 或者取得影片資訊, 並且可選擇不同的解析度或格式. 不過初步測試發現用 pytube 下載音訊時會出現錯誤. 

詢問 ChatGPT 原因是 YouTube 的網頁結構經常變動 (特別是影片串流資料的 player_response JSON), 導致 pytube 無法解析而失敗. 解決方案是改用 fork 版的 pytubefix 套件, 因為此套件有在持續修正 YouTube 更新所造成的串流解析錯誤. 

剛好岳母練歌要我幫她抓幾首歌, 就拿來當練習吧! 

https://www.youtube.com/watch?v=AgAWXdG_ck0 (蔡淳佳-陪我看日出)
https://www.youtube.com/watch?v=CeRIWoVkgyk (夏川里美-淚光閃閃)
https://www.youtube.com/watch?v=yYwn8k502qY (許美靜-城裡的月光)


首先用 pip 安裝 pytubefix 套件 :

pip install pytubefix   

D:\python\test>pip install pytubefix   
D:\python\test>pip install pytubefix
Collecting pytubefix
  Downloading pytubefix-10.2.1-py3-none-any.whl.metadata (9.2 kB)
Collecting aiohttp>=3.12.13 (from pytubefix)
  Downloading aiohttp-3.13.2-cp310-cp310-win_amd64.whl.metadata (8.4 kB)
Collecting nodejs-wheel-binaries>=22.20.0 (from pytubefix)
  Downloading nodejs_wheel_binaries-22.20.0-py2.py3-none-win_amd64.whl.metadata (4.6 kB)
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp>=3.12.13->pytubefix)
  Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)
Collecting aiosignal>=1.4.0 (from aiohttp>=3.12.13->pytubefix)
  Downloading aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
Requirement already satisfied: async-timeout<6.0,>=4.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from aiohttp>=3.12.13->pytubefix) (4.0.3)
Requirement already satisfied: attrs>=17.3.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from aiohttp>=3.12.13->pytubefix) (23.1.0)
Requirement already satisfied: frozenlist>=1.1.1 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from aiohttp>=3.12.13->pytubefix) (1.4.1)
Requirement already satisfied: multidict<7.0,>=4.5 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from aiohttp>=3.12.13->pytubefix) (6.0.4)
Collecting propcache>=0.2.0 (from aiohttp>=3.12.13->pytubefix)
  Downloading propcache-0.4.1-cp310-cp310-win_amd64.whl.metadata (14 kB)
Collecting yarl<2.0,>=1.17.0 (from aiohttp>=3.12.13->pytubefix)
  Downloading yarl-1.22.0-cp310-cp310-win_amd64.whl.metadata (77 kB)
Requirement already satisfied: idna>=2.0 in c:\users\tony1\appdata\roaming\python\python310\site-packages (from yarl<2.0,>=1.17.0->aiohttp>=3.12.13->pytubefix) (3.4)
Requirement already satisfied: typing-extensions>=4.2 in c:\users\tony1\appdata\local\programs\thonny\lib\site-packages (from aiosignal>=1.4.0->aiohttp>=3.12.13->pytubefix) (4.12.2)
Downloading pytubefix-10.2.1-py3-none-any.whl (1.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.5/1.5 MB 3.6 MB/s eta 0:00:00
Downloading aiohttp-3.13.2-cp310-cp310-win_amd64.whl (455 kB)
Downloading yarl-1.22.0-cp310-cp310-win_amd64.whl (86 kB)
Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)
Downloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
Downloading nodejs_wheel_binaries-22.20.0-py2.py3-none-win_amd64.whl (40.1 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 40.1/40.1 MB 9.9 MB/s eta 0:00:00
Downloading propcache-0.4.1-cp310-cp310-win_amd64.whl (41 kB)
Installing collected packages: propcache, nodejs-wheel-binaries, aiosignal, aiohappyeyeballs, yarl, aiohttp, pytubefix
  Attempting uninstall: aiosignal
    Found existing installation: aiosignal 1.3.1
    Uninstalling aiosignal-1.3.1:
      Successfully uninstalled aiosignal-1.3.1
  Attempting uninstall: yarl
    Found existing installation: yarl 1.9.4
    Uninstalling yarl-1.9.4:
      Successfully uninstalled yarl-1.9.4
  Attempting uninstall: aiohttp
    Found existing installation: aiohttp 3.9.1
    Uninstalling aiohttp-3.9.1:
      Successfully uninstalled aiohttp-3.9.1
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
line-bot-sdk 3.7.0 requires aiohttp==3.9.1, but you have aiohttp 3.13.2 which is incompatible.
line-bot-sdk 3.7.0 requires urllib3<3,>=2.0.5, but you have urllib3 1.26.19 which is incompatible.
finmind 1.6.9 requires ta~=0.5.25, but you have ta 0.11.0 which is incompatible.
Successfully installed aiohappyeyeballs-2.6.1 aiohttp-3.13.2 aiosignal-1.4.0 nodejs-wheel-binaries-22.20.0 propcache-0.4.1 pytubefix-10.2.1 yarl-1.22.0

請注意, pytubefix 依賴的 aiohttp 3.13.2 與 line-bot-sdk 的 aiohttp 3.9.1 版衝突, 後續要測試看看 LINE Bot 會不會受影響. 

匯入 pytude.YouTube 類別, 呼叫其建構式並傳入 Youtube 影片網址 (例如夏川里美的淚光閃閃) 建立 YouTube 物件 :

>>> from pytube import YouTube   
>>> url='ttps://www.youtube.com/watch?v=CeRIWoVkgyk'  
>>> yt=YouTube(url)  
>>> type(yt)   
<class 'pytube.__main__.YouTube'>

然後用下列指令從影片可用的所有串流中過濾出音訊串流 (挑出第一個純音訊串流), 這會傳回一個 Stream 物件 : 

>>> audio_stream=yt.streams.filter(only_audio=True).first()   
>>> type(audio_stream)   
<class 'pytubefix.streams.Stream'>

呼叫 Stream 物件的 downlowd() 方法即可下載音訊 (mp4 影片的音訊為 m4a 檔案格式) :

>>> audio_stream.download()  
'D:\\python\\test\\夏川里美  淚そうそう ( 淚光閃閃).m4a'

用 Windows 的 Media Player 即可播放 m4a 檔案, 但舊款車載音響或播放器可能不支援 m4a, 可以用 ffmpeg 軟體轉成 mp3, 參考 : 


將 .m4a 檔案轉成 .mp3 檔的轉換指令如下 : 

ffmpeg -i input.m4a output.mp3    

上面所下載的 m4a 檔名含有空格, ffmpeg 會無法處理, 所以先將其改為 "夏川里美-淚光閃閃.m4a" 再轉換, 指令如下 :

ffmpeg -i 夏川里美-淚光閃閃.m4a 夏川里美-淚光閃閃.mp3

D:\python\test>ffmpeg -i 夏川里美-淚光閃閃.m4a 夏川里美-淚光閃閃.mp3
ffmpeg version 2025-11-02-git-f5eb11a71d-essentials_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developers
  built with gcc 15.2.0 (Rev8, Built by MSYS2 project)
  configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-cairo --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-dxva2 --enable-d3d11va --enable-d3d12va --enable-ffnvcodec --enable-libvpl --enable-nvdec --enable-nvenc --enable-vaapi --enable-openal --enable-libgme --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libtheora --enable-libvo-amrwbenc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-librubberband
  libavutil      60. 16.100 / 60. 16.100
  libavcodec     62. 19.100 / 62. 19.100
  libavformat    62.  6.101 / 62.  6.101
  libavdevice    62.  2.100 / 62.  2.100
  libavfilter    11.  9.100 / 11.  9.100
  libswscale      9.  3.100 /  9.  3.100
  libswresample   6.  2.100 /  6.  2.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '夏川里美-淚光閃閃.m4a':
  Metadata:
    major_brand     : dash
    minor_version   : 0
    compatible_brands: iso6mp41
    creation_time   : 2024-04-30T04:26:11.000000Z
    encoder         : Google
  Duration: 00:04:18.72, start: 0.000000, bitrate: 48 kb/s
  Stream #0:0[0x1](eng): Audio: aac (HE-AAC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 48 kb/s (default)
    Metadata:
      creation_time   : 2024-04-30T04:26:11.000000Z
      handler_name    : ISO Media file produced by Google Inc.
      vendor_id       : [0][0][0][0]
Stream mapping:
  Stream #0:0 -> #0:0 (aac (native) -> mp3 (libmp3lame))
Press [q] to stop, [?] for help
Output #0, mp3, to '夏川里美-淚光閃閃.mp3':
  Metadata:
    major_brand     : dash
    minor_version   : 0
    compatible_brands: iso6mp41
    TSSE            : Lavf62.6.101
  Stream #0:0(eng): Audio: mp3, 44100 Hz, stereo, fltp (default)
    Metadata:
      encoder         : Lavc62.19.100 libmp3lame
      creation_time   : 2024-04-30T04:26:11.000000Z
      handler_name    : ISO Media file produced by Google Inc.
      vendor_id       : [0][0][0][0]
[out#0/mp3 @ 0000019b3b488f40] video:0KiB audio:4043KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.008334%
size=    4043KiB time=00:04:18.71 bitrate= 128.0kbits/s speed=66.7x elapsed=0:00:03.88

不過 mp3 檔案大小通常會比 m4a 大一些 :

D:\python\test>dir 夏川里美-淚光閃閃.*  
 磁碟區 D 中的磁碟是 新增磁碟區
 磁碟區序號:  1258-16B8

 D:\python\test 的目錄

2025/11/10  下午 08:38         1,578,364 夏川里美-淚光閃閃.m4a
2025/11/10  下午 09:13         4,140,230 夏川里美-淚光閃閃.mp3
               2 個檔案       5,718,594 位元組

注意, 雖然將 .m4a 副檔名改成 .mp3 在 PC 上仍可播放, 但其編碼格式仍是 m4a, 拿到只能播放 mp3 的舊款播放器上還是不行的. 

完整的程式碼如下 :

# get_yt_audio.py
import sys
from pytubefix import YouTube

def download_audio(url, path='.'):
    """從 YouTube 下載音訊 (m4a 檔案)"""
    try:
        yt=YouTube(url)
        print(f'下載音訊:{yt.title}')
        # 取得音訊串流
        audio_stream=yt.streams.filter(only_audio=True).first()
        if audio_stream is None:
            print('找不到可用的音訊串流!')
            return
        file_path=audio_stream.download(output_path=path)
        print(f'音訊已下載為 m4a 檔:{file_path}\n')
    except Exception as e:
        print(f'下載失敗:{e}')

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('使用方法:python get_yt_audio.py <video_id> [download_path]')
        sys.exit(1)
    video_id=sys.argv[1]
    path=sys.argv[2] if len(sys.argv) >= 3 else '.'
    video_url=f'https://www.youtube.com/watch?v={video_id}'
    download_audio(video_url, path)

此程式從命令列輸入兩個參數, 第一個是必要的 YT 影片 ID, 就是網址 "https://www.youtube.com/watch?v=" 後面的影片編號, 第二個參數是可有可無的儲存音檔之路徑, 預設是目前工作路徑, 執行結果如下 :

D:\python\test>python get_yt_audio.py DkiVWdJampQ   
下載音訊:高勝美 ~ 晚風
音訊已下載為 m4a 檔:D:\python\test\高勝美 ~ 晚風.m4a

D:\python\test>python get_yt_audio.py AgAWXdG_ck0  
下載音訊:陪 我 看 日 出   (蔡淳佳)《原曲 : 淚光閃閃 / 原唱 : 夏川里美》(4K 5.1聲道)
音訊已下載為 m4a 檔:D:\python\test\陪 我 看 日 出   (蔡淳佳)《原曲  淚光閃閃  原唱  夏川里美》(4K 5.1聲道).m4a

D:\python\test>python get_yt_audio.py p4YkHQrPt2I    
下載音訊:黃鶯鶯 Tracy Huang - 哭砂 Cry Sand (official官方完整版MV)
音訊已下載為 m4a 檔:D:\python\test\黃鶯鶯 Tracy Huang - 哭砂 Cry Sand (official官方完整版MV).m4a

... (略) ...

用 ffmpeg 轉成 mp3 檔後, 再用 Mp3Gain 將音量一律改成 94, 最後用 Mp3tag 修改 Title 與 Author 欄位即大功告成啦! 參考 : 


沒有留言 :