ゆるふわクオンツの日常

Pythonとseleniumでスクレイピング必勝講座書いてたら(察し

注意:この記事で取り扱っているスクレイピング先のサイトの仕様が変わってました。記事で紹介している手法は汎用的なものなので、皆さん他のサイトで工夫して使っていただけると幸いです

今回はseleniumを使ったwebクローリング&スクレイピングの記事になります。

事前準備

まずは、seleniumpythonライブラリ)のインストールです。

pip install selenium

or

conda install -c anaconda selenium

続いて、seleniumからブラウザを操作する用のドライバをダウンロードします。

seleniumは、pythonから他のブラウザ(google chromeなど)を起動して、

そのインターフェイスを利用していろいろやるのですが、

その際に、seleniumが他のブラウザを操作できるようにするのがドライバです。

以下のseleniumの公式サイトからご利用のブラウザに対応するドライバを入手してみてください。

https://www.selenium.dev/downloads/

以上、 seleniumのインストール②ドライバのダウンロード の2つが終われば事前準備は完了です。

クローリング&スクレイピングする先は?

今回は以下の仮想通貨デリバティブの取引所(webアプリ)を題材に

データを取得してみたいと思います。(ガッツリreactで書かれているぽい)

https://app.lien.finance/trade

この取引所アプリでは、

販売されているデリバティブの価格が適正価格からある程度乖離したら、

ページの右上の方にアラートメッセージが表示される仕組みになっています。

実際にトレーディングで裁定取引をやろうとすると、今回のやり方だとスピード的にアレな気もしますが、

とりあえずwebのデータ取集の練習として 、

右上のarbitrage alertを取得するコードを書いてみたいと思います。

コード

ざっくりと以下のようなコードを書いてみました。

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
import datetime as dt
import time


URL = "https://app.lien.finance/trade"
DRIVER_PATH = "./chromedriver"


for _ in range(10):  # とりあえず10回やる. 定期実行ならcronでやった方が良さげ
    options = Options()
    options.add_argument('--headless')
    driver = webdriver.Chrome(DRIVER_PATH, options=options)
    driver.get(URL)

    is_arbitrage = False
    arbitrage = 0
    arbitrage_atr = ""

    try:
        WebDriverWait(driver, 30).until(
            EC.presence_of_element_located(
                (By.XPATH, "//div/div/div/span/p/span")
            )
        )
        time.sleep(5)

        now = dt.datetime.now()
        elements = driver.find_elements_by_xpath(
            "//*[contains(@class, 'font-bold')]"
            )
        for element in elements:
            if element.text=='Arbitrage Alert!!':
                is_arbitrage = True
                _comments = driver.find_elements_by_xpath(
                    "//div/div/div/span/p"
                    )
                arbitrage_atr = _comments[0].text
                _arbitrage = driver.find_elements_by_xpath(
                    "//div/div/div/span/p/span"
                    )
                arbitrage = float(_arbitrage[0].text)
                break
    except:
        now = dt.datetime.now()

    if is_arbitrage and abs(arbitrage) > 10:  # gas代高いですからね...
        print('{}: {}'.format(now, arbitrage_atr))

    driver.close()
    driver.quit()

適宜確認していきます。

まず初めにドライバの読み込みです。

ディレクトリ構造は、今回動かす.pyファイルとドライバが同じディレクトリにある想定で

.
├── 今回のファイル.py
└── chromedriver

みたいな感じを想定しています。

options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(DRIVER_PATH, options=options)

上の2行でドライバのオプションを指定しているのですが、

"--headless"の内容はseleniumから動かすブラウザを非表示にするというものです。

このオプションはなくても当然問題無いので、

動作を目で確認したい人はオプションを外してもらっても大丈夫です。

続いてwait処理です。

# arbitrage alert!! or timeoutするまで待機.timeoutしたらexceptに流れる
WebDriverWait(driver, 30).until(
    EC.presence_of_element_located((By.XPATH, "//div/div/div/span/p/span"))
)
# 描画にラグがあることがあるので少し待つ
time.sleep(5)

サイトをブラウザで見てもらえばわかるのですが、実はこのサイト結構重いです。

ですので、driver.getした後にすぐに処理を開始しようとするとダメです。

そこで、右上のarbitrage alertの最下層のxpathが取得できるまで一旦停止する処理がWebDriverWaitになります。

また、arbitrage-alertのあとも少し待つことで

ちゃんとブラウザを目視したときと同じ情報が取れます。

このあたりでめんどくさくなってきたのですが、

_comments = driver.find_elements_by_xpath("//div/div/div/span/p")
arbitrage_atr = _comments[0].text
_arbitrage = driver.find_elements_by_xpath("//div/div/div/span/p/span")
arbitrage = float(_arbitrage[0].text)

ここで、arbitrage alertの内容を取得しています。

取得の仕方は絶対にもっと良いやり方があるのですが、めんどくさ(略

で、最後にログを吐き出してオワオワリです。

if is_arbitrage and abs(arbitrage) > 10:  # 10%以上のarbitrage alertのみ反応
    print('{}: {}'.format(now, arbitrage_atr))
    # notifyを追加したければここに

通知

notifyとしては、例えばmacのローカルだったら

import subprocess
import datetime as dt
import platform

def mac_notify(txt):
    now = dt.datetime.now()
    pf = platform.system()
    if pf == 'Darwin':  # 使ってるOSがmacなら
        script = """osascript -e 'display notification "{}: {}"'""".format(now, txt)
        subprocess.run(script, shell=True)

こんな感じで通知を飛ばしたりできます。

他にも以下のような関数を用意してLINEに通知を送ったりもできます。

import requests

def line_notify(message):
    line_notify_token = ''  # 発行したトークン
    line_notify_api = 'https://notify-api.line.me/api/notify'
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + line_notify_token}
    requests.post(line_notify_api, data=payload, headers=headers)

ではまた!

今回の関連書籍

この本は昔読みましたよかった気がします。ただ、最近の本はあんまり見てないので、もっといい本が出ている可能性はありそうです。