質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.91%

YouTube Liveのアーカイブからチャットを取得するコードを模写したが上手くいかない @Python

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 4
  • VIEW 787

mongaa

score 1

前提・実現したいこと

PythonでYoutube Liveのアーカイブからチャットを取得したいです。
こちらのサイトのコードをほぼそのまま使用しています。
https://github.com/geerlingguy/youtube_chat_crawler/blob/master/YoutubeChatReplayCrawler.py
コードは下記について変更しています。
→youtubeの動画IDを引数としてvideo_idに入れているのを、初めからtarget_urlに打ち込んでいます。したがって、引数の長さを判別する箇所もコメントアウトしています。

発生している問題・エラーメッセージ

pyファイルを実行し作成されるテキストファイル(comment_data.txt)を開いても中身が空っぽです。
cmd,Visual Studio Code共にエラーメッセージは発生していません。
指定した動画からコメントを取得する流れのどこかで想定通りに動いていないと思われますが、
解決策が分からなくて困っています。

該当のソースコード

#!/usr/bin/env python3
from bs4 import BeautifulSoup
import ast
import requests
import re
import sys
# Verify user supplied a YouTube URL.
#if len(sys.argv) == 1:
#   print("Please provide a YouTube URL (e.g. ./YoutubeChatReplayCrawler.py YOUTUBE_VIDEO_URL)")
#   sys.exit(0)
# Produce a valid filename (from Django text utils).
def get_valid_filename(s):
   s = str(s).strip().replace(' ', '_')
   return re.sub(r'(?u)[^-\w.]', '', s)
# Set up variables for requests.
#target_url = sys.argv[1]
target_url = "https://www.youtube.com/watch?v=mOBvvwCosE4" #←適当に選んだ動画のURL
dict_str = ''
next_url = ''
comment_data = []
session = requests.Session()
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'}
# Get the video page.
html = session.get(target_url)
soup = BeautifulSoup(html.text, 'html.parser')
# Retrieve the title and sanitize so it is a valid filename.
title = soup.find_all('title')
title = title[0].text.replace(' - YouTube', '')
title = get_valid_filename(title)
# Regex match for emoji.
RE_EMOJI = re.compile('[\U00010000-\U0010ffff]', flags=re.UNICODE)
# Find any live_chat_replay elements, get URL for next live chat message.
for iframe in soup.find_all("iframe"):
   if("live_chat_replay" in iframe["src"]):
       next_url = iframe["src"]
if not next_url:
   print("Couldn't find live_chat_replay iframe. Maybe try running again?")
   sys.exit(0)
# TODO - We should fail fast if next_url is empty, otherwise you get error:
# Invalid URL '': No schema supplied. Perhaps you meant http://?
# TODO - This loop is fragile. It loops endlessly when some exceptions are hit.
while(1):
   try:
       html = session.get(next_url, headers=headers)
       soup = BeautifulSoup(html.text, 'lxml')
       # Loop through all script tags.
       for script in soup.find_all('script'):
           script_text = str(script)
           if 'ytInitialData' in script_text:
               dict_str = ''.join(script_text.split(" = ")[1:])
       # Capitalize booleans so JSON is valid Python dict.
       dict_str = dict_str.replace("false", "False")
       dict_str = dict_str.replace("true", "True")
       # Strip extra HTML from JSON.
       dict_str = re.sub(r'};.*\n.+<\/script>', '}', dict_str)
       # Correct some characters.
       dict_str = dict_str.rstrip(" \n;")
       # TODO: I don't seem to have any issues with emoji in the messages.
       # dict_str = RE_EMOJI.sub(r'', dict_str)
       # Evaluate the cleaned up JSON into a python dict.
       dics = ast.literal_eval(dict_str)
       # TODO: On the last pass this returns KeyError since there are no more
       # continuations or actions. Should probably just break in that case.
       continue_url = dics["continuationContents"]["liveChatContinuation"]["continuations"][0]["liveChatReplayContinuationData"]["continuation"]
       print('Found another live chat continuation:')
       print(continue_url)
       next_url = "https://www.youtube.com/live_chat_replay?continuation=" + continue_url
       # Extract the data for each live chat comment.
       for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]:
           comment_data.append(str(samp) + "\n")
   # next_urlが入手できなくなったら終わり
   except requests.ConnectionError:
       print("Connection Error")
       continue
   except requests.HTTPError:
       print("HTTPError")
       break
   except requests.Timeout:
       print("Timeout")
       continue
   except requests.exceptions.RequestException as e:
       print(e)
       break
   except KeyError as e:
       error = str(e)
       if 'liveChatReplayContinuationData' in error:
           print('Hit last live chat segment, finishing job.')
       else:
           print("KeyError")
           print(e)
       break
   except SyntaxError as e:
       print("SyntaxError")
       print(e)
       break
       # continue #TODO
   except KeyboardInterrupt:
       break
   except Exception:
       print("Unexpected error:" + str(sys.exc_info()[0]))
