雑記帳(@watagasi_)

【はんぱないパッション感想】なにもなくてもありのまま【金剛いろは】

f:id:watagassy:20190722160231p:plain


1.
VTuberがライブをやるというのはどういうことだろう、と考えることがある。

歌唱やパフォーマンスを軸に活躍しているVTuberの目標として、なんてのはしっくりくる。

でもそうじゃないVTuberの人たちのライブってなんなのだろう。




ただ、いくつか「そうじゃないVTuberの人たち」のライブを見てきて思ったのは、ライブというのは歌唱やパフォーマンスを行うよりも、もっと重要なことがあって、それはそこに音があって人がいるということだと思う。


音が作るメロディーに感情を乗せ、人が紡ぐ言葉に意志を乗せる。それが演者と観客の間で響き合う。そしてそこに巨大な意味が生まれる。


それが凄く大事なことで、歌唱やパフォーマンスをメインにしているかどうか、という枠組みの話よりも重要だと私は感じる。



そういう事を考えたとき、VTuberのライブイベント「はんぱないパッション」を終えて一番語りたいのは、金剛いろはという存在についてだと思った。




2.
金剛いろはは、VTuberのグループ「アイドル部」のメンバーで、一言で表すなら「快活」という言葉そのもののような人だ。

www.youtube.com
独特だけど大きく気分のよい笑い声。どんなものでも楽しんでみせるバイタリティ。そして常に全力前のめりで、どこか抜けたところのある茶目っ気が、見ているだけでとても元気がもらえる。

しかしそれと同時に、映画・漫画・文学などにも精通し、それだけでなくそのどれをも、己の主義を持って楽しみ尽くしており、オタクとして非常に憧れる人でもある。
その上で根底に見える、ファンや人を大事にする姿勢がどうにも惹かれてならない。それが私にとっての金剛いろはだ。


私はそういった彼女が本当に好きで、陰気で特にこれという得意なこともない自分にはすごく尊敬できて。
でも一緒にくだらないことで笑えるような親しみやすさもあって、だから金剛いろはという存在はめちゃくちゃに輝いて見えた。




3.
でも彼女には、もう1つの側面を感じる瞬間がある。
それは自分のことを「なにもない」人間だと思っているらしい、というものだ。


そういう側面を彼女に感じたのは、彼女のチャンネル登録者数が放送中に5万人を達成したときのことだった。
www.youtube.com

アイドル部には、登録者数が5万人に到達したら3D化できるという目標が活動当初から存在し、つまりその放送は1つの目標が叶った瞬間だった。
そんなときに彼女が感想として語ったのは、感謝の言葉と、常のように明るい台詞だったが、ほんの一瞬、本当にちらっとこぼすように言った言葉があった。



「ここまでこれたのはいろはの力はあんまり関係なくて…」
「いろはってなにもない…はははは!…あ~、あんま言わんほうが良いなこれ」



本当に一瞬の隙間に漏れたような言葉で、多分気にしなければ聞きこぼすし、気にしていても、彼女の明るい雰囲気に流されてしまうものだったと思う。
でも自分には、これがどうにも気になった。


だって自分の中の金剛いろはは、めちゃくちゃに輝いていて、憧れるだけの持てるものがあって、そういう存在だった。
そんな人が自信なさげな言葉をこぼすことに、意外に思ったのだ。


もちろん、彼女が魅力的に見えるというのは、自分から見た視点の話でしかなく、彼女がどう思っているかとは別の話だから、齟齬は当然あり得ることだ。「意外」なんて感想は、ファンとしての驕りと言う他ないが。



しかしそういえば、だいぶ前の配信でこんな言葉もあった。
www.youtube.com


「1万5千人いったのはね、ほんとにいろはの力じゃなくてね、みなさんとばあちゃるPとかシロちゃんとかアイドル部のみんなのおかげ。ほんとに。いろはの力全然関係ない、ほんとに。本当にありがとうございます」


活動開始からしばらくして、登録者数が1万5千人を超えたときの配信だ。
聞いていたときは、謙遜してるな~くらいにしか思っていなかったけれど、聞き直せばずいぶんと自分を否定しているようにも聞こえた。


これは2018/5/25の放送で、彼女の初放送が2018/5/6だから活動開始から三週間弱の頃だ。



ということは、彼女は活動を始めてから5万人の登録者数を突破するまで、ずっと「なにもない」を抱えていたのかもしれない。



