どらめも

なんでもないことをかきます

seleniumでスクレイピングする話 ~ページ解析用にseleniumIDEを添えて~

まえがき

突然ですがch○nithmnetって使いにくくないですか??フレンドランキングはアクティブ10人までしか見れないし、プレイ回数ランキングは見れないし、そのくせに有料だし...。機能がないなら作ればいいじゃない!ということでseleniumというchrome自動操作ライブラリの存在を知ったので上の機能を実装してraspberry piに食べさせてslackで情報を送ると自動であれこれしてくれるようにしました。やったね!今回の記事はその時に得たselenium周りについての記事です。

スクレイピングについて

基本的に相手方のサーバに負荷をかけるやり方はDoS攻撃として見なされる可能性もあるし、そもそもマナーとして好ましくないのでpythonだとtime.sleepなどを用いて適宜時間を置くようにしましょう。当ブログの方法でaimeの垢バンとか訴えられたとかあったとしても一切責任は取れませんのでご了承ください。スクレイピングについて、Pythonだと主にrequestsモジュールを扱うやり方とseleniumでブラウザを直接操作して行うやり方の2通りがあります。今回の実装ではログイン処理などが伴うため、それのやりやすいseleniumで実装しました。そのため、今回の記事では後者について説明します。

使ったライブラリとか

使用した言語はPython3です。 今回使うツールは
selenium(chromeの操作)
・BeautifulSoup4(htmlの解析)
GoogleChrome(Chromium)
・seleniumIDE(HPの解析)
です。前の二つはpythonのモジュールとしてダウンロードでき、seleniumIDEはChrome拡張機能としてダウンロードすることができます。一番つまづきやすそうなポイントとしてGoogleChromeを操作する際に別途chromedriverが必要になるのですが、これはChromeのバージョンに合わせたものをダウンロードしてくる必要があります。適当にググってください。
あと、seleniumIDEを使うとデスクトップ上でスクリプトを書かなくても簡単に動作を自動化できるらしいですが、そういう使い方については今回触れていないのでご了承ください。

seleniumを使ってみる

では早速使っていきましょう。まずは

from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.google.com")

