YouTube Liveのアーカイブからチャットを取得するコードを模写したが上手くいかない @Python
解決済
回答 1
投稿
- 評価 1
- クリップ 3
- VIEW 678
前提・実現したいこと
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&hl=ja&service=youtube&passive=true&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ページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
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"]:
以上になります。
-
回答の評価を上げる
以下のような回答は評価を上げましょう
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.95%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