@naetoru2144

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

pytchatがheroku内でのみ、特定のvideo_id群に対してエラーを起こす

解決したいこと

PythonでYouTube関連のwebアプリを作っています。Pythonスクリプトを定期実行しているんですが、pytchatライブラリがheroku内でのみ特定のvideo_id(約1/60くらいの確率で発生)についてエラーを起こします。これを解決したいです。

作業環境

  • Python 3.12.4
  • Pytchat 0.5.5

発生している問題・エラー

Traceback (most recent call last):
  File "/app/.heroku/python/lib/python3.12/site-packages/pytchat/util/__init__.py", line 107, in get_channelid
    raise IndexError
IndexError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/app/test_script/mini_livetest.py", line 15, in <module>
    chat = pytchat.create(video_id=ids[0],interruptable=False)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.heroku/python/lib/python3.12/site-packages/pytchat/core/__init__.py", line 7, in create
    return PytchatCore(_vid, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.heroku/python/lib/python3.12/site-packages/pytchat/core/pytchat.py", line 96, in __init__
    self._setup()
  File "/app/.heroku/python/lib/python3.12/site-packages/pytchat/core/pytchat.py", line 106, in _setup
    channel_id=util.get_channelid(self._client, self._video_id),
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.heroku/python/lib/python3.12/site-packages/pytchat/util/__init__.py", line 110, in get_channelid
    ret = get_channelid_2nd(client, video_id)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/.heroku/python/lib/python3.12/site-packages/pytchat/util/__init__.py", line 119, in get_channelid_2nd
    raise InvalidVideoIdException(f"Cannot find channel id for video id:{video_id}. This video id seems to be invalid.")
pytchat.exceptions.InvalidVideoIdException: Cannot find channel id for video id:g5vKkS19nq8. This video id seems to be invalid.

該当するソースコード

import pytchat
import requests, json

# API接続ができるか確認
url = requests.get("http://api.aoikujira.com/kawase/json/usd")
text = url.text
json_currency = json.loads(text)
print(json_currency)
print("Successful in API communication")

# エラーを起こしたvideo_idを人力で持ってきている
ids = "g5vKkS19nq8,kHo37taBFP8,6IJpVVGIxno,LPI_FnuvfhE,dWhmdgpeBts,NXcmsY5iXkw,ECATLuOt1AE"
ids = ids.split(",")

# エラーを起こす部分 上に挙げたどのvideo_idに対しても同じエラーが起こる
chat = pytchat.create(video_id=ids[0],interruptable=False)

自分で試したこと

YouTubeAPIでエラーを起こすvideo_idの共通点を探しました。コメント量や再生回数、放送の日時にばらつきがあり、YouTubeAPIで確認できる範囲でエラーを起こすvideo_idに特有の特徴は見つかりませんでした...

またローカルと、↓のcolabにおいてコードが正常に動作します。
https://colab.research.google.com/drive/1npGihQ4KVHFn1tBMpAbY7HDFVsDk4CNW

1 likes

2Answer

Comments

  1. @naetoru2144

    Questioner

    自分もそう思って問い合わせたんですがサポートの範囲外と言われてしまい...
     2024-12-10 20.08.06.png

自己解決

pytchatのget_channel_id()内のhttpリクエストがリモートかつ特定のvideo_idにおいて失敗することが原因でした。
アクセスしているhttps://www.youtube.com/embed/(ここにvideo_id) https://m.youtube.com/watch?v=(ここにvideo_id)がスピナー画面(チャンネルIDなし)を返しているという機序でした。

以下のように外部ページ埋め込み用のAPIエンドポイントであるhttps://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=(ここにvideo_id)&format=jsonを参照するようにget_channel_id()をパッチすることでエラーが解消しました。

Pytchatの関数

def get_channelid(client, video_id):
    resp = client.get("https://www.youtube.com/embed/{}".format(quote(video_id)), headers=config.headers)  
    match = re.search(PATTERN_CHANNEL, resp.text)
    try:
        if match is None:
            raise IndexError
        ret = match.group(1)
    except IndexError:
        ret = get_channelid_2nd(client, video_id)
    return ret


def get_channelid_2nd(client, video_id):
    resp = client.get("https://m.youtube.com/watch?v={}".format(quote(video_id)), headers=config.m_headers)  
    
    match = re.search(PATTERN_M_CHANNEL, resp.text)
    if match is None:
        raise InvalidVideoIdException(f"Cannot find channel id for video id:{video_id}. This video id seems to be invalid.")
    try:
        ret = match.group(1)
    except IndexError:
        raise InvalidVideoIdException(f"Invalid video id: {video_id}")
    return ret

パッチした関数

import re, httpx
from urllib.parse import quote
import pytchat.util as original_util
from pytchat import config


def _get_channelid_via_oembed(video_id: str) -> str | None:
   """oEmbedにアクセス、UCから始まるchannel_idを返す"""
    url = f"https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v={quote(video_id)}&format=json"
    with httpx.Client(http2=False, timeout=10, headers={"Accept-Language": "ja,en-US;q=0.9,en;q=0.8"}) as c:
        r = c.get(url)
        if r.status_code != 200:
            return None
        author_url = r.json().get("author_url", "")
        if not author_url:
            return None
        m = re.search(r"/channel/(UC[0-9A-Za-z_-]{22})", author_url)
        if m:
            return m.group(1)
        if "/@" in author_url:
            r2 = c.get(author_url + "/about", follow_redirects=True)
            m2 = re.search(r'"externalId":"(UC[0-9A-Za-z_-]{22})"', r2.text)
            if m2:
                return m2.group(1)
    return None


def robust_get_channelid(client, video_id):
    """安定版 get_channelid: oEmbed優先 → HTMLフォールバック"""
    uc = _get_channelid_via_oembed(video_id)
    if uc:
        return uc

    # pytchatの通常HTMLルートをフォールバック
    for url in (
        f"https://www.youtube.com/embed/{quote(video_id)}?hl=ja&gl=JP",
        f"https://m.youtube.com/watch?v={quote(video_id)}&hl=ja&gl=JP",
    ):
        r = client.get(url, headers=config.headers if "embed" in url else config.m_headers)
        m = re.search(r'"channelId":"(UC[0-9A-Za-z_-]{22})"', r.text)
        if m:
            return m.group(1)

    return original_util.get_channelid_2nd(client, video_id)
0Like

Your answer might help someone💌