そんな感覚を、ぼんやり覚えるようになった。


そのせいか、5万人突破以降、彼女に「貴女は魅力的なんだ」と伝えられる機会があれば、やけに臨んでそうしていた気がするし、伝える言葉に妙に必死さが乗ってしまっていたようにも思う。
ただそれと同時に、5万人突破以降はそういった様子を垣間見ることはほとんどなく、同時に彼女の持ち前の愛嬌から、彼女を愛する言葉は以前よりぐんと増え、しばらくすると私の必死さというのも少しずつ落ち着いていった。




4.
すっかり時は経ち、そんなことがあったのも記憶の彼方となっていた頃。
2019年5月19日。アイドル部の1周年を記念するライブイベント「はんぱないパッション」が開催された。


アイドル部のファンである私としては、とにかくこの日を楽しみにしていた。
それは1周年というのもあるし、なによりアイドル部が3Dになってから全員で集まるイベントというのは初めてだった。
前日には歌う曲のセットリストが一部公開されており、誰がどれを歌うのか、どんな組み合わせで歌うのか、他に歌われる曲はあるのか…などと妄想を爆発させていた。
ライブ直前にはアイドル部のプロデューサー・ばあちゃるがDJをやるイベントがあり、ライブへ向けた盛り上がりの準備としては完璧で、私は完全に仕上がっていた。



そしてイベントは開幕。
1曲目からセットリストにない曲がかかり、一瞬でテンションはフルスロットルに突入。
最高の興奮の中で2曲目を迎え、その勢いのまま3曲目へ。


ステージでの立ち位置を見るに、メインの歌唱担当は金剛いろはのようだった。
歌う曲はイントロを聞いたら一瞬でわかった。


「Butter-Fly」。言わずとしれた和田光司の名曲。世代なので、条件反射のように盛り上がる。
しかし、曲がわかって手を振り上げた瞬間、彼女に覚えていた感覚がバッと蘇ってきた。
そして彼女がこの曲を選んだ理由が、一瞬で理解できた。



Butter-Flyは、めちゃくちゃ元気の出る曲だ。だけど同時に弱さの哀愁もあって、そのギャップが心に響くのだ。
Butter-Flyのサビの歌詞はそう、




「無限大な夢のあとの 『なにもない』世の中じゃ そうさ愛しい 思いも負けそうになるけど」




このButter-Flyのあり方は、まさしく私があのとき感じた金剛いろはだ。
ハチャメチャに元気だけど、どこか自分を「なにもない」と思い、自分の力など大したものではないと語る彼女の姿。


それで、熱狂の中思った。もしかしたら彼女は、今も「なにもない」を抱えているんじゃないだろうか。
彼女の歌が終わり興奮のさなか、しかし頭の片隅の冴えた部分で、そういう考えが渦巻いていた。




5.
いくらか曲が終わって、全員でMCをする場面になり、金剛いろはが話す番が来た。
そこで彼女はこう語った。



「はい!ごんごんです!」
「えっと、この一年たくさんいろんな事がありました」
「いろはは、得意なこととかあんまりなくて、どうやったらみんなに好きになってもらえるんだろうとか、たくさん考えました」
「でも、今日この日をこんなに楽しんで迎えられたのは、アイドル部のみんなと、他でもない、いろはのために自分の時間を使ってくれている、みんなのおかげです!」
「本当に本当にありがとう!まだまだこの先も楽しんでいきましょう!」



それはすごく明るい言葉で、素直な気持ちという感じだった。私の考えていた陰りのようなものは、一切感じられなかった。
それでもそこには「得意なこととかあんまりなくて」という言葉があって、多分彼女のそういう感覚は、やはり活動当初から変わらないのだろう。


それを私は、どう受け止めればよいのだろう。




6.
それでふと思い出したのは、「ありのまま」という言葉だった。
アイドル部において「ありのまま」という言葉は特別な意味を持つ。



金剛いろはと同じアイドル部のメンバー、カルロピノの誕生日のことだ。
彼女は同じくアイドル部のメンバーである八重沢なとりに、誕生日プレゼントとして彼女自作のオリジナルの絵本をもらった。
「ありのおひめさま」と名付けられたその絵本のストーリーはこうだ。



