AItuberのライブ配信でYutubuのコメントを取得する (1)pytchat

長時間連続でAItuberのライブ配信をする場合、Yutubuのコメントを公式のAPIで取得すると利用制限にかかるので、pytchatで実装をしましたが、取得ができなくなる問題と、動的にvideo_idを変更できる機能が必要だったため、自分なりに工夫をして動かしました。他にもっとスマートな方法があるのかもしれません。
(注: ここのコードは実際に動いているコードからコメントとデバッグ用のprint文及び不要な引数を削除しています。この過程でミスが有って、このままのコードを動かすと何らかのエラーが出る可能性があります)

システムはフロントエンドにVue,バックエンドがFastAPIです。
フロントのブラウザーで入力されたYutubeのライブ配信URLを入力し、バックエンドのFastAPIへ渡して、pytchatでコメントを取得するよになっています。pytchatの代わりに公式APIを使っても可能ですが、取得制限に引っかかり長時間配信の途中でコメントが取得で来なくなります。pytchatなら制限なしに取得できるので、取得間隔を短くすることもできてとても便利です。

私のシステムでは実際に利用すると少し問題がありました。

1)video_idを動的に変更できない。
 ライブ配信を新規で開始するとvideo_idが更新されるので、ブラウザでURLを入力してコメントを取得しようとすると、公式APIでは問題なくできるのですが、pytchatを使うとエラーになります。
 livechat = pytchat.create(video_id)
の部分はthreadではなくmainで動かすように促されます。確かに公式のコードはそのようになっていますが、FastAPIだとAPIがthreadなので、エラーになるようです。そこで、pytchatは独立した子プロセスとして動かすことにしました。

pytchatの開始と停止
フロントのボタン操作とURLでpytchatの開始と停止を制御します。以下はpytchatの開始と停止をフロントからのリクエストに応じて実行するバクエンドのAPIです。
pytchatはmultiprocessingで動かし、受け取ったYutubeのコメントをメッセージqueueでフロントに送るために、queueを作成してURLとqueueを引数としてpytchatプロセスを開始しています。

#outube_commentのget_pytchat procesの開始と停止
@app.post('/api/get_pytchat_start')
def get_pytchat_start(youtube_url: str = Form(...), start_stop: str = Form(...)):
    global queue_ytcom

    if start_stop=="start":
        queue_ytcom = multiprocessing.Queue()  # Queueを作成
        proc = multiprocessing.Process(target=get_pytchat, args=(youtube_url,queue_ytcom))  # get_pytchat()を使うプロセスを作成
        proc.start()  #スタート
        result="start"
    elif start_stop=="stop":
        proc.kill()   #停止
        result="stop"
    else:
        result="error"#unknown function
    return {"message":result}

pytchat側ではURLからvideo_idを取り出し、
 livechat = pytchat.create(video_id)
を作成して、livechat 監視ループに入ります。

2)コメントが稀に取得できなくなる。
なぜか数時間動かすとコメントが取得できなくなる問題がありました。原因は、
while livechat.is_alive():
でlivechat.is_alive()がFalseになることでプロセスが自動的にターミネートされることだとわかりました。新たに、大きなループを設けてlivechat.is_alive()がFalseのときには最初から再スタートするよに変更しています。以下は改良済みの pytchatプロセスです。

# multiprocessing terget
def get_pytchat(youtube_url):
    video_id = yt_url.replace('https://www.youtube.com/watch?v=', '') #youtube_urlから video_idを取り出し
    while True: #無限ループになるので、親プロセスでkillする。
        
        livechat = pytchat.create(video_id)

        datetime=[]
        name=[]
        com_msg=[]
    
        while livechat.is_alive():
            # チャットデータの取得
            chatdata = livechat.get()
            print(chatdata.items)
            if chatdata!=[]:
                for c in chatdata.items:
                    datetime.append(c.datetime)
                    name.append(c.author.name)
                    com_msg.append(c.message)

                #コメントをqueueで返す
                queue_data=[com_msg,datetime,name]
                queue.put(queue_data)
       
            time.sleep(1)   #コメント確認間隔
            print(livechat.is_alive())

        time.sleep(5)  #livechat.is_alive()がFalseだったときにlivechat = pytchat.create(video_id)からやり直す


取得したコメントの読み出し
メッセージqueueからコメントを読み出してフロントへ返すAPIのコードです。pytchatプロセスが取得したコメントはフロントがこのAPIを呼んだときにqueueから読み出してフロントへ渡します。

#get_pytchatが送ったyoutube_commentをQueueから取得 
@app.post('/api/get_youtube_comment')
def get_youtube_comment():
    global queue_ytcom

    try:
        if queue_ytcom.empty()==False:
            received_data = queue_ytcom.get() # 子プロセスからデータを取得
            print("Queue:",received_data)
            com_msg=received_data[0]
            datetime=received_data[1]
            nameg=received_data[2]
            result="ok"
        else:
            com_msg=[]
            datetime=[]
            nameg=[]          
        result="empty"
    except:
        result="error"
    return {"message":result,"com_msg"com_msg,"datetime":datetime,"nameg":nameg}

改良後は順調にlivechat.is_alive()のFalseをかわしながら順調に動いています。

pytchatを使う場合、Yutubeの仕様が変更になると使えなくなる可能性はあります。その時は公式APIに戻して制限解除申請をするしかありません。