うしろのこの本ください

なんでもかきます

【一般P向け暇つぶし用】お手持ちのChromeでミリシタのデータを回収してみよう

世の中にはWikiって奴が存在して、その中にはたくさんのゲームデータが詰まっている。非公式だけど。

これ手元に欲しくない?欲しいよね!お手持ちのChromeには超絶強力な開発者ツールが備わってます。しかも誰でも使えて完全無料!いいの?いいよ

情報を回収してみよう

Fantasia というミリシタの 非公式情報ポータルがある。ボーダーBotとかで皆お世話になっているはずだ。

mltd.matsurihi.me


まずはこのサイトから情報を引っ張り出してみよう。プログラムは全部コピペで動くから安心してほしい。では早速、カード一覧のページを開こう。

f:id:apple19940820:20181224093533p:plain

余計な情報がなくていいレイアウト。じゃあそれぞれのカード名とそのカードのページURLをセットで取ってみる。


ChromeWindows なら F12 、Mac なら Cmd + Opt + I でデベロッパーツールと呼ばれる開発者用のウィンドウが開くようになっている。

f:id:apple19940820:20181224093844p:plain

最初に見えるのはそのページの中身だ。この情報を元にしてブラウザは UI を配置する。それぞれが Element とか言われたりするのでこのタブの名称は Elements になっている。


ここでやる事もあるが、まずはどうやって情報を手に入れるかを先に説明しよう。隣の Console タブへ移動。

f:id:apple19940820:20181224094624p:plain

ここでは実際にプログラムを書いて実行することができる。いわゆるエディタ実行環境がセットになっているのが Console だ。ここで Chrome に備わっている機能を使って、各カード情報ページのURLとカード名称を1:1に紐づけてデータとして抽出するのが今回の目標ということにしよう。

手始めに全てのカード名をクリップボードへコピーすることからやってみる。

難しそうに見えるけど、実際はたった1行のコードで出来てしまう。以下をコピーして Console に貼り付けて Enter キーを押してみよう。

copy($$('.card-wrapper .card-name-wrapper').map(e => ({cardName :e.textContent})))

すると undefined と返って来るはずだ。

f:id:apple19940820:20181224100355p:plain


既にクリップボードに結果がコピーされている。

適当な場所に ctrl + v で貼り付けると{ cardName:カード名 } の形式で全てのカード情報が取れている。

[
  {
    "cardName": "りるきゃん 篠宮可憐"
  },
  {
    "cardName": "りるきゃん 野々原茜"
  },
  {
    "cardName": "フローズン・クリスタル 三浦あずさ"
  },
  {
    "cardName": "雪解けブレイクダンス! 舞浜歩"
  },
  {
    "cardName": "一生懸命の音色 中谷育"
  },
  {
    "cardName": "早朝お寝惚け温泉 真壁瑞希"
  },
  {
    "cardName": "ステージ後のひととき 豊川風花"
  },
  {
    "cardName": "ゴシックホワイトドール 七尾百合子"
  },
  {
    "cardName": "ほわほわステップ 高槻やよい"
  },
  {
    "cardName": "幻想の光の中で 二階堂千鶴"
  },
//以下省略
]

あなたは今 JavaScript というプログラミング言語のコードと、 Chrome コマンドライン API を実行して目的の情報を手に入れました!おめでとう!君もプログラマーだね。


何が起こったのかは後に説明するとして、今のままでは情報が不足しているので少し変えてみる。

copy($$('.a-card-select').map(e => ({cardName : e.childNodes[1].childNodes[3].textContent, url : e.href})))