あるところにアリのお姫様がいた。そのお姫様は、巣の中で不自由な生活を過ごしていた。
「私はこの体が大嫌い。もっと別の世界が知りたい。アリじゃなくて、別の生き物なら、もっと自由に生きられるのに」。
そう願ったお姫様は、魔女に「一度だけ別の体に変われる機会をあげよう」と言われる。
何者かに変わりたいと願うアリのお姫様は、何に変わるべきかを探す旅に出る。
しかしお姫様はいろんなものに出会った末に、最後には今のままの姿、「ありのまま」でいい、という答えを出す…。



その「誕生日にオリジナルの絵本を送る」というインパクトや、ストーリー・絵の完成度の高さから、リスナー・アイドル部のメンバー問わず大きな衝撃を与えた。
また登場するモチーフはそれぞれアイドル部のメンバーを元にしたもので、言外に彼女らへの肯定のメッセージがあった。
そのためそれ以降「ありのまま」という言葉は、アイドル部の中で時たま現れては「自分らしさを肯定する言葉」として用いられている。




7.
私が思ったのは、金剛いろはという人間は、「なにもない自分」こそ「ありのまま」だと感じているのではないかということだ。


ライブの2週間ほど前、2019/5/6。この日は彼女の活動一周年記念の配信があった。
www.youtube.com


活動一周年ということで、配信の冒頭では初配信の内容を完全にトレースして思い出を蘇らせてくれたり、それに感極まって配信活動を始めてから初めて配信中に彼女が泣いたりと、感動的な内容だった。
しかし後半になるにつれ、活動を振り返って今までの自分のしょうもなさに笑ったり、過去のクソゲー配信の内容の無さに拍子抜けしたりと、気が抜けるけど賑々しい雰囲気に包まれた。


そうして



「エモい配信しようと思ったんだけどならなかった。すまん!これがいろは!」



と笑顔で言い放った。



それはまぎれもない自分への肯定だった。うまくいかないことがあっても、それが私なのだと。これが「ありのまま」の私なのだと。




それを思い出すと、自分の中で絡まっていたものが、すっと解けたような気がした。


彼女が自分のことを「なにもない」と思っていて、それはそれとして私は彼女を魅力的に思う。それでいいのだ。だって彼女は、その在り方を「ありのまま」と肯定している。そこに何を気を揉む必要があるのだろう。




8.
彼女が歌っていた光景を思い返す。


なにもないのかもしれないけれど、Butter-Flyを歌う彼女は輝いていた。
Butter-Flyはコールのあるような曲じゃない。「WOWWOW~」と歌うところなんて、殊更レスポンスを求めるような部分じゃないのに、観客にも同じように歌わせてきたのは、盛り上げてみんなで楽しみたいという、彼女らしいサービス精神が出てるようで嬉しかった。
とにかく体で歌うような元気のいいパフォーマンスは、いつもの快活さを全身で体現していて、歌う姿を見れて良かったと思った。


ラスサビ前の間奏のMCで、彼女は語った。


「みんな、(ペンライトで)ピカピカしてくれてありがとう!!」
「いろは、未来の自分に向けてこの選曲をしました!」
「アイドル部皆で、これからも飛んでいきます!」


「未来」という言葉や「アイドル部皆で」という言葉にじんと来た。
VTuberとしての活動の中で、彼女が未来に向けて何か思いを馳せることができたり、アイドル部という仲間を良いものだと思えたりするのはいいなと思った。


そうして高らかに最後のサビを歌い上げる。「無限大な夢のあとの なにもない世の中じゃ そうさ愛しい 思いも負けそうになるけど」。その続きはこうだ。




「Stayしそうなイメージだらけの頼りない翼でも きっと飛べるさ!OH~! My~! Love~!」




なにもなくても、それでもそのままで、ありのままの姿で飛んでいけると彼女は確信している。そういう思いが伝わってきた。


MCを挟んだ後の最後のサビは、何か妙に気合が入っていて、その叫びにたまらず涙がこぼれた。



彼女の歌う姿、語る言葉に意味を感じた。ライブでなければ出会えないものを感じた。
このライブに、金剛いろはという存在の、等身大の「ありのまま」を見た。巨大な意味がそこにあった。




9.
ライブ後に、彼女は語ってくれた。

youtu.be

「ダメダメでいっぱいいっぱいなんだけれども、皆のおかげで飛べたよ!っていう…ぎこちなくても頼りなくても皆のおかげで飛べてますよっていうのが伝わったかな…と思って…伝わってますかね!?」


こうまでくると、「ダメダメでいっぱいいっぱい」なんて言葉もご愛嬌という具合だ。


