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

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

新規登録して質問してみよう
ただいま回答率
85.35%
YouTube

YouTubeとはユーザーがビデオをアップロード・共有・閲覧できるビデオ共有ウェブサイトです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

6585閲覧

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

mongaa

総合スコア2

YouTube

YouTubeとはユーザーがビデオをアップロード・共有・閲覧できるビデオ共有ウェブサイトです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

2グッド

5クリップ

投稿2020/07/10 20:37

編集2020/09/10 00:40

前提・実現したいこと

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共にエラーメッセージは発生していません。
指定した動画からコメントを取得する流れのどこかで想定通りに動いていないと思われますが、
解決策が分からなくて困っています。

該当のソースコード

Python

1#!/usr/bin/env python3 2from bs4 import BeautifulSoup 3import ast 4import requests 5import re 6import sys 7# Verify user supplied a YouTube URL. 8#if len(sys.argv) == 1: 9# print("Please provide a YouTube URL (e.g. ./YoutubeChatReplayCrawler.py YOUTUBE_VIDEO_URL)") 10# sys.exit(0) 11# Produce a valid filename (from Django text utils). 12def get_valid_filename(s): 13 s = str(s).strip().replace(' ', '_') 14 return re.sub(r'(?u)[^-\w.]', '', s) 15# Set up variables for requests. 16#target_url = sys.argv[1] 17target_url = "https://www.youtube.com/watch?v=mOBvvwCosE4" #←適当に選んだ動画のURL 18dict_str = '' 19next_url = '' 20comment_data = [] 21session = requests.Session() 22headers = {'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'} 23# Get the video page. 24html = session.get(target_url) 25soup = BeautifulSoup(html.text, 'html.parser') 26# Retrieve the title and sanitize so it is a valid filename. 27title = soup.find_all('title') 28title = title[0].text.replace(' - YouTube', '') 29title = get_valid_filename(title) 30# Regex match for emoji. 31RE_EMOJI = re.compile('[\U00010000-\U0010ffff]', flags=re.UNICODE) 32# Find any live_chat_replay elements, get URL for next live chat message. 33for iframe in soup.find_all("iframe"): 34 if("live_chat_replay" in iframe["src"]): 35 next_url = iframe["src"] 36if not next_url: 37 print("Couldn't find live_chat_replay iframe. Maybe try running again?") 38 sys.exit(0) 39# TODO - We should fail fast if next_url is empty, otherwise you get error: 40# Invalid URL '': No schema supplied. Perhaps you meant http://? 41# TODO - This loop is fragile. It loops endlessly when some exceptions are hit. 42while(1): 43 try: 44 html = session.get(next_url, headers=headers) 45 soup = BeautifulSoup(html.text, 'lxml') 46 # Loop through all script tags. 47 for script in soup.find_all('script'): 48 script_text = str(script) 49 if 'ytInitialData' in script_text: 50 dict_str = ''.join(script_text.split(" = ")[1:]) 51 # Capitalize booleans so JSON is valid Python dict. 52 dict_str = dict_str.replace("false", "False") 53 dict_str = dict_str.replace("true", "True") 54 # Strip extra HTML from JSON. 55 dict_str = re.sub(r'};.*\n.+</script>', '}', dict_str) 56 # Correct some characters. 57 dict_str = dict_str.rstrip(" \n;") 58 # TODO: I don't seem to have any issues with emoji in the messages. 59 # dict_str = RE_EMOJI.sub(r'', dict_str) 60 # Evaluate the cleaned up JSON into a python dict. 61 dics = ast.literal_eval(dict_str) 62 # TODO: On the last pass this returns KeyError since there are no more 63 # continuations or actions. Should probably just break in that case. 64 continue_url = dics["continuationContents"]["liveChatContinuation"]["continuations"][0]["liveChatReplayContinuationData"]["continuation"] 65 print('Found another live chat continuation:') 66 print(continue_url) 67 next_url = "https://www.youtube.com/live_chat_replay?continuation=" + continue_url 68 # Extract the data for each live chat comment. 69 for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]: 70 comment_data.append(str(samp) + "\n") 71 # next_urlが入手できなくなったら終わり 72 except requests.ConnectionError: 73 print("Connection Error") 74 continue 75 except requests.HTTPError: 76 print("HTTPError") 77 break 78 except requests.Timeout: 79 print("Timeout") 80 continue 81 except requests.exceptions.RequestException as e: 82 print(e) 83 break 84 except KeyError as e: 85 error = str(e) 86 if 'liveChatReplayContinuationData' in error: 87 print('Hit last live chat segment, finishing job.') 88 else: 89 print("KeyError") 90 print(e) 91 break 92 except SyntaxError as e: 93 print("SyntaxError") 94 print(e) 95 break 96 # continue #TODO 97 except KeyboardInterrupt: 98 break 99 except Exception: 100 print("Unexpected error:" + str(sys.exc_info()[0])) 101# Write the comment data to a file named after the title of the video. 102with open(title + ".json", mode='w', encoding="utf-8") as f: 103 f.writelines(comment_data) 104print('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
glyzinieh, asano0005👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

can110

2020/07/10 21:19

