PythonのHTTPRequestを使ったスクレイピングでSBI証券の口座残額取得
いままでスクレイピングで利用していたSeleniumだがChromeの変更なのか動かないことがあるため、別の方法でスクレイピングをしようと思い HTTPを直接使えばいちいちウインドウが開くこともなくスムーズに動くと思い実際に作成を試みたが非常に難しく2週間かけてようやく出来たのでそのメモ
スクレイピングは時には大量のデータを送りつけたとして犯罪になる可能性があるので送信量には注意されたい。
HTTP
HTTPとはテキストを送るプロトコルでHTMLなどの転送に主に用いられるがその仕組をいまいち理解していなかった。
まずHTTPにはいくつかのメソッドがあるが主に使われるのがGETとPOSTの2つである。
GET
GETメソッドは指定されたURLからリソースを取り出すもので通常のWebページの閲覧ではこれが主に使われる。
HTMLのソースコードも画像もJavaScriptやCSSなどもGETを使ってサーバに要求し取得する。
POST
POSTメソッドは反対に要求ではなくこちらからデータ等を送信するのに利用する。 ログインのIDのパスワードやその他の情報などをPOSTを使いサーバに送信する。
この点は、下記のサイトが非常に分かりやすかった。
Cookie
HTTPはデータの送信や要求等ができるが全ての通信は独立しておりセッションを確立しない。 そのため1度行った通信相手をサーバが覚えている事ができない。
しかしそれではWebサーバがユーザ管理をできず同じページの表示しか返信できずユーザ毎にサービスを提供することができない。 それを改善するためにCookieというものが開発され実装された。
CookieはHTTPのヘッダに含められ、サーバがクライアントに対して送る。 クライアント(Webブラウザ)は次回以降サーバと通信する際にCookieを一緒に送ることでサーバ自分が誰かを教えることができる。
プログラムの流れ
以上の点から実際にHTTPの通信は以下のようになる。
1.SBI証券にユーザIDとパスワードをPOSTして認証してもらう。
2.認証が終わればCookieが帰ってくるのでそのCookieを使い口座残額のあるページを要求する。(厳密に言えば認証に失敗してもCookieは返ってる)
3.株価の残高取得ができたら続いて先物ページも同じく取得する。
という流れでプログラムの作成を試みた。
HTTPリクエストとCookieの実装
参考にしたサイトはこちら
まずPythonでHTTPとCookieを使えるようにするために以下のモジュールをインポートする必要がある。
from urllib.request import build_opener, HTTPCookieProcessor from http.cookiejar import CookieJar
続いてCookieを用意する
opener = build_opener(HTTPCookieProcessor(CookieJar()))
続いてSBI証券にログインするために自身のIDとパスワードを用意する
post = { "user_id":"*****", "user_password":"*****", }
これをHTTPのPOSTに乗せて送る必要があるのでutf-8にエンコードするがそのために下記のモジュールをインポートする必要がある
#エンコードするモジュール from urllib.parse import urlencode #エンコード data = urlencode(post).encode('utf-8')
続いて実際にHTTPをPOSTしてみる
#トップページのurl url = "https://site1.sbisec.co.jp/ETGate/" res = opener.open(url, data)
残念ながらログインされた結果が帰ってこない。
帰ってきたHTMLのソースコードにユーザIDとパスワードを入力する欄がありログインできていなことが分かる。
POSTの盗聴
どうやらログインするのに必要なのはIDとパスワードだけではないらしくほかにも送らなければならない情報があるみたいだ。 そのため実際にログインする際の情報を確認してみることにする。
Chromeのデベロッパーモード
ChromeではF12を押すとデベロッパーモードが開きHTMLのソースコードや通信状態を監視することが出来る。
上の箇所がElementsになっているとソースコード、Networkにすると通信状態が表示されるようになるので、Networkに切り替えて通常通りログインしてみる。
すると幾つかの通信状態が表示されるので実際にログインした際の通信を表示してみる。
今回はログインしたので通信はPOSTになり、urlはETGateになるので画像で言えば1番上のになる。
実際にログインした際のPOSTデータを確認してみると確かにユーザIDとパスワード以外にもPOSTしていることが分かる。
(IDとパスワードは勿論だが、他の要素が何を表しているか調べてなく、問題はないとは思うが個人IDであるとマズいので一部、塗りつぶした。)
POSTデータの修正
上記の結果からPOSTデータを修正してみるが他の要素が何を表しているか不明であるがとりあえずそのまま送ってみることにする。
post = { "JS_FLG":"1", "BW_FLG":"chrome,52", "_ControlID":"WPLE****", "_DataStoreID":"DSWPLETl*****", "_PageID":"WPLETl******", "_ActionID":"login", "getFlg":"on", "allPrmFlg":"on", "_ReturnPageInfo":"***", "user_id":"*****", "user_password":"*****", "ACT_login.x":"80", "ACT_login.y":"13" }
このPOSTデータを再度、送信してみる。
data = urlencode(post).encode('utf-8') res = opener.open(url, data) #帰ってきたHTMLのソースコードをテキスト保存 with open('source.text', 'wb') as f: f.write(res.read())
今度はソースコードに重要なお知らせとあり、ログインに成功したことが分かる。
続いて口座管理のURLを予めコピーしておきそのURLをGETする。
右クリックでリンクアドレスのコピーで良い
kouzakanri = "https://site1.sbisec.co.jp/ETGate/?_ControlID=WPLETac***"
res = opener.open(kouzakanri)
今回も一応、非表示にした。
あとは先のレスポンスをテキストにするなり正規表現等を使って抜き出すなりすれば良い。
正規表現
ちなみに正規表現で抜き出すにはreをインポートする必要がある
#正規表現のインポート import re #response(bytes)はHTMLのソースコードなので.read()で読み出す html = res.read() #bytes型をデコード shift = html.decode('Shift_JIS') #現金残高抜き出し,cash.group()で参照可能 #残高が1000円以上 try: cash = re.search("現金残高等[\">\'=<\/0-9A-Z\ra-z \n,]*",shift) cash = re.search("[0-9]*,[0-9]*",cash.group()) cash.group() #残高が1000円未満だとこっちが走る except AttributeError: cash = re.search("現金残高等[\">\'=<\/0-9A-Z\ra-z \n,]*",shift) cash = re.search("[0-9]*",cash.group()) cash.group() #株式評価額 try: stock = re.search("株式<\/div>[\r\n]*(.)*[\r\n]*(.)*",shift) stock = re.search("[0-9]*,[0-9]*",stock.group()) stock.group() except AttributeError: stock = re.search("株式<\/div>[\r\n]*(.)*[\r\n]*(.)*",shift) stock = re.search("[0-9]*",stock.group()) stock.group() print("株式買付余力:" + cash.group()) print("株式評価額 :" + stock.group())
ソースコード全容
ソースコードをつなげて再度載せる
from urllib.request import build_opener, HTTPCookieProcessor from urllib.parse import urlencode from http.cookiejar import CookieJar import re # HTTP通信時にCookie処理を有効化 opener = build_opener(HTTPCookieProcessor(CookieJar())) # ログインに使う送信データ post = { "JS_FLG":"1", "BW_FLG":"chrome,52", "_ControlID":"WPLE****", "_DataStoreID":"DSWPLETl*****", "_PageID":"WPLETl******", "_ActionID":"login", "getFlg":"on", "allPrmFlg":"on", "_ReturnPageInfo":"***", "user_id":"*****", "user_password":"*****", "ACT_login.x":"80", "ACT_login.y":"13" } data = urlencode(post).encode('utf-8') #トップページのurl url = "https://site1.sbisec.co.jp/ETGate/" #sbiにログイン res = opener.open(url, data) #口座管理ページのURL kouzakanri = "https://site1.sbisec.co.jp/ETGate/?_ControlID=WPLETac***" res = opener.open(kouzakanri) #response(bytes)はHTMLのソースコードなので.read()で読み出す html = res.read() #bytes型をデコード shift = html.decode('Shift_JIS') #現金残高抜き出し,cash.group()で参照可能 try: cash = re.search("現金残高等[\">\'=<\/0-9A-Z\ra-z \n,]*",shift) cash = re.search("[0-9]*,[0-9]*",cash.group()) cash.group() except AttributeError: cash = re.search("現金残高等[\">\'=<\/0-9A-Z\ra-z \n,]*",shift) cash = re.search("[0-9]*",cash.group()) cash.group() #株式評価額 try: stock = re.search("株式<\/div>[\r\n]*(.)*[\r\n]*(.)*",shift) stock = re.search("[0-9]*,[0-9]*",stock.group()) stock.group() except AttributeError: stock = re.search("株式<\/div>[\r\n]*(.)*[\r\n]*(.)*",shift) stock = re.search("[0-9]*",stock.group()) stock.group() print("株式買付余力:" + cash.group()) print("株式評価額 :" + stock.group())
先物
先物のページについては後日記載
参考にしたサイト(再掲)
[Web] HTTPリクエストの中身を学んでみた。GETやPOSTの違いなど - YoheiM .NET
[Python] HTTP通信にCooki処理を追加して、はてなにログインする - YoheiM .NET
[Python] HTTP通信でGetやPostを行う - YoheiM .NET