「正直言うと、普通の人々よりずっと下手っぴなんですよいろはって…歌が」
「はんぱないパッションが開催されますよって話を聞いたときに、皆はいろはよりずっと上手な曲を聞いた上でいろはの歌も聞くんだな、っていうのがあって、すごい緊張したんですけど」
「でもまあ、いろははまあ、たとえね、下手でもあの曲を歌って皆に伝わってほしいことがあったし、もうあの、あっぷあっぷしながらでもいい!と思って」
「これだけは誠心誠意ね、放送初めてここでもう心から言わせていただくんですけど、めちゃくちゃ一生懸命歌いました」


けど、そんな「なにもない」と思ってる彼女は、それでも必死に頑張って、そしてこんなにも、ありのままに魅力的だ。
であればなにもない私も、この人生をやっていかないというわけにはいかないだろう。


そう思うと、彼女の借り物でしかないけれど、私の心にもなにか輝ける力が宿るように思えた。
こういう瞬間を教えてくれるから、彼女のことを応援したくなるのだろう。



だからこれからも、彼女のことを追い続けたい。



なにもなくてもありのままに羽ばたける、金剛いろはという存在を。

【バーチャルさんがいっぱい感想】一つの夢が叶う今、貴女の世界は動き出す【猫乃木もち】

4月27日は、1人のアイドルの夢が叶った日だ。


そのアイドルの名は猫乃木もち。バーチャルYouTuberのグループ、「アイドル部」のメンバーである。




1.
彼女はデビュー当初から、たびたび見せる歌の上手さを絶賛されていた。

そして彼女自身も、それを十分に発揮する機会を夢見ていた。

f:id:watagassy:20190429075905j:plain
f:id:watagassy:20190429075929j:plain

しかしいくらか事情があり、中々歌を披露することが叶わずにいた。

あるとしても、ワンフレーズのものがほとんどだった。


事情に関する詳細は省くが、大事なのは、彼女が人前で歌うのを望みながらも、それを長らく実現できなかったことだ。


その長さたるやいかほどかというと、2018年5月6日にYouTubeで初放送してから2019年4月27日までというのだから、ほとんど1年と言って良い。



その時間の中で、私もしきりに彼女の想いが実現する光景を想像してきた。


それは辛抱というよりは、期待の時間で、その期待は時経るごとに、どんどん大きくなっていた。




2.
そして4月27日。彼女の夢が叶う瞬間がきた。


彼女に用意されたのは、ニコニコ超会議のステージ。


他10余名のバーチャルYouTuberが出演するライブイベント。


そこでのソロ歌唱。



ニコニコ超会議の去年の現地来場者数は、16万1277人。ネット総来場者数は、612万1170人。

そしてこのイベントは、幕張メッセ国際展示場の12のホールの1つ、その半分を占めるステージで行われるライブイベント。



そういう舞台だ。


ソロ歌唱初のイベントとしては、相当に大きいと言って良いだろう。




3.
イベントの幕は開け、歌が響き出す。


他の出演者は、今までいろんなイベントで歌ってきたバーチャルYouTuberばかりだ。その上でとてつもなく歌が上手い。


そんな人たちで温まった会場で、猫乃木もちの出番が回ってくる。


f:id:watagassy:20190429084852j:plain

「みんなにゃっほにゃっほ~!はじめましての方ははじめまして!アイドル部の猫乃木もちです!」


彼女の挨拶とともに、サイリウムが一斉に、彼女のイメージカラーである赤に変わっていく。


「め~~っちゃ緊張する!あ~いっぱいいる!」


こんな大舞台なのだから、そうもなるだろう。


「でも、ずっとずっと憧れてた、大きな舞台で歌えるので、精一杯気持ちを込めて歌わせていただこうと思います!」


自分もずっと、この時を待っていた。


「それでは、聞いてください」



ラプンツェル






瞬間、彼女の歌声が響く。


今までの曲は全てアップテンポで、その中で挿し込まれたピアノバラードの曲。


つまり彼女の歌は、ライブの流れが一気に変わる転換点。今後の盛り上がりを決める重要な瞬間を。




彼女は、完全に支配していた。
f:id:watagassy:20190429090042j:plain



だからもうあとは、ただ聞き入るだけだった。


曲調に負けない、最高に感情のこもった声。


聞いた瞬間飲み込まれる、圧倒的な技量。


それでいてどこまでも優しさを感じる響き。