元のコードを使用し適当なURLで試してみましたが、同じメッセージが表示されます。 元コードに問題があるのではないでしょうか?
mongaa

2020/07/11 04:01

そうであれば、このコードを元にしたものや少し改良を加えたもの(※1)で動作させてる人がいるので、人によって結果が変わる理由を知りたいです。そこがわかれば解決に向かう気がするので・・・ ちなみにどちらのコードも今問題だと考えている箇所までの違いはないです(動画IDを手動で持ってくると考えれば) ※1 https://teratail.com/questions/263421 https://note.com/or_ele/n/n5fc139ff3f06
can110

2020/07/11 04:22

ひょっとして取得したい動画にもよるかと。 また、YouTubeサーバも日々アップデートされ、取得されるページ構造も今は変わっている可能性もあります。 まずは現時点で成功する、動画とコードの組み合わせを見つけるところからかと思います。
mongaa

2020/07/11 05:47

午前中ずっと探していましたが見つかりませんでした・・・ 少し離れて考えてみます・・・
mongaa

2020/07/11 17:29

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

2020/07/11 17: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です。 現在わかったのはここまでです・・・・・
guest

回答1

0

ベストアンサー

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

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

や、6番目のコメントの

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

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

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

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

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

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

実行前に

lang

1pip install requests_html

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

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

lang

1#!/usr/bin/env python3 2(略) 3import requests 4import requests_html # <= 追加 5 6(略) 7# session = requests.Session() この行を↓に置き換え 8session = requests_html.HTMLSession() 9 10# soup = session.get(target_url) この行を↓に置き換え 11resp = session.get(target_url) 12 13resp.html.render(sleep=3) # <= 追加。レンダリングを完了させるため3秒待ちます。 14# soup = BeautifulSoup(html.text, 'html.parser')  <= ここは不要なのでコメントアウトする。 15 16(略) 17 18# title = soup.find_all('title') この行を↓に置き換え 19title = resp.html.find('title') 20 21(略) 22 23# for iframe in soup.find_all("iframe"): 24# if("live_chat_replay" in iframe["src"]): 25# next_url = iframe["src"] 26# この3行を↓に置き換え 27for iframe in resp.html.find("iframe"): 28 if "live_chat_replay" in iframe.attrs["src"]: 29 next_url = "".join(["https://www.youtube.com", iframe.attrs["src"]]) 30 31(略) 32 33 34# for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]:   35# これだと、チャットデータのブロックごとに最初の行が欠落するため、下記のようにする。 36 for samp in dics["continuationContents"]["liveChatContinuation"]["actions"]:

以上になります。

投稿2020/07/13 11:36

編集2020/07/13 11:38
patapi

総合スコア820

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mongaa

2020/07/15 16:48

回答ありがとうございます!!無事データ取得できました! seleniumは使ったことはあったのですが、requestsライブラリとの棲み分けがわかっておらず「requestsだけあればよくないか?」と思ってました・・・ ですが、回答にあった"ヘッドレスブラウザ"という単語から調べてみてrequestsとselenium(requests_html)の違いを学ぶことができました! >>具体的には、ブラウザは、最初に獲得したソースからさらにjavascript等のスクリプトや他のサーバから追加のデータを取得し、レンダリングした上でページを表示している場合があります。 >>requestsライブラリはブラウザではないため、ブラウザの完全なレンダリング機能は持っていません。 こちらも知らなかったので、助かりました。今後は、ブラウザの"検証"コマンドとあわせて使うならrequestsではなくヘッドレスブラウザを使っていこうと思います。 回答がひとつひとつ丁寧に書かれていたので順を追って理解できました。ありがとうございます。 今回の疑問は解消できたのですが、ひとつ質問をしてもよろしいでしょうか? ヘッドレスブラウザではなく、requestsを使う場面ってどういう時なのでしょうか? 「Webページの構成を知るにはブラウザの"検証"コマンドを使用することになる→ヘッドレスブラウザの方が確実」という流れになりrequestsを使用したい場面が思いつかないのですが・・・
patapi

2020/07/16 02:43 編集

おっしゃる通りスクレイピングのような用途ではヘッドレスブラウザを使ったほうが確実な場面は多いと思います。 requestsでできることはたいていヘッドレスブラウザでもできるはずです。 回答したコードも元々あったrequestsは残していますが、すべてrequests_htmlに置き換えても動作すると思います。(一部コード修正は必要かもしれませんが) ただヘッドレスブラウザはrequestsに比べるとライブラリのサイズが大きく、メモリ消費量も重いです。 たとえばyoutube api等のweb apiを通じてデータをやり取りする場合等、わざわざレンダリングを考慮する必要がない場合は、requestsや、python標準のhttpライブラリを使った方が軽いプログラムにできます。
mongaa

2020/07/16 03:41

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

2020/10/20 03:21

横からすみません。 私も同じ問題で悩んでいて、こちらの質問と回答に辿り着きました。 おかげさまで、私もコメントの取得に成功しました。 本当にありがとうございます! そこで1つ気になっているのですが、このコードで取得できるのは、上位チャットでしょうか? それとも、すべてのチャットでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問