# Write the comment data to a file named after the title of the video.
with open(title + ".json", mode='w', encoding="utf-8") as f:
   f.writelines(comment_data)
print('Comment data saved to ' + title + '.json')

試したこと

・実行時、すぐ"Couldn't find live_chat_replay iframe. Maybe try running again?"と表示されるので、while(1):のループに入る前を見ていけば良いと考えました。
・そこで、デバッグによって各変数の状態を確認したところ、next_url=""のままでした。よって、>>if("live_chat_replay" in iframe["src"]):は一度もTrueにならなかったと考えています。(Trueなら何かしら代入されているため)
・上記条件式で使用されるiframeの中にはいろんなタグが入っていました。"src"に関係ありそうな箇所(自信ないです)を載せておきます。
<iframe src="https://accounts.google.com/ServiceLogin?uilel=3&amp;hl=ja&amp;service=youtube&amp;passive=true&amp;continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Ffeature%3Dpassive%26next%3D%252Fsignin_passive%26hl%3Dja%26action_handle_signin%3Dtrue%26app%3Ddesktop" style="display: none"></iframe>
iframeを見ても何を確認してどうすれば良いかわからなくなり手詰まりの状態になっています。。

補足情報(FW/ツールのバージョンなど)

python         3.6.5
beautifulsoup4 4.9.1
使用PC:
Surface Pro 5
Windows 10

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • mongaa

    2020/07/12 02:29

    夜ずっと考えてみましたがわかりませんでしたwww
    誰か・・・

    キャンセル

  • mongaa

    2020/07/12 02:34

    ブラウザの"検証"で確認してみると、下記のコードにチャットのツリーが繋がっていました。
    <iframe frameborder="0" scrolling="no" id="chatframe" class="style-scope ytd-live-chat-frame" src="/live_chat_replay?continuation=op2w0wRgGlBDamdhRFFvTGJVOUNkblozUTI5elJUUXFKd29ZVlVNeGIzQklWWEozT0hKMmJuTmhaRlF0YVVkd04wTm5FZ3R0VDBKMmRuZERiM05GTkNBQkABWgQQsPUncgIIBHgB"></iframe>

    なので、
    >>for iframe in soup.find_all("iframe")
    と次の行で"src"=の部分が抽出されればよいと思うのですが、「試したこと」で記載した通り、変数iframeの中にはこの部分がそもそも格納されていない状況です。
    ※現在iframeに格納されている"https://accounts.google.com/ServiceLogin~~~~"はhtmlの<head>タグのもので、抽出したい箇所は<body>タグにあるiframeです。

    現在わかったのはここまでです・・・・・

    キャンセル

  • 退会済みユーザー

    2020/09/10 02:16

    複数のユーザーから「意図的に内容が抹消された質問」という意見がありました
    解決後に編集機能を用いて質問内容を改変し関係のない内容にしたり、内容を削除する行為は禁止しています。
    投稿していただいた質問は、後に他の誰かが困ったときに助けになる情報資産になると考えるからです。
    「質問を編集する」ボタンから編集を行い、他のユーザにも質問内容が見えるように修正してください。

回答 1

checkベストアンサー

+2

推測なのですが、質問文の

next_url=""のままでした。よって、>>if("live_chat_replay" in iframe["src"]):は一度もTrueにならなかったと考えています。

や、6番目のコメントの

ブラウザの"検証"で確認してみると、下記のコードにチャットのツリーが繋がっていました。~

以下から推測するに、requestsで得られるHTMLと、実際にブラウザが最終的に獲得しているHTMLが異なるのではないでしょうか。

具体的には、ブラウザは、最初に獲得したソースからさらにjavascript等のスクリプトや他のサーバから追加のデータを取得し、レンダリングした上でページを表示している場合があります。
requestsライブラリはブラウザではないため、ブラウザの完全なレンダリング機能は持っていません。