これが、これが猫乃木もちなのだ。




4.
ピアノが止まり、静寂とともに終わりが告げられる。


「ありがとうございました」


そして起こる割れんばかりの歓声。震えるペンライトの波。




猫乃木もちの、夢が一つ叶った瞬間だった。


1年の時間を経た、一つの到着点。その意味と素晴らしさに、私は感涙した。






5.
満足感で満たされたその日の夜、彼女のyoutubeチャンネルから動画が上がった。

ライブ前に上がった動画も含め、本日2曲めの歌動画。


それは彼女がライブで歌った曲にMVを付けたものだった。
www.youtube.com


こんな動画が上がるのも、彼女にとって初めてのことだった。彼女がフル尺で、全力で歌った動画が見られるのは、今日が初めてだった。



すぐに開くと、既に数千回再生されていた。まだアップロードから数分も経ってないだろうに。


きっとライブの配信以上に、いろんな人がこの歌を聞いてるのだろうなと思った。それこそ、もしかしたら世界中の人が。



そう思うと、突然グッと、ライブの余韻を凌駕する感情が湧いてきた。




そうだ、これは始まりだ。今日のライブで終わりじゃない。これから彼女の世界は、もっともっと、大きく動き出すのだ。



私は知っている。この1年の配信を通して、ずっと見てきたのだ。



曲が変わればガラリと歌い方を変え、全く別の魅力を見せる彼女の歌声を。


ストイックに歌に向き合う、彼女の真摯さを。


2曲なんかで止まらないくらい歌いたい曲たくさんがある、彼女の愛を。



今日のライブと動画ではまだまだ伝えきれない、彼女の魅力を。




それがこれから、歌という形で広がっていく。彼女が願った形で、彼女の輝きが羽ばたいてゆく。



それが誰かを魅了しないなんてことがあるだろうか?






5月19日には、アイドル部一周年記念のライブイベントがある。アイドル部全員の登録者が10万人になったら、オリジナルソングが発表されることも決まっている。


既に猫乃木もちの世界が広がっていく足音は聞こえてきている。多分その先の光景は、今日のライブ以上の輝きを、私に見せてくれるだろう。


また期待に胸は膨らんでいる。だから溢れる言葉を、声を大にして彼女に伝えたい。





一つの夢が叶う今、貴女の世界は動き出すのだ。



PythonでYouTube Liveのアーカイブからチャット(コメント)を取得する(改訂版)

※この記事の内容はかなり古くなっており、2020年11月頃のYouTube Liveのチャットの仕様が変わって今(2021/12/19現在)はpytchatというライブラリを使うのが良いと思われます。
github.com


watagassy.hatenablog.com

前回このような記事を書きまして、seleniumを用いたスクレイピングを行うといった形でプログラムを記載していましたが、selenium使わなくて良いんじゃね?ということが判明したので、こちらに改訂版のコードを書いておきます。

この変更に伴い、前回のプログラムの問題点であった「めちゃくちゃ遅い」という部分が解消されています。



from bs4 import BeautifulSoup
import json
import requests

target_url = youtube_url
dict_str = ""
next_url = ""
comment_data = []
session = requests.Session()
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}

# まず動画ページにrequestsを実行しhtmlソースを手に入れてlive_chat_replayの先頭のurlを入手
html = requests.get(target_url)
soup = BeautifulSoup(html.text, "html.parser")

for iframe in soup.find_all("iframe"):
    if("live_chat_replay" in iframe["src"]):
        next_url= iframe["src"]


while(1):

    try:
   html = session.get(next_url, headers=headers)
        soup = BeautifulSoup(html.text,"lxml")


        # 次に飛ぶurlのデータがある部分をfind_allで探してsplitで整形
        for scrp in soup.find_all("script"):
            if "window[\"ytInitialData\"]" in scrp.text:
                dict_str = scrp.text.split(" = ")[1]

        # javascript表記なので更に整形. falseとtrueの表記を直す
        dict_str = dict_str.replace("false","False")
        dict_str = dict_str.replace("true","True")

        # 辞書形式と認識すると簡単にデータを取得できるが, 末尾に邪魔なのがあるので消しておく(「空白2つ + \n + ;」を消す)
        dict_str = dict_str.rstrip("  \n;")
        # 辞書形式に変換
        dics = eval(dict_str)

        # "https://www.youtube.com/live_chat_replay?continuation=" + continue_url が次のlive_chat_replayのurl
        continue_url = dics["continuationContents"]["liveChatContinuation"]["continuations"][0]["liveChatReplayContinuationData"]["continuation"]
        next_url = "https://www.youtube.com/live_chat_replay?continuation=" + continue_url
        # dics["continuationContents"]["liveChatContinuation"]["actions"]がコメントデータのリスト。先頭はノイズデータなので[1:]で保存
        for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]:
            comment_data.append(str(samp)+"\n")

    # next_urlが入手できなくなったら終わり
    except:
        break