同じように Console に貼り付けて Enter で実行。すると先ほどのリストにそれぞれのページへのリンクが追加されている。

   {
      "cardName": "りるきゃん 篠宮可憐",
      "url": "https://mltd.matsurihi.me/cards/459"
    },
    {
      "cardName": "りるきゃん 野々原茜",
      "url": "https://mltd.matsurihi.me/cards/460"
    },
    {
      "cardName": "フローズン・クリスタル 三浦あずさ",
      "url": "https://mltd.matsurihi.me/cards/456"
    },
    {
      "cardName": "雪解けブレイクダンス! 舞浜歩",
      "url": "https://mltd.matsurihi.me/cards/457"
    },
    {
      "cardName": "一生懸命の音色 中谷育",
      "url": "https://mltd.matsurihi.me/cards/458"
    },
    {
      "cardName": "早朝お寝惚け温泉 真壁瑞希",
      "url": "https://mltd.matsurihi.me/cards/453"
    },
    {
      "cardName": "ステージ後のひととき 豊川風花",
      "url": "https://mltd.matsurihi.me/cards/454"
    },
    {
      "cardName": "ゴシックホワイトドール 七尾百合子",
      "url": "https://mltd.matsurihi.me/cards/455"
    },
    {
      "cardName": "ほわほわステップ 高槻やよい",
      "url": "https://mltd.matsurihi.me/cards/449"
    },
    {
      "cardName": "幻想の光の中で 二階堂千鶴",
      "url": "https://mltd.matsurihi.me/cards/450"
    },
    //以下省略

そこそこ便利なデータになった。プログラマにこれを引き渡せばカード名にリンクを付けてテーブル一覧にするくらいは10秒でやってくれるだろう。

【おまけ】他のサイトでやってみる

※ネタバレ、失敗しました。

もしかしたら別の Wiki を愛用していてそっちでやりたいって人がいるかもしれない。さっきのコードは少し変えるだけでどんな Web サイトでも同じようにデータを抽出できる。 in が合っていれば out が同じものになる仕組みをインターフェースと言う。コンソール API の I はインターフェースの I だ。


ミリシタ wiki でググったらここが出てきた。ので、SSR のページで同じことが出来るか試してみる。

imasml-theater-wiki.gamerch.com

F12 で デベロッパーツールを開いて、 Console に移動。とりあえずさっきのコードをコピペして実行してみる。undefind と返ってきたらとりあえずエラーなしにコードが通った証だ。

ではいざ ctrl + v!

f:id:apple19940820:20181224114424p:plain

どう見ても空。さっき貼り付けたコードを確認してみよう。

copy($$('.a-card-select').map(e => ({cardName : e.childNodes[1].childNodes[3].textContent, url : e.href})))

なんで空だったかっていうと、対象がなかったから。サイトによって作りが違うから当たり前なんだけどね。


じゃあさっき言ってた API ってなんなんだよ!ってなるじゃない?変えなくていいのは実はこの部分だけなんです。

copy($$('サイトによって変える').map(e => ({cardName : e.サイトによって変える, url : e.サイトによって変える})))

「サイトによって変える」の部分はサイトによって変えなければならない。冒頭で触れた Elements の構造が違うからだ。このコードは Elements を順番に辿って行って目的のデータがあればそれを手に入れるというもの。

では Console タブから Elements タブに戻って、このサイトの構造を調べてみよう。作りが良ければアイコンの URL も回収できるかもしれない。


f:id:apple19940820:20181224115131p:plain

見づれぇ。。。何かしらの Wiki ジェネレーターで作ってるはずだし仕方ない。目的の Element へのあたりを付けるために近くまで移動する必要がある。

デベロッパーツールの左上にななめ矢印のアイコンがある、それをクリック。すると実際のページ上の各種 Element (というかDOM)を範囲選択のように触ることができるようになる。


とりあえず、春香のアイコンをクリックしてみる。Elements 側もそれに対応して Element ツリーを開いてくれる。

f:id:apple19940820:20181224115746p:plain

なるほど、このサイトは Fantasia とは違い table タグで整列させているようだ。見れば分かるが。

ここからさっきのコードにいい感じに当てはめる要素を探っていく。まずはこの部分

copy($$('サイトによって変える')

ここには欲しい情報のすべての親になっている Element を指定するのが良い。Element ツリーは階層構造になっているのが見て取れるが、上へ行くほど親となり下へ行くほどその子となる。


つまり、実際に欲しいデータ(子)を持つ親を探せばいいってわけだ。「カード名を持つ子」と「そのページへの URL を持つ子」の親となる要素はどれだろう。

f:id:apple19940820:20181224120332p:plain

table > tbody > tr がそうっぽい。tr は子要素として複数の td を持つ。table の基本的な構造だ。つまりこの SSR 一覧はこういう作りになっている。

table > tbody > tr(SSR1枚の行) > td(画像|名前|アイドル|タイプ|限定) > a(画像、リンク等)


欲しいデータはこれの内 a にあたるので、その親となる td を全て含む tr 'サイトによって変える' に指定する。右クリックから Copy > Copy selector でそこまでのパスをコピーできる。

f:id:apple19940820:20181224121145p:plain


まず単体で実行してみよう。

copy($$('#ui_wikidb_table_913361 > tbody > tr:nth-child(1)'))

f:id:apple19940820:20181224122152p:plain

まあ置いておこう。残りを埋めればいい結果が得られるはず。

.map(e => ({cardName : e.サイトによって変える, url : e.サイトによって変える})))

