扶桑工廠

I sleep all night and I work all day.

遊戯王の全カード名を公式データベースから抜き出す

概要

今回は前の記事その前の記事のあわせ技として、動的に生成されるサイトからスクレイピングします。個人の備忘録でもあります。
少し用があったので遊戯王OCGカードデータベースから遊戯王OCGに存在するカード名称を全部引っこ抜きます。
後ろにコードを掲載しているので、使う際は注意して使ってください。(多少手を加えないと動きませんが)
使うのはいつものPython 3.6とSeleniumです。ドライバーの更新は忘れずに。

手順

大体前の記事と一緒です。
1.開発者ツールを使って、ウェブサイトの構造を確認する
2.探しているものの場所を確認
3.コード書いて回す(time.sleepを忘れない)

よし、簡単ですね。それでは作っていきましょう。

1.ウェブサイトの構造を確認する

最初にrobot.txtを探します。自動化に関するルールが書いてあるので確認する必要があります。
Scrapyとかだと勝手にやってくれますけどね。
今回は見当たらなかったですが、サーバーに負荷をかけすぎないようにこまめにtime.sleepを入れます。

では本題へ。全カードが検索結果にある状態にします。
これは「カード検索」から何も入れずに検索するだけで済みます。
じゃあこれで出したページをTargetURLとして設定すれば一発で行ける!と考えましたが、結果から言えばこれではだめでした。
理由はよくわかりませんが、トップ画面に飛ばされてしまいます。
となると、トップからボタンを操作して進まないといけません。面倒臭い
仕方がないのでボタンの階層を調べます。
f:id:fusowasedr09:20200502183031p:plain
これは1つだけ。相対パス表記で次の行き先が書いてありますが、普通にボタンを押せば良さそうです。

次は検索ボタン。
f:id:fusowasedr09:20200502183242p:plain
検索ボタンも1つだけですが、Javascriptで書かれているのでスクリプト直打ちでも動きます。まあここは別にボタンを押して処理で良いでしょう。
後ろの方を見ると厄介なのがよく分かるので検索条件指定とかやらなくて本当に良かった。

2.探しているものの場所を確認

f:id:fusowasedr09:20200502184211p:plain カード名の入っている場所を検索結果から探します。
開発者ツールで探すと、読み方はbox_card_name書いてあるカード名はcard_status、レベル等はbox_card_spec、カードテキストはbox_card_textに直接書かれています。
読み方とカード名が別れているので注意が必要です。読み方が欲しかったので、box_card_nameを狙います。
ちなみに画像も取ってこれますが、一段掘らないといけないので他よりひと手間入ります。
値が参照できるなら、全部引っ張ってこれるスクレイピング原則に基づいて引っこ抜きましょう。(著作権やrobot.txtは守りましょう)

引っこ抜くところは見つかりましたが、全部取得するには自動的に次のページに進むようにしてやる必要があります。
問題は普通にクリックさせるだけではダメなところでした。下の画像を見るとその理由がわかります。
f:id:fusowasedr09:20200502190112p:plain 「次へ」ボタンがページ番号指定と一緒!しかも要素の区別もできないからボタンを選んで押せない!
いやー今回は詰みですね。お疲れ様でした…


なんてね!あわてないあわてない!
Javascriptで動作しているなら、こっちが合わせてJavascriptを実行してやるまでです。だいぶ力押しになりましたが。

3.コードを書く

前回記事であるWaifuLabsの自動化コードを改造して作ります。
取得したテキストを保存しないといけないのと、Javascriptを実行する部分をつけてやれば動くでしょう。 というわけで作ったのがこちら(なおこれは失敗します)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import os.path
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

Target =  "https://www.db.yugioh-card.com/yugiohdb/"   # 指定URL
Driver_path = "chromedriver.exe"   # chromedriverの場所。windows,プログラムと同じ場所にある場合
Interval = 5 # 遅延時間設定(あまり短くしないこと)

driver = webdriver.Chrome(executable_path=Driver_path)

def main():
    # 本命の操作
    path_w = "YGOlist.txt"
    items = driver.find_elements_by_class_name("box_card_name")
    for item in items:
        name = item.find_element_by_xpath("./span")
        with open(path_w, mode="a") as f:
            f.write(name.text + "\n")
    time.sleep(Interval)
    driver.execute_script("javascript:ChangePage("+ str(i + 1) + ")")
    
    
if __name__ =="__main__":
    driver.get(Target) # URL先に移動
    driver.find_element_by_class_name("cards").click()
    driver.find_element_by_class_name("black_btn").click()
    for i in range(3):
        main()
    driver.quit()

…これでヨシ!ってやって10回ほどトライ&エラーを繰り返しました。
これだとbox_card_nameを取得した際に空のリストが帰ってきてしまいます。
あと、うまく行ってもrange指定が間違ってるので1ページ目が2回取得されます。(これはただのアホ)

ここでググりつつ色々動かしてみると、どうやらページが生成される前に要素を取得しているから空のリストが帰ってきていることがわかりました。
そういや、プログラムは瞬時にアクセスしますがそんなにwebページの実行は早くないですもんね…
これなら、time.sleepを適当な時間とってやれば要素が帰ってくるはずです。
というわけで、完成品がこちら。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import os.path
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

Target =  "https://www.db.yugioh-card.com/yugiohdb/"   # 指定URL
Driver_path = "chromedriver.exe"   # chromedriverの場所。windows,プログラムと同じ場所にある場合
Interval = 5 # 遅延時間設定(あまり短くしないこと)

driver = webdriver.Chrome(executable_path=Driver_path)

def main():
    # 本命の操作
    path_w = "YGOlist.txt"
    time.sleep(Interval)
    items = driver.find_elements_by_class_name("box_card_name")
    for item in items:
        name = item.find_element_by_xpath("./span")
        with open(path_w, mode="a") as f:
            f.write(name.text + "\n")
    time.sleep(Interval)
    driver.execute_script("javascript:ChangePage("+ str(i + 1) + ")")
    
    
if __name__ =="__main__":
    driver.get(Target) # URL先に移動
    driver.find_element_by_class_name("cards").click()
    driver.find_element_by_class_name("black_btn").click()
    for i in range(1,1040):
        main()
    driver.quit()    

これですべて取得できました。
少し苦戦しましたが、概ね今までの応用で突破できました。
動的な生成ページはこの方法でスクレイピングできるので、困っている方は参考にしてください(大したの書けませんが)
連絡はTwitter(@fuso_wasedr09)にお願いします。
それではまた!