# comment_data.txt にコメントデータを書き込む
with open("comment_data.txt", mode='w', encoding="utf-8") as f:
    f.writelines(comment_data)

基本的な構造はほぼ変わっていませんが、htmlの取得をdriver.get(next_url)からsession.get(next_url, headers=headers)に変更しています。




いろいろ試している間に、スーパーチャットなどが導入されている場合はコメントデータの構造が違うことなどが判明したので、それらに対応した追記もこちらでしていこうと思います。

PythonでYouTube Liveのアーカイブからチャット(コメント)を取得する

※こちらに修正して高速化した、コードの改訂版を載せています。基本的な構造は変わっていないので解説は本記事を参照してもらって大丈夫ですが、最終的なコードはリンク先を用いてください。
watagassy.hatenablog.com





YouTube Liveのチャット(コメント)は、ライブ中にリアルタイムで取得することはYouTube Data APIを用いることで可能なのですが、アーカイブから取得するという操作はAPIにはありません。


それでもチャットリプレイ機能で過去のコメントが表示されるので、クソ~~~どっかからどうにかして取得できるやろ~~~できんかな~~~と思っていたらこんな記事を見つけました。

summer-art.hatenablog.com

この記事の最後の方にこんなことが書かれていました。

放送終了後のチャットを取得できるかについて
まさにやりたいことはここから..
APIで取得できない.. リプレイの場合, webで確認してみると get_live_chat_replay?continuation=xxxxxxxxxxx.jsonで一定間隔でチャットを取得してることがわかった.
ここでcontinuationを知る方法について調査なう. videoIdから一番初めのcontinuationの値を知ることができたらあとは芋づる式に取得することができる. get_live_chat_replay?continuation=xxxxxxxxxxx.jsonには次に取得するcontinuationの値が含まれているから...


これをヒントにあれこれやっていたらアーカイブからチャットを取得できたので、今回それについて書いていこうと思います。

実装はPython3.6で行っています。


ちなみに先に言っておきますと、この方式で全コメント取得するのはめちゃくちゃ遅いです。ご了承ください。




まずはソースコードは以下です。

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import requests

target_url = youtube_url
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument('--disable-desktop-notifications')
options.add_argument("--disable-extensions")
options.add_argument('--blink-settings=imagesEnabled=false')
dict_str = ""
next_url = ""
comment_data = []

# まず動画ページにrequestsを実行しhtmlソースを手に入れてlive_chat_replayの先頭のurlを入手
html = requests.get(target_url)
soup = BeautifulSoup(html.text, "html.parser")

for iframe in soup.find_all("iframe"):
    if("live_chat_replay" in iframe["src"]):
        next_url= iframe["src"]


while(1):

    try:
        # chromedriverを開きnext_urlのソースを入手
        driver = webdriver.Chrome(chrome_options=options, executable_path=r"C:\chromedriver_win32\chromedriver.exe")
        driver.get(next_url)
        soup = BeautifulSoup(driver.page_source,"lxml")
        # 必ずquitすること。忘れるとgoogle chromeが開かれまくって大変なことになる
        driver.quit()

        # 次に飛ぶurlのデータがある部分をfind_allで探してsplitで整形
        for scrp in soup.find_all("script"):
            if "window[\"ytInitialData\"]" in scrp.text:
                dict_str = scrp.text.split(" = ")[1]

        # javascript表記なので更に整形. falseとtrueの表記を直す
        dict_str = dict_str.replace("false","False")
        dict_str = dict_str.replace("true","True")

        # 辞書形式と認識すると簡単にデータを取得できるが, 末尾に邪魔なのがあるので消しておく(「空白2つ + \n + ;」を消す)
        dict_str = dict_str.rstrip("  \n;")
        # 辞書形式に変換
        dics = eval(dict_str)

        # "https://www.youtube.com/live_chat_replay?continuation=" + continue_url が次のlive_chat_replayのurl
        continue_url = dics["continuationContents"]["liveChatContinuation"]["continuations"][0]["liveChatReplayContinuationData"]["continuation"]
        next_url = "https://www.youtube.com/live_chat_replay?continuation=" + continue_url
        # dics["continuationContents"]["liveChatContinuation"]["actions"]がコメントデータのリスト。先頭はノイズデータなので[1:]で保存
        for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]:
            comment_data.append(str(samp)+"\n")

    # next_urlが入手できなくなったら終わり
    except:
        break