pythonでブラウザのレンダリング機能を実現しようとするとselenium等のヘッドレスブラウザを使うことになります。

seleniumでも可能ですが、ドライバの準備等でハードルが高いため、もっと簡単に使える、requests_htmlというライブラリがあります。

質問文のコードを下記のように置き換えてみてはどうでしょうか。

実行前に

pip install requests_html


で requests_htmlをインストールしてください。

5行目以降。「#~コメント」にしているところを置き換えています。略となっているところはそのままでOKだと思います。

#!/usr/bin/env python3
(略)
import requests
import requests_html # <= 追加

(略)
# session = requests.Session() この行を↓に置き換え
session = requests_html.HTMLSession()

# soup = session.get(target_url) この行を↓に置き換え
resp = session.get(target_url)

resp.html.render(sleep=3)  # <= 追加。レンダリングを完了させるため3秒待ちます。
# soup = BeautifulSoup(html.text, 'html.parser')  <= ここは不要なのでコメントアウトする。

(略)

# title = soup.find_all('title') この行を↓に置き換え
title = resp.html.find('title')

(略)

# for iframe in soup.find_all("iframe"):
#     if("live_chat_replay" in iframe["src"]):
#         next_url = iframe["src"]
# この3行を↓に置き換え
for iframe in resp.html.find("iframe"):
    if "live_chat_replay" in iframe.attrs["src"]:
        next_url = "".join(["https://www.youtube.com", iframe.attrs["src"]])

(略)


#         for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]:  
# これだと、チャットデータのブロックごとに最初の行が欠落するため、下記のようにする。
         for samp in dics["continuationContents"]["liveChatContinuation"]["actions"]:

以上になります。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/16 01:48

    回答ありがとうございます!!無事データ取得できました!

    seleniumは使ったことはあったのですが、requestsライブラリとの棲み分けがわかっておらず「requestsだけあればよくないか?」と思ってました・・・
    ですが、回答にあった"ヘッドレスブラウザ"という単語から調べてみてrequestsとselenium(requests_html)の違いを学ぶことができました!

    >>具体的には、ブラウザは、最初に獲得したソースからさらにjavascript等のスクリプトや他のサーバから追加のデータを取得し、レンダリングした上でページを表示している場合があります。
    >>requestsライブラリはブラウザではないため、ブラウザの完全なレンダリング機能は持っていません。
    こちらも知らなかったので、助かりました。今後は、ブラウザの"検証"コマンドとあわせて使うならrequestsではなくヘッドレスブラウザを使っていこうと思います。

    回答がひとつひとつ丁寧に書かれていたので順を追って理解できました。ありがとうございます。


    今回の疑問は解消できたのですが、ひとつ質問をしてもよろしいでしょうか?
    ヘッドレスブラウザではなく、requestsを使う場面ってどういう時なのでしょうか?
    「Webページの構成を知るにはブラウザの"検証"コマンドを使用することになる→ヘッドレスブラウザの方が確実」という流れになりrequestsを使用したい場面が思いつかないのですが・・・

    キャンセル

  • 2020/07/16 08:01 編集

    おっしゃる通りスクレイピングのような用途ではヘッドレスブラウザを使ったほうが確実な場面は多いと思います。

    requestsでできることはたいていヘッドレスブラウザでもできるはずです。
    回答したコードも元々あったrequestsは残していますが、すべてrequests_htmlに置き換えても動作すると思います。(一部コード修正は必要かもしれませんが)

    ただヘッドレスブラウザはrequestsに比べるとライブラリのサイズが大きく、メモリ消費量も重いです。
    たとえばyoutube api等のweb apiを通じてデータをやり取りする場合等、わざわざレンダリングを考慮する必要がない場合は、requestsや、python標準のhttpライブラリを使った方が軽いプログラムにできます。

    キャンセル

  • 2020/07/16 12:41

    >>ただヘッドレスブラウザはrequestsに比べるとライブラリのサイズが大きく、メモリ消費量も重いです。
    たとえばyoutube api等のweb apiを通じてデータをやり取りする場合等、わざわざレンダリングを考慮する必要がない場合は、requestsや、python標準のhttpライブラリを使った方が軽いプログラムにできます。
    →具体的な例でイメージできました!確かにAPIを叩く時はレンダリングを考慮しなくてよいですね!
     教えて頂きありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.91%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る