目次
前提
以下の環境で実装、動作確認済み
要素 | バージョン |
---|---|
debian | 8.6 |
python | 3.6.2 |
概要
非常にニッチな目的に感じる題だが、以下の流れでこうなった
- 諸事情でチャットワークの表示名を自動で変更する手段を作りたい
- チャットワークAPIを使えばいけそう
- ユーザ情報を変更するAPIが存在しなかった(見落としているだけであったら教えてください)
- 仕方ないのでスクレイピングで強引に変更しよう
- チャットワークにログインする必要もあるのでSeleniumを使うのが良いのかな
- せっかくだから最近勉強してるPython3を使おう
以上より、Seleniumを用いてFirefoxを自動操作し、チャットワークにログイン、表示名を任意のに変更するスクリプトを実装する。
なお、今回はGUIを持たない環境で実装するので、Xvfbを用いた仮想画面を使って動かす(この辺理解が怪しい)
下準備
まずFirefoxを入れる。画面無くて良いならPhantomJSとかヘッドレスブラウザ使えばいいじゃんと思い最初はそうしたのだが、どうしてもチャットワークが正しく表示できなかったので安定を取ってFirefoxにした。その分容量が大きいけど仕方ない。
$ apt-get install firefox
Firefoxのレンダリングエンジンであるgeckoをseleniumから利用するためのドライバーが別途必要。
こちらからダウンロードできるので、OSの種類に応じてダウンロードする。
今回は64ビット版のLinuxなので以下のように。パスが通ってるところで展開したほうが後々楽。
$ cd /usr/local/bin $ wget https://github.com/mozilla/geckodriver/releases/download/v0.18.0/geckodriver-v0.18.0-linux64.tar.gz $ tar -zxvf geckodriver-v0.18.0-linux64.tar.gz $ rm geckodriver-v0.18.0-linux64.tar.gz
仮想モニタが必要になるのでインストール
$ apt-get install xvfb
必要なPythonモジュールをpipでインストール
$ pip install selenium $ pip install PyVirtualDisplay
ログイン情報を定義
チャットワークにログインするためのメールアドレスをパスワードが必要になるが、ソースコード中にハードコーディングしてしまうとコードの共有ができなくなってしまうので、今回はJSONファイルに別途書き出すようにする。
以下のようなsecret.jsonを作成した。
{ "email": "hogehoge@fugafuga.com", "password": "qawsedrftgyhujikol" }
実装
今回は一つのファイルで全て完結させるので、前項で作成したsecret.jsonと同じフォルダに、main.pyを作成する。
モジュールインポート
今回使うモジュールは以下の通りなのでimportする。
import time import json import sys from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions from selenium.webdriver.common.by import By from pyvirtualdisplay import Display
ログイン情報を取得する
secret.jsonを開いて、jsonモジュールでデシリアライズ。メールアドレスとパスワードを抜き出す
with open('secret.json', 'r') as file: secret = json.load(file) email = secret['email'] password = secret['password']
Seleniumの初期設定
仮想モニタを立ち上げ、SeleniumDriverをFirefoxで初期化。画面サイズがやたらと大きいのはデバッグ時のスクリーンショットを見やすくするためなので最終的に意味はない。
display = Display(visible=0, size=(1920, 1080)) display.start() driver = webdriver.Firefox()
チャットワークへのアクセス
SeleniumDriverのgetメソッドで簡単にWebサイトにアクセスできる。
driver.get('https://www.chatwork.com/login.php?lang=ja&args=')
メールアドレス/パスワードを入力
find_element_by_css_selectorメソッドで、CSSセレクターを用いてDOMを取得し、それに対してsend_keysメソッドを用いてキー入力を行うことができる。JSONファイルから取得しておいたメールアドレス/パスワードを入力する。今後他にも出てくるけど、SeleniumDriverって何かとメソッド名が冗長だなと思った。
driver.find_element_by_css_selector('form[name="login"] input[name="email"]').send_keys(email) driver.find_element_by_css_selector('form[name="login"] input[name="password"]').send_keys(password)
ログインする
同じくfind_element_by_css_selectorメソッドを用いてログインボタンを取得し、clickメソッドでクリックすることでログインする。
driver.find_element_by_css_selector('form[name="login"] input[name="login"]').click()
ログイン処理には時間がかかる上、チャットワークはJavaScriptで画面を構築していくので、構築の完了まで待機する必要がある。
Seleniumでは、以下のようにWebDriverWaitを用いることで、「特定の何かが確認できるまで待機する」という記述を行うことができる。
以下では、IDが_myStatusNameのDOMが確認できるまで待機するという処理(10秒でタイムアウト)
wait = WebDriverWait(driver, 10) wait.until(expected_conditions.visibility_of_element_located((By.ID, "_myStatusName")))
_myStatusNameは、この部分。これが表示されたら読み込み完了と捉える。
プロフィール画面を開く
ヘッダのプルダウンメニューを開いて、編集メニューをクリックしてプロフィール編集画面を表示する。
かなりまどろっこしい事してる気がする。可能であれば最初からプロフィール編集画面を表示するためのJavaScriptコードを実行させれば一発だと思うが、Seleniumの練習も含めて地道に行う。
0.5秒のスリープを毎回挟んでるのは、UIのアニメーションがあるのでスグに次のUIを操作できないため。これもっと良い方法あったら教えて下さい。
driver.find_element_by_css_selector('#_myStatusName').click() time.sleep(0.5) driver.find_element_by_css_selector('#_myProfile .myAccountMenu__anchor').click() time.sleep(0.5)
表示名を編集する
編集ボタンをクリックし、テキストボックスの内容を書き換え、保存ボタンをクリックする。
変更後の表示名は、コマンドライン引数から取ることを想定し、sys.argvを参照する。
driver.find_element_by_css_selector('#_profileContent ._profileEdit').click() time.sleep(0.5) driver.find_element_by_id('_profileInputName').clear() driver.find_element_by_id('_profileInputName').send_keys(sys.argv[1]) driver.find_element_by_css_selector("div[aria-label='保存する']").click()
実行
以下コマンドで実装できる。ネットワーク環境の影響を大きく受けるが、手元の環境で実行時間は10秒程度。APIがあれば1秒で終わるというのに。
$ python main.py "ふー ばーのすけ"
最新のソースコードはこちら
所感
- Seleniumの入門としてはえらくニッチな使い方になったが、個人的にやりたかったことが実現できたので良かった
- 全体的冗長なコードになりやすそう。ラッパーライブラリが多いのも頷ける
- JavaScriptで動いてるページをうまく制御したければ関連ライブラリ/フレームワークを使うべきか
- このやり方だとチャットワーク側がちょいとHTML変更したら動かなくなったりするのでやっぱりAPIが欲しい