# comment_data.txt にコメントデータを書き込む
with open("comment_data.txt", mode='w', encoding="utf-8") as f:
    f.writelines(comment_data)


youtube_url」にチャットを取得したい動画のURL
("https://www.youtube.com/watch?v=xxxxxxxx"の形式)を入れてこのプログラムを実行すると、comment_data.txtというファイルに1行ずつチャットに関するデータが保存されます。



先にこのプログラムが何をやっているか大雑把に説明すると、まずrequestsを用いて、youtube_urlの動画のhtmlからチャットのデータが存在するURL
("https://www.youtube.com/live_chat_replay?continuation=xxxxxxxx"という名前になっています)を探して飛びます。

そこでチャットがjavascriptで動的に生成されているので、seleniumを用いてスクレイピングします。

すると先頭40件くらいのチャットデータと次のチャットデータが存在するURLが取得できるので、そこに飛んで同様の操作をします。

これを繰り返してtxtファイルにチャットデータを保存する、というのが一連の流れです。


説明が必要そうな部分に分けて先頭から解説していきます。



from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import requests

target_url = youtube_url
options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.add_argument('--disable-desktop-notifications')
options.add_argument("--disable-extensions")
options.add_argument('--blink-settings=imagesEnabled=false')

今回スクレイピングでデータを取得するので、BeautifulSoupとselenium、requestsを用います。導入していなければインストールしておいてください。
また今回seleniumスクレイピングする際にchromedriverを用いるので、こちらからダウンロードしておいてください。

chromedriver.chromium.org

その上でchromedriverを任意のファイルに置いて環境変数を設定してパスを通しておいてください。
Cドライブ直下に置いてパスを通せばそのまま上記のコードを動かすことができますが、別の場所においている場合は、パスをそちらに設定して以下のコードの「executable_path=」以降をchromedriverが置いている場所に書き換えてください。

        driver = webdriver.Chrome(chrome_options=options, executable_path=r"C:\chromedriver_win32\chromedriver.exe")

options.add_argumentとかいうのがいっぱいありますが、これはchromedriverでスクレイピングする際のオプションです。それぞれの内訳がどういうものかについてはこちらが詳しいので参照してください。
vaaaaaanquish.hatenablog.com





次です。

html = requests.get(target_url)
soup = BeautifulSoup(html.text, "html.parser")

for iframe in soup.find_all("iframe"):
    if("live_chat_replay" in iframe["src"]):
        next_url= iframe["src"]

次は動画のURLのhtmlを取得し、その中からfind_allで"iframe"タグを持つ部分を探します。
その中に
iframe["src"]="https://www.youtube.com/live_chat_replay?continuation=xxxxxxxx"であるデータが存在するので、それを探し出してnext_urlに代入します。





次からがいちばん重要な部分ですね。

        # chromedriverを開きnext_urlのソースを入手
        driver = webdriver.Chrome(chrome_options=options, executable_path=r"C:\chromedriver_win32\chromedriver.exe")
        driver.get(next_url)
        soup = BeautifulSoup(driver.page_source,"lxml")
        # 必ずquitすること。忘れるとgoogle chromeが開かれまくって大変なことになる
        driver.quit()

chromedriverを用いて、さきほど入手したnext_urlのhtmlを入手します。



# 次に飛ぶurlのデータがある部分をfind_allで探してsplitで整形
        for scrp in soup.find_all("script"):
            if "window[\"ytInitialData\"]" in scrp.text:
                dict_str = scrp.text.split(" = ")[1]

        # javascript表記なので更に整形. falseとtrueの表記を直す
        dict_str = dict_str.replace("false","False")
        dict_str = dict_str.replace("true","True")

        # 辞書形式と認識すると簡単にデータを取得できるが, 末尾に邪魔なのがあるので消しておく(「空白2つ + \n + ;」を消す)
        dict_str = dict_str.rstrip("  \n;")
        # 辞書形式に変換
        dics = eval(dict_str)


