0
0

Pythonのyoutube-transcript-apiの使用時に、プロキシサーバーを使用する

Last updated at Posted at 2024-09-22

初心者です。間違い等ありましたら、ご指摘ください🙇

環境

  • MacBook Air M2
  • laravel 10.48.22
  • python 3.12.4
  • VSCode

SwiftUI✖️Laravel✖️Pythonを使用したアプリをHerokuへデプロイし、動作確認を行っていたところ、以下の問題に遭遇。

問題

Pythonライブラリyoutube-transcript-api を使用してYouTube動画の字幕を取得できるのだが、ローカル上では取得ができ、Heroku上では取得ができない…

結論(未解決)

プロキシサーバー(有料)を利用する。

原因

YouTubeが、クラウドサービスのIPアドレスからのアクセスをブロックしているっぽい?

以下の議論を参考にした。

簡単に総括すると、

  • クラウドサービス(例えば、Digital Ocean、AWS、Google Cloud、Herokuなど)を使うと、他のユーザーと同じIPアドレスのプールからIPが割り当てられるため、多くのユーザーが同じクラウドサービスからYouTubeにアクセスしようとすると、同じIPアドレスが大量のリクエストに使用される。

  • その結果、YouTubeが「なんか同じIPアドレスからいっぱいリクエストが来るな。なんか怪しいな。このIPアドレスはブロックしておこう」という感じになっている?

  • 議論の中で、proxy serverを利用することで解決している複数のコメントが見られた。

方法・過程

詳細を見ていく。

まず、元々のソースコードから。

  • /htdocs/youtube-transcript-app/routes/api.php(字幕取得のAPIのURL)

    // ルーティング
    Route::get('/get_subtitles', 'App\Http\Controllers\SubtitleController@getSubtitles');
    
  • /htdocs/youtube-transcript-app/app/Http/Controllers/SubtitleController.php(字幕取得pythonスクリプトの実行)

    class SubtitleController extends Controller
    {
        // 指定の動画の字幕取得
        public function getSubtitles(Request $request)
        {
            // 字幕取得したいYouTube動画のIDを取得
            $video_id = $request->query('video_id');
    
            // 実際の字幕取得処理を行うPythonスクリプトのパス
            $scriptPath = base_path('myenv/scripts/get_transcript.py');
    
            // Processコンポーネントは、外部プログラムやコマンドをPHPスクリプト内で実行するためのもの
            $process = new Process(['python3', $scriptPath, $video_id]);
            // 実行
            $process->run();
            
            // 以下はエラーハンドリングのため、今回はあまり関係ない
            try {
                // プロセスの成功を確認
                if (!$process->isSuccessful()) {
                    throw new ProcessFailedException($process); // プロセス失敗時に例外をスロー
                }
                // 成功した場合の処理
                $subtitles = $process->getOutput();
                // フォーマット化したjsonレスポンスを返却
                return response()->json(json_decode($subtitles, true));
            } catch (ProcessFailedException $e) {
                // 失敗の例外処理
                Log::error('Process Failed Exception: ' . $e->getMessage());
                return response()->json([
                    'detail' => $e->getMessage(),
                ]);
                // throw new VideoSubtitleException(message: '字幕の取得に失敗しました。\nこの動画には字幕が含まれていない可能性があります。', detail: $e->getMessage());
            } catch (Exception $e) {
                // その他のエラー
                // throw $e;
                return response()->json([
                    'detail' => $e->getMessage(),
                ]);
            }
        }
    }
    
  • /htdocs/youtube-transcript-app/myenv/scripts/get_transcript.py(字幕取得処理)

    import sys
    import json
    from youtube_transcript_api import YouTubeTranscriptApi
    
    def get_transcript(video_id):
    
        # 字幕取得
        transcript_list = YouTubeTranscriptApi.list_transcripts(video_id)
    
        # 英語字幕を探して抽出
        transcript = transcript_list.find_transcript(['en'])
        transcript_data = transcript.fetch()
        
        〜〜〜〜〜
        # その他の任意の処理
        〜〜〜〜〜
    
       # 取得した字幕情報をlaravel側に返す
        return 〜〜〜
    
    if __name__ == "__main__":
        video_id = sys.argv[1]
        transcripts = get_transcript(video_id)
        print(json.dumps(transcripts, ensure_ascii=False))
    
    

以下を実行すると、

http://localhost:8000/api/get_subtitles?video_id=NaqB5zYGEsw&t=4s

