スクレイピングでコインチェックのスプレッドを調べる

コインチェックのスプレッドが知りたかったのでスクレイピングをしてみました、というお話。

#背景
有名仮想通貨取引所の一つにコインチェックがある。金融庁の登録はまだ下りていないようだが、ググるとビットフライヤーなんかと並んでおすすめとして紹介しているブログがあったりして、主要取引所の一つといえると思う。日本語で使える取引所の中では、取り扱い通貨数も多い。

そんなメリットの一方、スプレッドが非常に高いのではないかという声もある。ビットコインに関しては取引所と販売所の2つがあり、取引所取引（売り板買い板が見えるほう)ならスプレッドもほぼない。
一方でビットコイン以外のいわゆるアルトコインは販売所しかない。こちらは、売りと買いで数％はありそう。LSKは10％くらいあるんじゃないかと感じることもある。つまり、買って即売ると、仮にマーケットが変化していなかったとしても数％は損する(取引所取引のビットコインなら多分ほぼゼロ円)。

そんなコインチェックの販売所だが、スプレッドは公表されていない。FXなんかだと通貨ペアによって「スプレッドX銭！」のような表示があったりするが、コインチェックの販売所においてはそれはない。

そこで、スクレイピングを行ってコインチェックのスプレッドがどれくらいなのか調べようと思った。もともとはスクレイピングというほどでもなく、まずapiが公開されてるのでそれを使えばいいか、と安易に考えていたのだが、apiは一つのレートしか用意されてない様子(アスクかビッドかミッドかは謎)。そこでスクレイピングの勉強もかねてやってみた。当初BS4を使えばよいかと思っていたが、JavaScript(?)で動的に変化するページをとってくる方法がよくわからず断念。で、調べてみるとSeleniumというライブラリがあることを知り、使えそうだったので今回はこれを利用。

#やること
環境はpython3.6

で、やりたいことは次の通り。
１．ログイン
２．コインを指定
３．それを買う、としたときいくらかをとってくる
４．それを売る、としたときいくらかをとってくる
５．３，４を一定間隔で繰り返す

##ログイン
まず1については
>
https://qiita.com/gymnstcs/items/3776959458fea045c9ea

を参考にさせていただいた。

##ログイン後、売買それぞれのレートの取得
で、問題は２以降。
まず、買のページのアドレスがどうなっているか調べてみると、
>
https://coincheck.com/ja/buys?pair=btc_jpy

となっている。試しに"buys"を"sells"に変えてみると、売りのページになるのでこれでよさげ。あとはコインのティッカーをインプットとして渡してやれば、任意のコインの売買ページには行ける。

次に、このページの価格が表示されている部分がhtmlでどう書かれているかを知ればよい。これは以前BeautifulSoup4を使った時にやったことがあったが、ページ上で、"htmlのソースを知りたいところを右クリック→ページ検証"の機能を使えばよい。

すると、タグが"rate right ng-binding"なものを探せばよいとわかるが、、seleniumの"find element by tag name"でその部分を指定してもうまくいかない。そこで検索すると、stackoverflowに回答が出ていて、"css selector"を使えばよいとわかった。
>
https://stackoverflow.com/questions/18288333/need-to-find-element-in-selenium-by-css
とか。

あとは繰り返すだけ、、、と思ったが一つ問題が。それは、自分が持ってないコインは価格が表示されないこと。１枚でも入力してやれば表示されるので、それを入力するプログラムも追加。
>
amount.send_keys('0.01')

##コード
以上を考慮して書いたのが以下。