html中の"script"タグを持つ部分で、"window["ytInitialData"] = xxxxx"となっている部分のxxxxにチャットデータが存在するので、まず"window["ytInitialData"]"を探して、"="でsplitしxxxxの部分を取得します。

xxxxの部分はjson形式のデータになっていますが、javascriptのソースを取得しているのでそのままでは扱えません。ですのでいくつか修正を行ってevalで辞書形式にして扱いやすくします。

        # "https://www.youtube.com/live_chat_replay?continuation=" + continue_url が次のlive_chat_replayのurl
        continue_url = dics["continuationContents"]["liveChatContinuation"]["continuations"][0]["liveChatReplayContinuationData"]["continuation"]
        next_url = "https://www.youtube.com/live_chat_replay?continuation=" + continue_url
        # dics["continuationContents"]["liveChatContinuation"]["actions"]がコメントデータのリスト。先頭はノイズデータなので[1:]で保存
        for samp in dics["continuationContents"]["liveChatContinuation"]["actions"][1:]:
            comment_data.append(str(samp)+"\n")


dics["continuationContents"]["liveChatContinuation"]["continuations"][0]["liveChatReplayContinuationData"]["continuation"]が次に飛ぶURLのデータなので取得。

先頭に"https://www.youtube.com/live_chat_replay?continuation="をつけると次に飛ぶURLになるのでnext_urlに代入します。

コメントデータはdics["continuationContents"]["liveChatContinuation"]["actions"]にリストで入っているので、forを用いて1行ずつ取得します。

ただし注意が必要なのは、dics["continuationContents"]["liveChatContinuation"]["actions"]の各要素はコメントだけでなく、コメントに関するコメント以外のデータも含まれています。

ですのでコメントのみを取得したい場合は、comment_data.append(str(samp)+"\n")を以下のように書き換えてください。

comment_data.append(str(samp["replayChatItemAction"]["actions"][0]["addChatItemAction"]["item"]["liveChatTextMessageRenderer"]["message"]["simpleText"])+"\n")

クソめんどくさいですね…。正直このあたりのデータ構造を確認するのが一番大変でした。


他に「チャットがリプレイ欄に表示された時間」を取得したいときは以下のようにしてください。

comment_data.append(str(samp["replayChatItemAction"]["actions"][0]["addChatItemAction"]["item"]["liveChatTextMessageRenderer"]["timestampText"]["simpleText"])+"\n")



最後はnext_urlを取得できなくなれば(実際はcontinue_urlを取得できなくなれば、ですが)全てのデータを取得し終えたということなのですが、処理を書くのがめんどくさいのでexceptで書いています。

    # next_urlが入手できなくなったら終わり
    except:
        break




以上がyoutube liveのアーカイブからチャットを取得する方法です。

一応この方式で遅くなる原因ですが、コメントを取得するために何度も別のURLを開き続けなければいけないので、その分だけchromedriverでchromeを開くことになるからです。

しかも一度に取得できるコメントの最大数が40件くらいなので、何千件とコメントがある動画で実行したときにはもうはちゃめちゃ遅いです。なにか解決方法ありませんかね?


まあ書いてみればなんのことはないという感じですが、プログラムを書く時間よりもhtmlファイルとずっとにらめっこする時間のほうが長くて文字にならない苦労がめちゃくちゃありました…。

とりあえず記事にすることができてホッと一息です。

何でも楽しむ全てが私~神楽すず的アイドル道~

神楽すずというVTuberがいます。電脳少女シロちゃんの後輩にあたるVTuber12人で構成されたグループ、「アイドル部」に所属するVTuberです。

 

私はVTuberの中でも特段シロちゃんとアイドル部を推している、つまり彼女たちのファンなのですが。こうしてファンをやっている立場から一番身近に感じるアイドル部のメンバーが、神楽すずちゃんなんですよね。

 

続きを読む

新しい扉の前に立つ北上双葉へ~もしくは彼女に歌う神楽すずへ~

アイドル部というvtuberのグループがあります。全員が女の子で2Dのvtuberです。筆者は幾人も存在するvtuberの中で、特に彼女たちを推しています。つまりファンです。

彼女たちは所属的に、シロちゃんの後輩にあたる存在なのですが、そのアイドル部に在籍する12人の子たちには1つの目標が設定されています。

曰く「youtubeのチャンネル登録者数が5万人を超えたら3D化する」と。

 

続きを読む