以下のような字幕情報が得られ、期待するレスポンスが得られた。

        {
            "text": "everyone is watching this show it is thank I mean it's a breakout hit and",
            "start": 0.16,
            "duration": 12.56
        },
        {
            "text": "I I know sometimes when you're shooting as an actor you're kind of like in a",
            "start": 7.24,
            "duration": 6.718999999999999
        },
        {
            "text": "vacuum and you don't understand that what's happening in the world when did",
            "start": 10.28,
            "duration": 8.68
        },
        .......

しかし、Herokuへデプロイしたものを実行したところ…

"The command \"'python3' '/Applications/MAMP/htdocs/youtube-transcript-app/myenv/scripts/get_transcript.py' 'NaqB5zYGEs'\" failed.\n\nExit Code: 1(General error)\n\nWorking directory: /Applications/MAMP/htdocs/youtube-transcript-app/public\n\nOutput:\n================\n\n\nError Output:\n================\nTraceback (most recent call last):\n  File \"/Applications/MAMP/htdocs/youtube-transcript-app/myenv/scripts/get_transcript.py\", line 85, in <module>\n    transcripts = get_transcript(video_id)\n                  ^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/Applications/MAMP/htdocs/youtube-transcript-app/myenv/scripts/get_transcript.py\", line 40, in get_transcript\n    transcript_list = YouTubeTranscriptApi.list_transcripts(video_id, proxies=proxies)\n                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/Applications/MAMP/htdocs/youtube-transcript-app/myenv/lib/python3.12/site-packages/youtube_transcript_api/_api.py\", line 71, in list_transcripts\n    return TranscriptListFetcher(http_client).fetch(video_id)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/Applications/MAMP/htdocs/youtube-transcript-app/myenv/lib/python3.12/site-packages/youtube_transcript_api/_transcripts.py\", line 48, in fetch\n    self._extract_captions_json(self._fetch_video_html(video_id), video_id),\n    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/Applications/MAMP/htdocs/youtube-transcript-app/myenv/lib/python3.12/site-packages/youtube_transcript_api/_transcripts.py\", line 62, in _extract_captions_json\n    raise TranscriptsDisabled(video_id)\nyoutube_transcript_api._errors.TranscriptsDisabled: \nCould not retrieve a transcript for the video https://www.youtube.com/watch?v=NaqB5zYGEs! This is most likely caused by:\n\nSubtitles are disabled for this video\n\nIf you are sure that the described cause is not responsible for this error and that a transcript should be retrievable, please create an issue at https://github.com/jdepoix/youtube-transcript-api/issues. Please add which version of youtube_transcript_api you are using and provide the information needed to replicate the error. Also make sure that there are no open issues which already describe your problem!\n"

「字幕が取得できない」という旨のエラーが発生。

video_id の値も変えたりしてみたが、変わらず。しかし、ローカル上では動作する…

Proxy Server を構築する

先ほども言及した、こちらの議論を参考に、プロキシサーバーを使用してみる。

  • プロキシサーバーとは?

    以下のサイトが参考になりました。

今回の用途としては、以下のような感じになるのでしょうか。

Smart ProxyResidentail Proxy でプロキシサーバーを構築。(有料)

Log in | Smartproxy

構築が完了したら、Smart Proxy で自動生成されたユーザー名とパスワードを用いて、プロキシサーバーにアクセスする。

import sys
import json
from youtube_transcript_api import YouTubeTranscriptApi

def get_transcript(video_id):

######################## 追記
username = 'xxxxxxxxxxx' # SmartProxyにて、自動生成されたユーザー名
password = 'yyyyyyyyyyy' # SmartProxyにて、自動生成されたパスワード
proxy = f"http://{username}:{password}@[プロキシサーバーのURL]:[ポート番号]"

# 字幕取得
transcript_list = YouTubeTranscriptApi.list_transcripts(video_id, proxies = {
        'http': proxy,
        'https': proxy
    })
#########################

    # 英語字幕を探して抽出
    transcript = transcript_list.find_transcript(['en'])
    transcript_data = transcript.fetch()
    
    〜〜〜〜〜
    # その他の任意の処理
    〜〜〜〜〜

   # 取得した字幕情報をlaravel側に返す
    return 〜〜〜

if __name__ == "__main__":
    video_id = sys.argv[1]
    transcripts = get_transcript(video_id)
    print(json.dumps(transcripts, ensure_ascii=False))

※ 個人情報のため、外部に公開する前に、username, password, proxy は.envファイルに環境変数として定義。今回は、Herokuをクラウドサービスとして利用しているため、以下をターミナルで実行し、Heroku上に環境変数として定義。

heroku config:set PROXY_SERVER_URL="http://{username}:{password}@[プロキシサーバーのURL]:[ポート番号]"

終わり。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0