さてこの部分。そろそろ何を入れればいいのか分かってきたかもしれない。e => の e というのはさっき指定した tr だと思っていい。そこから目的のデータまで辿っていけば良い。

Fantasia の時はこうだった。

e => ({cardName : e.childNodes[1].childNodes[3].textContent, url : e.href

e.ChildNodes というのが子要素を指定する奴で、[1] とかは子が複数ある時にどれを辿っていくか選ぶ奴だ。これを参考にしよう。

そうそう、さっき Copy selector で取った奴は春香の tr 要素だから、それより一個上の tbody から始めないとカード一覧全てを対象にできない。それも踏まえてコードを修正しよう。


どこから始めれば良いのか分かったら、次はどのデータを使うかを調べる。 Fantasia 版で言う .textContent とか .href の部分。これは基本的にどんな Web サイトでも変わらないように出来ている。開発者がオリジナルの属性を作らない限り、同じように分類分けされる。

デベロッパーツールの右側にまだ使っていなかったパネルがある。現在は Styles となっているはず。tbody を選択後、これを Properties タブに変更すると、このようになる。

f:id:apple19940820:20181224123952p:plain

▶tbody をクリックすると tbody が抱えているもの全てが展開される。childrenNodes も持っていることが分かる。


どうすればいい感じに tbody からスタートして各カード情報を得られるだろうか。実はこれを書いているうちに tr の1つ目の子(td)が、欲しい情報を全て持っていることに気が付いてしまった。

カード名は tbody > tr > td > a > img.alt 、ページ URL は tbody > tr > td > a.href 、ついでにサムネURLは tbody > tr > td > a > img.src だ。


調べて得た情報を childNodes を辿ってコードに当てはめてみる。長いから改行する。

copy($$('#ui_wikidb_table_913361 > tbody')
.map(e => ({
    cardName : e.childNodes[0].childNodes[0].childNodes[0].alt, 
    url : e.childNodes[0].childNodes[0].href, 
    img : e.childNodes[0].childNodes[0].childNodes[0].src
})))


説明してなかったけど、.map というのは付けた対象が持っているもの全てを1つずつ取り出して、() の中身を実行するという JavaScript のコードだ。ここは本当のコードを書いている。俗にいう配列操作と言う奴。

これを Console に戻って貼り付けて Enter !

f:id:apple19940820:20181224130637p:plain

普通に間違えた。 tbody 以下の tr.map で取り出していくので、 childNodes[0] はまずい。1つずつ取り出せるようにスタート位置を tbody.childNodes に変えて見よう。

copy($$('#ui_wikidb_table_913361 > tbody.childNodes')
.map(e => ({
    cardName : e.childNodes[0].childNodes[0].alt, 
    url : e.childNodes[0].href, 
    img : e.childNodes[0].childNodes[0].src
})))


こんどこそいけ!

f:id:apple19940820:20181224131600p:plain


f:id:apple19940820:20181224133226p:plain

OK、どうやらこのサイトでは Console から値を取るのは無理みたいだ。多分動的なサイトなんだな。


ムカつくから番外編始めます。

【番外】pandas で table の値をスクレイピングしてくる

お使いのパソコンに python をインストールします。Anaconda 使おう。間違っても 2.x 版を入れないように。

f:id:apple19940820:20181224133557p:plain

この記事が分かりやすかった。

qiita.com

インストールが終わったら Anaconda Prompt を開き、Python の実行準備をする。プログラムはデスクトップに置いておくので、ディレクトリの移動も忘れずに。


適当にファイル名決めて拡張子.pyで保存。できたらこんな感じで pandas から url 指定して table 情報を根こそぎ攫ってくる。

f:id:apple19940820:20181224140759p:plain


Anaconda Prompt 上でこのスクリプトを実行。えい!

f:id:apple19940820:20181224140906p:plain

勝ったな。(うrl取られへん)

おわり

すいませんでした