でとりあえずchromeGoogle.comを表示してくれます。とても簡単です。これからのブラウザの動作は全てこのdriver.なんちゃらで行なっていきます。
それでは次に実際にホームページを操作してみましょう。今回の例はウェザーニュース(https://weathernews.jp/onebox/)の例を用いて行います。(chunithmnetは毎月お金を払わないと色々見れないので...)
これが多分今あなたの見えているWebページです。

f:id:doradorasuki:20190916170939p:plain
ウェザーニュースHPのスクリーンショット

今回は目標として都市名のところに大阪、と打ち込み、検索ボタンを押すことをまずは目標とします。それでは操作をするためにまずホームページのソースコードをのぞいてみましょう。ホームページのソースコードの見方はわかりますか?今回GoogleChromeを例にとって説明すると、右上のグーグルアカウントのアイコンの右にある設定を開くボタンをクリックすると、
f:id:doradorasuki:20190916171649p:plain
Chromeの設定画面のスクリーンショット

このように表示できるので、その他のツールからデベロッパーツールを選びます。(macだとcommand+alt+iでもひらけます)
そして、sourceからweathernews.jp/onebox/(index) みたいなファイルをクリックするとページのhtmlソースが見れると思います。それでは文章の部分のbodyを見てみましょう。seleniumでは基本的にここに書かれいたりする情報を指定してクリックや入力を行います。(html周りについては筆者もよく知らないのでググるなりなんなりしてください)

 <header id="header">
        <section class="header__bar">
            <figure class="wni-logo"><a href="https://weathernews.jp/"><img src="https://smtgvs.weathernews.jp/onebox/img/logo-wni.svg" alt="ウェザーニュース"></a></figure>
        </section>
        <section class="global-search">
            <form onsubmit="search_city1();return false;" class="search-box">
                <input type="text" id="search_city_input1" placeholder="都市名,郵便番号,市外局番など" class="form-txt">
                <button type="submit" class="form-submit">検索</button>
            </form>
        </section>
    </header>

適当に今回の目標的に必要そうな日本語が並んでいるところをコピペしてきました。ここら辺は頑張って見つけてください。(それかのちに説明する方法を使いましょう)
まず、都市名のところに大阪、と打ち込みたいのですが

<input type="text" id="search_city_input1" placeholder="都市名,郵便番号,市外局番など" class="form-txt">

こういうものが日本語的にhtmlから読み取れると思います。ここの情報を使っていきます。今回はこのidの情報を使ってみましょう。(要素の指定方法は複数あるので、うまくいかない場合は他の方法を試してみたりします。)

place_form=driver.find_element_by_id("search_city_input1")
place_form.send_keys("大阪")

idを用いて記述する場合にはfind_element_by_idメソッドを用います。文字の入力には基本的にsend_keysメソッドを用います。簡略化して

driver.find_element_by_id("search_city_input1").send_keys("大阪")

と書くこともできます。find_element_by_なんちゃらには色々あってclass_nameやxpathなど必要に応じて使い分けることが可能です。
そして、次にクリックですが、

<button type="submit" class="form-submit">検索</button>

このように記述があるので次はクラスを指定してクリック操作を行いたいと思います。すると、

place=driver.find_element_by_class_name("form-submit")
place.click()

こんな風になります。簡単ですね。seleniumの基本動作はこれが全てです。プログラミングというよりいかに正しく操作できるタグを早く見つけるかです。のちのtipsで紹介しますが、プラウザバック操作などもメソッド一つで簡単に行えます。
(xpathとは
XPath (XML Path Language)とは、XML形式の文書から、特定の部分を指定して抽出するための簡潔な構文(言語)です。 HTML形式の文書にも対応します。 CSSではセレクタを使ってHTML文書内の特定の部分を抽出しますが、XPathはより簡潔かつ柔軟に指定ができるとされています。
Google先生に検索かけたら出てきました参考程度に)

seleniumIDE利用の勧め

前の項で感じたと思いますが、seleniumの操作はとても簡単です。それでは何が大変なのか。そうhtmlをよんで要素を指定するところです。ウェザーニュースのページは比較的操作しやすくhtmlも綺麗ですが、ページによっては動作がjavascriptで書いてあったり、別のソースファイルに書いてあったりとそもそも発見が困難な場合があります。(筆者もこれになって一時期諦めました)そこで登場するのがseleniumIDEです。本来の使い方とは異なるかもしれませんが、非常に有用なツールです。なんとホームページのクリックしたところに働いているcssxpathを簡単に知ることができます!!前の項にhtmlを読もうとか書きましたが、これを使えば読む必要がないです!!(ちゃんとしたseleniumIDEの使い方すればそもそもpython書かなくていい説がありますが、今回はslack_botに移植が最終目標だったためpythonで書きました)それでは実際に使っていきましょう。
seleniumIDEは前項の通り、chrome拡張機能として公開されているのでインストールしてください。開いて適当にプロジェクトを作ってください。今回は調べ物として使うだけなので適当でいいです。

f:id:doradorasuki:20190916183544p:plain
seleniumIDEのスクリーンショット

こんな風な画面になると思います。Playback base URLには自動化を始めるURLにしておきましょう。あとはRECボタンを押すと、そのページにおける自分の挙動を記録することができます。さっきのウェザーニュースのページで色々やってみましょう。さっきのページで文字入力するところをクリックして、大阪とうち、検索ボタンをクリックすると、以下のような感じになると思います。
f:id:doradorasuki:20190916184337p:plain
操作後のseleniumIDEのスクリーンショット

今回だと3番目がフォームのクリック、5番目が検索ボタンのクリックに該当します。また、seleniumIDEでは対象をクリックし、targetを変更することで、その要素の他の指定項目を参照することが可能です。
f:id:doradorasuki:20190916184653p:plain
seleniumIDEのtarget

それではせっかくなので今回はxpathを用いて先ほどと同じ操作をしてみましょう。すると、こんな感じのコードになります。

place_form=driver.find_element_by_xpath('//input[@id="search_city_input1"]')
place_form.send_keys("大阪")
place=driver.find_element_by_class_name('//button[@type="submit"]')
place.click()

先ほどのhtmlと格闘するよりは10000000000倍くらい楽勝だし正確です。しかもこれだとjavascript関連でも結構問題なくいけます。この方法について書いてあるところがあまりなかったので今回ブログに書いています。ちなみにこのcss情報などは次の項で説明するBeautifulSoupでも応用可能です。収集したい文字列のところをクリックすると、そこについての情報がseleniumIDE上に表示されます。

BeautifulSoupで要素を抜き出してみる

ブラウザを動かしたところでやっと情報の収集です。この項ではウェブサイトから文字列を拾ってくるやり方について書きます。そのためにBeautifulSoupというライブラリを用いてhtmlから情報を取ってきます。そのためにまずdriverからhtml情報をもらいます。

from bs4 import BeautifulSoup
soup=BeautifulSoup(driver.page_source,"html.parser")

driverからはdriver.page_sourceでhtmlを取得可能です。parserとは、htmlのタグ情報から情報を解釈するプログラムのことらしいです。今回は標準搭載されているhtml.parserを採用しています。これにより、soupからはタグなどに応じた情報取得が可能になります。それでは1例としてウェザーニュースのサイトの時刻情報を抜き出してみましょう。時刻情報についてhtmlを読んで調べてみましょう。(今回もseleniumIDEを用いてcssの情報が取得できれば、それを利用することが可能です)

<div class="weather-day__item">
                                <p class="weather-day__time">17:00</p>
                                <p class="weather-day__icon"><img src="//smtgvs.weathernews.jp/onebox/img/wxicon/200.png"></p>
                                <p class="weather-day__r">0mm/h</p>
                                <p class="weather-day__t">25℃</p>
                                <p class="weather-day__w">5m/s<br></p>
                            </div>

今回はこのような記述があるので17:00という文字列を取得したい場合は

weather_time=soup.find("p",class_="weather-day__time")

を実行します。しかし、これでは一番初めにp class="weather-day_time"でヒットしたものがweather_timeに格納されます。全てを取得したい場合は

weather_time=soup.find_all("p",class_="weather-day__time")

を使いましょう。
ちなみに、(わかりやすいようにsoup.findの方で説明します)このときにprint(weather_time)を使うと

<p class="weather-day__time">23:00</p>

のようにhtml情報がprintされます。
これを避けるためにはprint(weather_time.string)とすると23:00と表示されるようになります。あと、この後sortなどをする場合に知っておくべきこととして、実験してもらうとわかると思うのですが、print(type(weather_time.string))の実行結果は

<class 'bs4.element.NavigableString'>

このようになります。なので、sortなどをしたい場合には必要に応じて文字列の組み替えなどを行なった上で適切な型にキャストしてやる必要があります。(筆者はこれでハマりました)pythonって普段型意識しないのでこういうところで詰まると発見が難しいですね。
以上のようにするのがBeautifulSoupの基本操作だと思います。

知っていると良さそうなTips

ここはコードを書く上でググったりして得た知見を適当に羅列しておきます。
・バグったり、うまくいかないときはtime.sleepとかでページの遷移を待つといいかもしれません(基本がブラウザ操作なので表示されるまでは情報が得られません)
chromeを表示させたくないときはheadless chromeとかでググるといいかもしれません
・driver.execute_script("javascriptのコード")でjavascriptのコードを実行させたりできます
・Alert(driver).accept()で出現したダイアログに対し、OKを送ることが可能です
・BeautifulSoupでcssを指定したいときはsoup.select(CSSの指定先)で指定可能です(seleniumIDEとの併用に便利)
xpathを使えばクリック操作はうまくいきやすい気がします(ただの経験則です)
・ブラウザバックはdriver.back()でできます
・最後にdriver.quit()するのを忘れないようにしましょう(無限にウィンドウが増えます)

最後に

スクレイピングの項でも触れましたが、過度なアクセスは相手方の迷惑になるためNGです。スクレイピングは仕様、用法を守り正しく服用しましょう。
できるだけググりやすいように記事は書いたつもりです。(環境構築とか書きたくないので)
以上の情報で少なくともch○nithmnetをスクレイピングするコードは書けます。
私の書いたch○nithm用のコードは github.com

に上がっているので興味があったり例をみたい人はどうぞ。
ここまで読んでいただきありがとうございました。