3日前、「yt-dlp」というYouTubeをダウンロードするコマンドの存在を知り、以下のコマンドでYouTube動画をコマンドラインでダウンロードすることができるようになった。
$ yt-dlp "URL"
これでも十分な進歩なのだが、私の目標は、落合道夫先生が「tkokinken ochiai」というチャンネル名でYouTubeに上げている168本の動画を全てダウンロードすること。これくらいの量であれば、ポチポチ地道にクリックしていればすぐに終わるが、できればカッコよく一発で「YouTube の、あるチャンネルの動画一覧を取得する」ということをやりたい。

そうしてググって探していたら、うってつけのPythonプログラムを見つけた。これを実行したくて、Pythonの概要の調査、自分のMacのPython環境設定、Pythonプログラムの実行までを実施したので自分用のメモとしてこの記事を残す。
■Pythonの概要
まず、Pythonについてちょっと調べてみた。Javaと同じようにPythonにも世代があり、とりあえず3世代存在する模様。そして現在主流なのはPython3で、過去のPython、Python2は、あまり使う機会がなさそうな雰囲気。
JavaやRubyと同様、過去バージョンのプログラム実行環境を管理するツールはPythonにも存在するようで、Javaのjenv、Rubyのrbenvと同様にPythonにはpyenvというものがあるようだ。
ただし、現在ではpyenvは推奨されておらず、標準で提供されているvenvという仮想環境ツールを使うのが主流のやり方。
このあたり、かなり情報が錯綜しているが、ググって出てくる情報の上から10個くらいを流し読みして、一番分かりやすいと思った下記の記事を参考にした。
「【仮想環境】ライブラリを綺麗に管理する」
https://zenn.dev/gomecha/books/4fa32ac5f76af31b2f81/viewer/7866cb
■Python3とpip3のインストール
まず、私のMacにPython3とpip3がインストールされているかを確認。HomeBrewのリスト表示で確認すると、Python3がインストールされていることが確認できた。なお、pipはPythonの一部という扱いのようなので個別でのインストールは不要。Upgradeしてみる。
$ brew upgrade python3 Warning: python3 3.13.2 already installed
既に最新バージョンだった。Python3とpip3の存在をwhichコマンドで確認する。
$ which python3 /opt/homebrew/bin/python3 $ which pip3 /opt/homebrew/bin/pip3
続いて、「pip3 freeze」コマンドでインストールされているPythonライブラリを確認する。
$ pip3 freeze certifi==2025.1.31 wheel @ file:///opt/homebrew/Cellar/python%403.13/3.13.2/libexec/wheel-0.45.1-py3-none-any.whl#sha256=b9235939e2096903717cb6bfc132267f8a7e46deb2ec3ef9c5e234ea301795d0
2行目のwheelってのはよく意味がわからないが、1行目の表示から「certifi」というライブラリのバージョン「2025.1.31」がインストールされていることがわかる。Pythonを使い込んでいる場合は、ここにもっとたくさんのライブラリが表示されるのだろうが、私は今まで一度もあるいはほとんどPythonを使ってこなかったのでほぼまっさらな状態だ。だがこれでよい。プロジェクトごとにvenvで仮想環境を作成し、そこに必要なライブラリをインストールしていく方針だからだ。
■プロジェクトディレクトリを作成し、ディレクトリの中にvenvの仮想環境を作成
プロジェクト名はシンプルにPythonワークという意味で「python_wk1」とした。
$ cd ~ $ mkdir python_wk1 $ cd python_wk1
「python3 -m venv <仮想環境名> 」コマンドで仮想環境を作成する。仮想環境名はvenvが慣例的によく使われるのでそうする。
$ python3 -m venv venv
仮想環境を有効にするために、ディレクトリの中にあるbin/activateを実行する。
$ source venv/bin/activate (venv) $
先頭に(venv)と表示されるのが、test_venv環境が有効になっていることを表す。
仮想環境が有効になった状態で、ライブラリをインストールするとその環境内にインストールされる。ためしに、「numpy」というライブラリをインストールしてみる。
$ pip3 install numpy Collecting numpy Downloading numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl.metadata (62 kB) Downloading numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl (5.1 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.1/5.1 MB 39.8 MB/s eta 0:00:00 Installing collected packages: numpy Successfully installed numpy-2.2.3 [notice] A new release of pip is available: 25.0 -> 25.0.1 [notice] To update, run: pip install --upgrade pip
「pip3 freeze」コマンドでインストールされたパッケージの確認をする。
(venv) $ pip3 freeze numpy==2.2.3
numpyライブラリのインストールが確認できた。仮想環境を無効化するときのコマンドはdeactivateを実行する。
(venv) $ deactivate $
(venv)の表示が消えたことで、仮想環境から抜け出したことがわかる。とりあえずpenvの仮想環境は使えるようになった。
■「YouTube の、あるチャンネルの動画一覧を取得する」に挑戦
「YouTube の、あるチャンネルの動画一覧を取得する」ということをやりたくて、ググって見つけたWebページはこちら。
「YouTube Data API による動画一覧取得|JQinglong」
https://zenn.dev/jqinglong/articles/1161615fdaa6f6
上記ページにあったプログラム「response.py」を実行してみた。
$ Python3 response.py Traceback (most recent call last): File "/Users/taka/python_wk1/response.py", line 1, in <module> from apiclient.discovery import build ModuleNotFoundError: No module named 'apiclient'
「apiclient」というモジュールがないというエラーとなった。ググって調べたら、「google-api-python-client」というライブラリをインストールしたらいいらしい。
$ pip install google-api-python-client
コンソールログがガーっと流れてインストール成功。確認する。
(venv) $ pip3 freeze cachetools==5.5.2 certifi==2025.1.31 charset-normalizer==3.4.1 google-api-core==2.24.1 google-api-python-client==2.162.0 google-auth==2.38.0 google-auth-httplib2==0.2.0 googleapis-common-protos==1.69.0 httplib2==0.22.0 idna==3.10 numpy==2.2.3 proto-plus==1.26.0 protobuf==5.29.3 pyasn1==0.6.1 pyasn1_modules==0.4.1 pyparsing==3.2.1 requests==2.32.3 rsa==4.9 uritemplate==4.1.1 urllib3==2.3.0
関連付けられたライブラリが一気にたくさんインストールされた。これを見ただけでも、プロジェクトごとにvenv仮想環境を作成して必要なライブラリをインストールする方法が優れた方法であると想像できる。
上記のGoogle APIライブラリをインストールした状態で、再度「response.py」を実行してみたら、「有効なAPIキーを指定してね」というエラーが返ってきた。
サンプルプログラムはAPIキーの部分が”xxx”という仮文字列になっているためだ。
■「YouTube Data API v3」のAPIキーを取得
なのでまず、「APIキーを取得」する。
APIキーの取得はこの記事のやり方に従った。特に迷ったところはなし。自分のGoogleメインアカウントで「Google Cloud」の利用登録をし、「YouTube Data API v3」のAPIキーを発行した。APIキーというのがなんのためにあるのかはちゃんと調べていないけど、無闇矢鱈に無料でAPIを使い倒されるとGoogle側としてはサーバー負荷がかかってしまうので、API呼び出しごとにアカウントと紐づいたAPIキーを受け渡す仕組みにして、1日あたりのAPIの呼び出し回数に制限をかけたり、有料オプションを用意したりするためにあるものと理解している。
「【Youtube】APIキーの取得手順(2021/04/08時点のキャプチャ)|@shinkai_(新海 正明)」https://qiita.com/shinkai_/items/10a400c25de270cb02e4
■APIキーを指定して再実行
APIキーを指定して再実行してみた。
$ Python3 response.py
成功。出力ファイル(response.json)に、チャンネル内の50個の動画の情報(動画のID、動画のタイトル、サムネイルのURL、etc…)を取得できた。
このサンプルプログラムは、サンプル用のYouTubeチャンネルの「チャンネルID」を指定していたので、これを落合道夫先生のYouTubeチャンネルの「チャンネルID」に書き換えなければならない。
■チャンネルIDを調べる
調べたら、「チャンネルID」はチャンネルのURLの文字列ではないらしく、通常利用者は意識しない乱数のようなものだった。以下のWebサイトが、チャンネルのURLを指定すると「チャンネルID」を取得してくるフォームを公開していたので、使わせてもらった。
「他人のYouTubeのチャンネルIDを調べる」
https://ilr.jp/tech/485/

落合道夫先生のYouTubeチャンネルのIDは「UC6d0ISWLewaGX08fLZy8gKQ」だった。
■チャンネルIDを指定して再実行
参考にしたのはこのページ(再掲)
「YouTube Data API による動画一覧取得|JQinglong」
https://zenn.dev/jqinglong/articles/1161615fdaa6f6
「response.py」の所望のチャンネルIDを指定して再実行してみた。
このプログラムはチャンネルIDと件数(=MAX50)を指定してYouTube APIを呼び出し、出力ファイル(response.json)に、チャンネル内の50個の動画の情報(動画のID、動画のタイトル、サムネイルのURL、etc…)を取得する。結果は成功。
「response.py」のコードはこれです。(チャンネルIDのところ以外は上述の「YouTube Data API による動画一覧取得|JQinglong」で公開しているものまんまです。APIキーは自分で取得したものを入れる。)
from apiclient.discovery import build import json # API情報 API_KEY = 'xxx' YOUTUBE_API_SERVICE_NAME = 'youtube' YOUTUBE_API_VERSION = 'v3' youtube = build( YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=API_KEY ) search_response = youtube.search().list( channelId='UC6d0ISWLewaGX08fLZy8gKQ', part='snippet', maxResults=50, ).execute() with open("response.json", mode="w", encoding="utf-8") as f: json.dump(search_response, f, ensure_ascii=False, indent=2)
ただしこのプログラムには1回の検索で50件までしか取得できないという問題があって、それを解決するのが次の「videolist.py」というプログラム。作者さんの説明がわかりやすいのでそのまま引用する。
>ところが、みなさん、苦労されているわけです。
>理由の一つは、maxResults の最大値が50であること。すなわち、一回の検索で、50件しか取得できません。これを超える動画を保有しているチャンネルの場合は、ループしながら取得する必要があります。そのための仕組みとして、pageToken というものが提供されており、maxResults を超える結果がある場合は、nextPageToken が返却されるので、次の検索をする際に、pageToken として nextPageToken を渡してあげれば、次のデータを取得できます。
>この処理を書かなければいけないということはありますが、それでも、そのような仕様が提示されているので、そこもまだ良いです。
>問題は、この仕組みで検索していっても、取得件数が500件くらいになると、nextPageToken が返って来なくなります。これがつらい。
>(そこで、)500件未満になる程度の期間条件をつけて検索すれば、その期間分は取得できる。また、期間を変えて検索すれば、また500件取得できる、という形です。
>ということで、こんな感じで書いてみました。こちらの例は、取得結果から、一部項目を取り出して、新しいjsonファイルを出力する、というものです。
「videolist.py」を実行したら「dateutil」ライブラリがないというエラーが出たのでpipでインストール。
$ pip3 install python-dateutil
そして改めて試したら実行成功。出力ファイル(videolist.json)を見ると、チャンネル内の179個の動画の情報(動画のタイトル、動画のID、動画のURL、サムネイルのURL、etc…)が取得できていた。
私が情報を取得したい落合道夫先生のYouTubeチャンネルの動画のアップロード日付は2010年2月〜2016年8月で、動画の本数は168本(チャンネルのTopページに表示がある)なので、500本未満なのでこのプログラムを使えば一度に情報取得ができそうだ。
チャンネルIDを書き換える
編集前:channelId='UCbDEamSXQhxqhovhd2NdEyg',
編集後:channelId='UC6d0ISWLewaGX08fLZy8gKQ',
期間条件の始期を書き換える
編集前:dt = datetime.datetime(2016, 1, 1, 0, 0)
編集後:dt = datetime.datetime(2010, 1, 1, 0, 0)
期間条件のループ数を書き換える(2ヶ月分→84ヶ月分)
編集前:for i in range(1, 2):
編集後:for i in range(1, 84):
プログラムの動作を見るためにコメントアウトされていたprint文(2箇所)を有効化print(search_result["snippet"]["title"])
print(nextPagetoken)
そしてもちろんAPIキーも有効値に置き換えて、再実行。成功。168個の動画情報が取得できた。
$ python3 [videolist.py](http://videolist.py/) 2010-01-01T00:00:00 3 2010-02-01T00:00:00 21 [支那事変とは何か]日本と汪兆銘政権の支那統治 日米戦争とは、 真珠湾事件前まで 支那事変とは何か 開戦前 大東亜戦争とは何だったのか 2010-03-01T00:00:00 18 イデオロギー概観 日米戦争とは何だったのか 真珠湾から敗戦まで :(以下略)
コンソールログを解説する。1〜2行目は、2010年1月に動画数が3個あることを表すが、それらの動画情報を取得できず、3行目の処理に進んでいる。3〜4行目は、2010年2月に動画数が21個あることを表すが、実際に取得できた動画タイトル数は4個(5〜8行目)、以下同文。
これは推察だが、あるはずの動画数(print(search_response["pageInfo"]["totalResults"])の値)よりも、実際に情報を取得できた動画タイトル数の方が少なくなっていた理由はおそらく、YouTubeの検閲で動画が削除されたせいだと思う。動画の内容的に、十分ありうる。
出力ファイル(videolist.json)を見ると、チャンネル内の168個の動画の情報(動画のタイトル、動画のID、動画のURL、サムネイルのURL、etc…)が取得できていた。これはチャンネルのTopページに表示されている数と同じなので、無事にチャンネル内に現存する全ての動画情報が取得できた模様。
「videolist.py」のコードはこれです。(「YouTube Data API による動画一覧取得|JQinglong」で公開しているものに、上記の変更だけを加えています。APIキーは自分で取得したものを入れる。)
from apiclient.discovery import build import json import datetime from dateutil.relativedelta import relativedelta # API情報 #API_KEY = 'xxx' API_KEY = 'AIzaSyDde-UwyjPfRUO4RjdGSxXkH5D1Bf5aJBg' YOUTUBE_API_SERVICE_NAME = 'youtube' YOUTUBE_API_VERSION = 'v3' videos = [] #videoURLを格納する配列 def youtube_search(pagetoken, st, ed): youtube = build( YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION, developerKey=API_KEY ) search_response = youtube.search().list( channelId='UC6d0ISWLewaGX08fLZy8gKQ', part='snippet', type='video', maxResults=50, publishedAfter=st, #'2013-01-01T00:00:00Z', publishedBefore=ed, #'2014-01-01T00:00:00Z', pageToken=pagetoken ).execute() print(search_response["pageInfo"]["totalResults"]) for search_result in search_response.get("items", []): if search_result["id"]["kind"] == "youtube#video": d = {'title': search_result["snippet"]["title"], 'description': search_result["snippet"]["description"], 'keywords': '', 'source': 'junyiacademy', 'inherentProperties' : { 'id': search_result["id"]["videoId"], 'url': 'https://www.youtube.com/watch?v=%s' % search_result["id"]["videoId"], 'thumbnailUrl': search_result["snippet"]["thumbnails"]["default"]["url"], 'publishedAt': search_result["snippet"]["publishedAt"], } } videos.append(d) print(search_result["snippet"]["title"]) try: nextPagetoken = search_response["nextPageToken"] print(nextPagetoken) youtube_search(nextPagetoken, st, ed) except: return dt = datetime.datetime(2010, 1, 1, 0, 0) for i in range(1, 84): print(dt.isoformat()) youtube_search('', dt.isoformat()+'Z', (dt + relativedelta(months=1)).isoformat()+'Z') dt = dt + relativedelta(months=1) with open("videolist.json", mode="w", encoding="utf-8") as f: json.dump(videos, f, ensure_ascii=False, indent=2)
■得られた動画情報から、動画URLのみを抜き出した一覧を作る
得られた動画情報(videolist.json)の中の、動画タイトル1個分の塊は以下の通り。
{ "title": "[支那事変とは何か]日本と汪兆銘政権の支那統治", "description": "南京占領後から終戦まで、日本と汪兆銘政権が7年間支那の大半を統治した。日 本の目的は講和撤退でありソ連の狙いは引き留め ...", "keywords": "", "source": "junyiacademy", "inherentProperties": { "id": "OUSHVCAz9-Q", "url": "https://www.youtube.com/watch?v=OUSHVCAz9-Q", "thumbnailUrl": "https://i.ytimg.com/vi/OUSHVCAz9-Q/default.jpg", "publishedAt": "2010-02-22T08:19:58Z" } },
「yt-dlp」コマンドで必要な情報はURLのみだから、まずはこのjsonファイルからURLの行のみを抜き出した一覧を作る。これはgrepコマンドの出力をファイルにリダイレクトするだけでできる。
$ grep url videolist.json > videolist.sh
得られたファイルの中身はこれ。うまくできている。
"url": "<https://www.youtube.com/watch?v=OUSHVCAz9-Q>", "url": "<https://www.youtube.com/watch?v=mRz-d_7aob8>", "url": "<https://www.youtube.com/watch?v=aZNPjuzOUeI>", :(以下略)
あとはエディタの矩形編集で「videolist.sh」を以下のように書き換える。私が使っているのはCotEditorで、「option + ドラッグ」操作で矩形選択ができた。
#!bin/sh yt-dlp "<https://www.youtube.com/watch?v=OUSHVCAz9-Q>" yt-dlp "<https://www.youtube.com/watch?v=mRz-d_7aob8>" yt-dlp "<https://www.youtube.com/watch?v=aZNPjuzOUeI>" :(以下略)
「videolist.sh」を「yt-dlp」の実行フォルダに持っていって、権限を与えて、実行。
$ cp [videolist.sh](http://videolist.sh/) ../yt-dlp/ TakaBookAirM1:python_wk1 taka$ cd ../yt-dlp/ TakaBookAirM1:yt-dlp taka$ ls [dl-list.sh](http://dl-list.sh/) [videolist.sh](http://videolist.sh/) yt-dlp.conf TakaBookAirM1:yt-dlp taka$ chmod 777 [videolist.sh](http://videolist.sh/) TakaBookAirM1:yt-dlp taka$ ./videolist.sh [youtube] Extracting URL: https://www.youtube.com/watch?v=OUSHVCAz9-Q [youtube] OUSHVCAz9-Q: Downloading webpage [youtube] OUSHVCAz9-Q: Downloading tv client config [youtube] OUSHVCAz9-Q: Downloading player b191cf34 [youtube] OUSHVCAz9-Q: Downloading tv player API JSON [youtube] OUSHVCAz9-Q: Downloading ios player API JSON [youtube] OUSHVCAz9-Q: Downloading m3u8 information [info] OUSHVCAz9-Q: Downloading 1 format(s): 137+140 [info] Downloading video thumbnail 44 ... [info] Video Thumbnail 44 does not exist [info] Downloading video thumbnail 43 ... [info] Writing video thumbnail 43 to: /Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].jpg [download] Destination: /Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].f137.mp4 [download] 100% of 93.32MiB in 00:00:39 at 2.34MiB/s [download] Destination: /Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].f140.m4a [download] 100% of 9.08MiB in 00:00:03 at 2.69MiB/s [Merger] Merging formats into "/Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].mp4" Deleting original file /Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].f137.mp4 (pass -k to keep) Deleting original file /Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].f140.m4a (pass -k to keep) [EmbedThumbnail] mutagen: Adding thumbnail to "/Users/taka/Downloads/[支那事変とは何か]日本と汪兆銘政権の支那統治 [OUSHVCAz9-Q].mp4" [youtube] Extracting URL: https://www.youtube.com/watch?v=mRz-d_7aob8 :(以下略)
約90分で167個の動画をダウンロードできた。なお、動画1つあたりの長さは約10分。成功なのだが、168個のはずがなぜ167個?ダウンロードリストとダウンロード結果を比較してみた。

「尖閣領海侵犯と日本人の対応 4」がダウンロードリストにあるのにダウンロードできていないので、yt-dlpのコンソールログを見直してみた。
[Merger] Merging formats into "/Users/taka/Downloads/尖閣領海侵犯と日本人の対応 5 [d8bUHhCvOs8].mp4" Deleting original file /Users/taka/Downloads/尖閣領海侵犯と日本人の対応 5 [d8bUHhCvOs8].f137.mp4 (pass -k to keep) Deleting original file /Users/taka/Downloads/尖閣領海侵犯と日本人の対応 5 [d8bUHhCvOs8].f140.m4a (pass -k to keep) [EmbedThumbnail] mutagen: Adding thumbnail to "/Users/taka/Downloads/尖閣領海侵犯と日本人の対応 5 [d8bUHhCvOs8].mp4" [youtube] Extracting URL: https://www.youtube.com/watch?v=Jpghajs4GuM [youtube] Jpghajs4GuM: Downloading webpage [youtube] Jpghajs4GuM: Downloading tv client config [youtube] Jpghajs4GuM: Downloading player b191cf34 [youtube] Jpghajs4GuM: Downloading tv player API JSON [youtube] Jpghajs4GuM: Downloading ios player API JSON ERROR: [youtube] Jpghajs4GuM: Sign in to confirm your age. This video may be inappropriate for some users. Use --cookies-from-browser or --cookies for the authentication. See https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp for how to manually pass cookies. Also see https://github.com/yt-dlp/yt-dlp/wiki/Extractors#exporting-youtube-cookies for tips on effectively exporting YouTube cookies [youtube] Extracting URL: https://www.youtube.com/watch?v=Fu-qzhgoQvo [youtube] Fu-qzhgoQvo: Downloading webpage [youtube] Fu-qzhgoQvo: Downloading tv client config [youtube] Fu-qzhgoQvo: Downloading player b191cf34 [youtube] Fu-qzhgoQvo: Downloading tv player API JSON [youtube] Fu-qzhgoQvo: Downloading ios player API JSON [youtube] Fu-qzhgoQvo: Downloading m3u8 information [info] Fu-qzhgoQvo: Downloading 1 format(s): 137+140 [info] Downloading video thumbnail 41 ... [info] Video Thumbnail 41 does not exist [info] Downloading video thumbnail 40 ... [info] Writing video thumbnail 40 to: /Users/taka/Downloads/尖閣領海侵犯と日本人の対応 2 [Fu-qzhgoQvo].jpg [download] Destination: /Users/taka/Downloads/尖閣領海侵犯と日本人の対応 2 [Fu-qzhgoQvo].f137.mp4 [download] 100% of 17.60MiB in 00:00:12 at 1.37MiB/s [download] Destination: /Users/taka/Downloads/尖閣領海侵犯と日本人の対応 2 [Fu-qzhgoQvo].f140.m4a [download] 100% of 8.61MiB in 00:00:03 at 2.29MiB/s [Merger] Merging formats into "/Users/taka/Downloads/尖閣領海侵犯と日本人の対応 2 [Fu-qzhgoQvo].mp4"
エラーが出ていた。この動画だけ年齢制限がかけられているため、ログインした状態でないとアクセスできないようだ。納得。(そういう動画はどうやってダウンロードするのだという疑問もあるが、それはまた今度。)
長い記事になってしまったが、目的を達成することができた。