```python:cc_scraping.py
import libraries
from selenium import webdriver
import pandas as pd
from datetime import datetime
import time

#boost driver
driver = webdriver.Chrome('driverへのパス')
driver.get('https://coincheck.com/ja/sessions/signin')

#login if required
mail = driver.find_element_by_id('email')
pass_wd = driver.find_element_by_id('password')
mail.send_keys(自分のメールアドレス)
pass_wd.send_keys(自分のパスワード)
pass_wd.submit()

#list of the coins you want to get
coins = [
            ['BTC',  'btc_jpy'],        
            ['ETH',  'eth_jpy'],
            ['ETC',  'etc_jpy'],
            ['LSK',  'lsk_jpy'],
            ['FCT',  'fct_jpy'],
            ['XMR',  'xmr_jpy'],
            ['REP',  'rep_jpy'],
            ['XRP',  'xrp_jpy'],
            ['ZEC',  'zec_jpy'],
            ['XEM',  'xem_jpy'],
            ['LTC',  'ltc_jpy'],
#            ['DSH',  'dsh_jpy'], Dashはなぜかうまくいかないので今回は除外
            ['BCH',  'bch_jpy'],
        ]

def main():
    price_table=pd.DataFrame(columns=['coin','time_stamp','ask','bid'])
    for i in range(len(coins)):
        temp_rate=[]
        for j in ['buys','sells']:
            driver.get('https://coincheck.com/ja/' + j + '?pair=' +coins[i][1]) 
            time.sleep(5)
            amount=driver.find_element_by_id('amount')
            amount.clear()
            time.sleep(1)
            amount.send_keys('0.01') #売買単位を入力(入力しないとレートが表示されないので)
            time.sleep(1)
            rate=driver.find_element_by_css_selector('.rate.right.ng-binding').text #レートを取得
            temp_rate.append(rate)
        temp_price_table=pd.Series([coins[i][0],datetime.now(),float(temp_rate[0][0:temp_rate[0].find(coins[i][0][0])].replace(',','')),float(temp_rate[1][0:temp_rate[1].find(coins[i][0][0])].replace(',',''))],index=['coin','time_stamp','ask','bid'])
        price_table=price_table.append(temp_price_table,ignore_index=True)
        price_table['askbid']=price_table['ask']-price_table['bid']　#ask bidの差を計算
        price_table['mid']=(price_table['ask']+price_table['bid'])/2 #midを計算
        summary_table['mid%']=summary_table['askbid']/summary_table['mid']
    return price_table

repeat=0
while(repeat<61): #繰り返し回数=60
    a=main()
    summary_table=pd.concat([summary_table,a])
    time.sleep(300) #何秒ごとに走らせるか
    summary_table.to_csv('保存先のパス') #whileが終わってから保存してもよいが途中でネットが切れた時等のため保存しておく
    repeat=repeat+1

#結果の表示
summary_table.groupby('coin')['mid%'].describe()

driver.close()
```

#コードの説明と注意点・課題
やってることは、askとbidをとってmidを計算している。タイムスタンプはかなりざっくり。取得時点と、記入時点が異なるし、そもそも売りと買いでwebページの遷移を待たせている(この辺は表示を待つ必要のないブラウザを使えば解決されると思う)。

で、repeat<61のところで回数を指定して、time.sleep(300)のところで何分に一回とるか、を指定すればよい。この辺も、「プログラムにかかる時間」＋「待ち時間（ここでは300秒）」としてるので、実際には正確な５分毎になっていないが、とりあえず一晩回してみようと思い、5分に一回＝一時間に12回、で、5時間と考えて60としてある。

あと最初のログインのところで、登録しているアドレスにメールが来て「ログインが求められています、ここでログインして、端末でもページを更新すれば見られます」みたいなことが書かれていた。必ず起こるのかわからないが、main()の前のところまでをいったん走らせてログインしてから、main以降を回せば動く(はず)。今更その辺が適当だったと気づいた。

#結果
まあその辺はざっくりで一晩回してみたところ、スプレッド率は次のような結果となった(絶対値表示、例えばBCHの一番右列"MAX"はスプレッドが11.07%を表す)。

|coin|count|mean|std|min|25%|50%|75%|max|
|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|
|BCH|60.0|0.1047|0.0023|0.1014|0.1027|0.1043|0.1062|0.1107|
|BTC|60.0|0.0103|0.0002|0.0097|0.0101|0.0103|0.0104|0.0109|
|ETC|60.0|0.0646|0.002|0.0614|0.0628|0.0645|0.0658|0.0694|
|ETH|60.0|0.0632|0.0014|0.0613|0.0623|0.0629|0.064|0.0682|
|FCT|60.0|0.0683|0.005|0.0609|0.0646|0.0671|0.0719|0.0796|
|LSK|60.0|0.0645|0.0025|0.0609|0.0623|0.0644|0.0665|0.0699|
|LTC|60.0|0.063|0.0011|0.061|0.0622|0.0628|0.0636|0.066|
|REP|60.0|0.0672|0.004|0.0615|0.0641|0.0674|0.0693|0.0815|
|XEM|60.0|0.065|0.0024|0.0611|0.0632|0.0648|0.0667|0.0715|
|XMR|60.0|0.0641|0.0021|0.061|0.0623|0.0633|0.0656|0.0703|
|XRP|60.0|0.0632|0.0014|0.0612|0.0621|0.0629|0.064|0.0676|
|ZEC|60.0|0.0642|0.0023|0.0613|0.0625|0.0642|0.0648|0.0729|

meanを見てみると…ビットコインは1％、大体のアルトコインは6～7％、そしてなんと分岐仕立てのBCHは10％！！ボラ(std)を見ると、LSKとか結構高いのかと思ってたけど意外とそうでもない様子。FCT, REPのほうが高いっぽい。

#まとめ
とまあまあ、あちこちかなりやっつけだけど可視化してみました、というお話でした。最初にも書いたが多分その時々でスプレッド変わるのでその辺は注意が必要。やったのも一晩だけなので長く見ると全然変わる可能性はあるのでもう少しトラックしていろんな角度から見てみると面白いと思う。スプレッドの水準もさることながら、スクレイピングの勉強になった。