Seleniumでマウスを自動操縦して、Googleから株価データを取得する

# 概要
Seleniumの**ActionChains**により、ブラウザ上でのマウス操作を自動化できる。
それを応用して、Google検索結果の株価チャートから株価データを取得した。

# 動機
## 株価の推移データの取得
いろいろあって株価の推移データを得る必要があったので、そういうことができるAPIを探していたのですが、手軽に使えるいいものがあまり見つかりませんでした。

うーん、どうしたものか
とGoogle検索結果の株価データを眺めていたのですが…

https://www.google.com/search?q=NASDAQ:GOOG&tbm=fin
<img width="628" alt="qii1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307646/d2f2c1e4-5885-1089-84e8-61a8e8993a2d.png">

ん？

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307646/d5f8417b-adbb-d947-b7d6-d642193b4edf.png)


**これだ！！**


## どれ？
Google検索結果の株価チャート、マウスオンするとその部分の値を表示してくれるようなのです。

![qii2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307646/762a287f-53ee-0970-cbfc-5ee9b2b5cf97.gif)

つまり**自動でマウスを動かして、この部分の数値**を取得すれば株価を得られそうですね！！

# 方法
## セットアップ
Pythonのパッケージ、**Selenium**を用いてブラウザを制御することで実現しようと思います。

Pythonのセットアップについては世の中に良質な記事がたくさんあるため割愛します。

Seleniumのセットアップについては以下のページが役に立つかもしれません。
https://tanuhack.com/selenium/

## 実装

### ロジック
株価の検索結果画面で「F12」キーを押してそれぞれの要素にどのようなクラス名がついているのかチラ見すると、以下のようになっていました。
<img width="517" alt="qii3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307646/c145c1ca-dab9-fdc2-2fc7-969076715722.png">

というわけで実装は
1. 「uch-path」の左端にマウスを移動させる
2. 「knowledge-～」の二つの要素のテキストを取得
3. マウスを右へ1ピクセル移動させる
4. uch-pathの右端に到達していなかった場合、2.に戻る

のようになりますね。

### ActionChainsによるマウス操作

そしてマウスを移動させる処理は、Seleniumの**ActionChains**によって実装できます。

使用する場合は

```Python:インポート

from selenium.webdriver.common.action_chains import ActionChains
```

によってActionChainsをインポートして、マウスを操作したい場面で呼び出します。
以下は先述したロジック1.の「uch-path」の左端にマウスを移動させる処理を実装したものです。

```Python:マウスの移動

# uch-path要素の取得
graph = driver.find_element_by_class_name("uch-path")

# uch-path要素の左端にマウスを移動
actions = ActionChains(driver)
actions.move_to_element(graph)
actions.move_by_offset(-(graph.rect['width'] // 2),0)
actions.perform()
```
ActionChainsクラスを呼び出して、さまざまな動きを記述し、perform()で実行するといった形です。
move_to_element(要素名)で要素の中央にマウスを移動
move_by_offset(X, Y)で**現在のマウス位置**からX, Yだけマウスを移動させます。
（```-(graph.rect['width'] // 2)```では、グラフの横幅widthの半分だけ左に（負の値で）移動させています。）

あとは1ピクセルずつ右に移動させればいいので、

```Python:右に1ピクセルだけ移動
actions = ActionChains(driver)
actions.move_by_offset(1,0)
actions.perform()
```
をチャートの横幅分繰り返して、その都度データを取得すればいいですね。


ActionChainsについて、詳しくは以下に載っています。
他にもキーの押下、キーを押しながらマウスのクリックなどさまざまな動きを定義できるようです。
https://kurozumi.github.io/selenium-python/api.html#module-selenium.webdriver.common.action_chains


### ソースコード

ソースコードは以下のようになりました。

```Python:Scraping.py
# セッティング
## WebDriverの場所（以下は同じフォルダに配置した場合、適宜変更する）
WEBDRIVER_PATH = r'chromedriver.exe'
## 検索したい銘柄のコード。このワードでGoogle検索する
STACK_SYMBOL = r'NASDAQ:GOOG'

# seleniumのインポート
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains

# ドライバーの起動とアクセス
driver = webdriver.Chrome(WEBDRIVER_PATH)
driver.get(r'https://www.google.com/finance?q=' + STACK_SYMBOL)

# 要素の選択（描画中で要素を選択できない場合、例外を飛ばすのでもう一回やる）
while True:
    try:
        # クラス名「PVZpFf」はチャート上の「1日」、「5日」、「1か月」…などのタブ。
        # 左から6番目の「5年」を選択している。
        driver.find_elements_by_class_name('PVZpFf')[6].click()

        # チャートの要素「uch-path」を取得
        graph = driver.find_element_by_class_name("uch-path")

        # マウスの動きを記述
        actions = ActionChains(driver)
        ## チャートの中心にマウスを移動
        actions.move_to_element(graph)
        ## チャートの横幅（width）の半分だけ左に移動させて、チャートの左端にマウスを持ってくる
        actions.move_by_offset(-(graph.rect['width'] // 2),0)
        ## 記述した動作の実行
        actions.perform()

        # グラフの横幅を取得
        graph_width = graph.rect['width']

    # 失敗した場合
    except:
        # 描画中の場合など、graph要素を選択しようとしても失敗する場合がある。
        # 描画が終われば正常に取得できるため、失敗したらまた繰り返すようにする
        print("continue!")
        continue

    # graph要素をうまく取得できればループから脱出（break）する。
    break


# データを格納する辞書の初期化
record = {}

# データの取得
for i in range(graph_width):
    # 浮かんでいるカードから日付・株価を取得する。
    time_info = driver.find_element_by_class_name("knowledge-finance-wholepage-chart__hover-card-time").text
    info = driver.find_element_by_class_name("knowledge-finance-wholepage-chart__hover-card-value").text
    
    # データをとれていなかった場合にはレコードに追加しない
    if not info:
        continue

    # 日付をキーにして辞書に格納
    record[time_info] = info

    # 経過が見られるように出力している
    print(i, time_info, info)
    
    # マウスを右に移動させる処理
    actions = ActionChains(driver)
    actions.move_by_offset(1,0)
    actions.perform()

```


以上のコードを実行したところ、record変数には

```
{'2014年10月31日': '557.55 USD',
 '2014年11月14日': '542.91 USD',
 '2014年11月21日': '536.03 USD',
 '2014年11月28日': '540.35 USD',
 '2014年11月7日': '539.53 USD',
 '2014年12月12日': '517.24 USD',
 '2014年12月19日': '514.94 USD',
 '2014年12月26日': '532.57 USD',
 '2014年12月5日': '523.82 USD',
 '2015年10月16日': '662.20 USD',
 '2015年10月23日': '702.00 USD',
 '2015年10月2日': '626.91 USD',
 '2015年10月30日': '710.81 USD',
 '2015年10月9日': '643.61 USD',
 '2015年11月13日': '717.00 USD',
...
```

のようなデータが残りました。成功ですね！
あとはこのデータをdatetime型や数値型にパースすれば扱いやすいデータになると思います。

# まとめ
たくさん調べればAPIで株価を取得できるサービスはいくらでもありそうなので、実際に使うならそっちを使ったほうがいいと思います。(/_;)
ありがとうございました。

# 参考
画像に漫画みたいな集中線を入れられるたのしいツール
http://neutralx0.net/tool/line/