Python
JavaScript
Selenium
websocket

web上で取得されているxhr(XMLHttpRequest)の値をpythonに渡してみた(1/2)

More than 1 year has passed since last update.

はじめに

よくリアルタイムにデータを更新しているWebページがありますね。そういう時はXHR(XMLHttpRequest)が使われている事があって、その生データを取得したい!って事があります。たぶんあります、あったのですが。。(seleniumとかでソースを取得してもいいですが表示していないデータとかもあったりしたり、また取得が遅いというのもあります)
後自動化を進める為全てスクリプトベースで実行出来るようにしています。

1.実現方法

出来るだけ簡単な方法がないか調べてみました。(他にも方法はありそうですが。。。)
今回の方法はざっくり書くと、

  1. Webでxhrモニター(JavaScript)を実行、生データの取得。
  2. xhrデータをwebsocketで通信をしてpythonに渡す

という方法で行っています。
ここで、websocketの実装方法が今回の場合あまり賢くないのですがお気軽(手抜き?)実装としています。

  • データの流れ
    Web(JavaScriptでxhrのモニター&websocket通信)
       ↓
    WebSoketサーバー(Python)
       ↓
    WebSocketクライアント(Python)
    となっています。(今回の機能だけだと最後のWebSocketクライアントはいらない子なんじゃないかと。。は気づかない事に!後、単純にデータベースに登録するだけであればサーバー側に機能追加した方がよさそうです。後双方向通信もしていないのでリッチ過ぎる方法とも・・・) 今回の実装にしておけば、他のpythonスクリプトでデータを見たい!となっても複数のWebSocketクライアントが接続可能なのでこの方法でもいいかと(あればラッキーと思いましょう)

2. 実装について

 4.1 xhrのモニターについて。
まずは、xhrのモニターについてですが以下を参考にして実装します。
Capture AJAX response with selenium and python
- Stack overflow

  • 殆ど理解していないのですが、onreadystatechangeの発生トリガで、xhrのデータを取得しているようです。以下のページが参考になります。
    非同期通信によるファイルの取得 - Ajaxを用いたHTTP通信 - Ajax入門
  • これを実行するとXHRの内容を実行Webにdivエレメントとして埋め込んでいるようです。そのままだとxhrの通信が行われる度に表示データが追加されるので表示データを減らす為に今回は間近4つのみ表示させています。
  • WebSocketの通信も書いていますが、取り敢えずこれ単体でも動作するので最初に動作確認としてはよいかと。
  • モニターの実験するwebは仮想通貨の以下のページがxhrで通信しているようなので、そこで実行してみます。(仮想通貨の中では少し特殊な所とはいえなくもないですが・・・)後仮想通貨を選んだのは殆どいつも動いている(メンテナンス以外?)からデバッグがやりやすいというのもあります。(はっ、流行りに乗ったからなんじゃないんだからね!)
    GMOコイン
    https://coin.z.com/jp/corp/guide/comparison/index.html
    xhrで通信しているwebであればurlを変えるだけで今回の方法が使えると思います。後xhrで通信しているかどうかはchromeのデベロッパーツールで見てみるとわかりやすいです。 以下の[Network]パネルの所とかが参考になると思います
    Web開発でよく使う、特に使えるChromeデベロッパー・ツールの機能 - Build Insider

3 xhrのモニタの実行スクリプト

以下が実行スクリプトになります。これを実行するとGMOコインのページが表示されて、ページの下側までスクロールするとxhrの生の値の間近4つが表示されて刻々と変わっているのが確認出来ると思います。(仮想通貨の相場はメンテ中でなければ1週間休まずに動いているのでいつでもチェック出来ると思います)

こんな感じで表示されます。
gmocoin.jpg

後、事前にseleniumとchromeドライバーをインストールしておいて下さい。

python -m xhr_mon.py
で実行出来ます

xhr_mon.py
import time
from selenium import webdriver

driver = webdriver.Chrome("chromeのインストールパス")
# load some site
driver.get('https://coin.z.com/jp/index.html')

driver.execute_script("""
(function(XHR) {
  "use strict";

  var element = document.createElement('div');
  element.id = "interceptedResponse";
  element.appendChild(document.createTextNode(""));
  document.body.appendChild(element);

  var open = XHR.prototype.open;
  var send = XHR.prototype.send;
  var socket = new WebSocket("ws://127.0.0.1:8089");
  var xhrdata = [];
  var datacount = 0;

  XHR.prototype.open = function(method, url, async, user, pass) {
    this._url = url; // want to track the url requested
    open.call(this, method, url, async, user, pass);
  };

  XHR.prototype.send = function(data) {
    var self = this;
    var oldOnReadyStateChange;
    var url = this._url;

    function onReadyStateChange() {
      if(self.status === 200 && self.readyState == 4 /* complete */) {
        xhrdata.push(self.responseText + ' ***** <br>');
        datacount = datacount + 1;
        if(datacount>4) {
                xhrdata.shift();
                datacount = 4;
        }
        var outdata = "";
        for (var i=0;i<datacount;i++){
                outdata += xhrdata[i];
        }
        document.getElementById("interceptedResponse").innerHTML = outdata;
        socket.send(self.responseText);
        document.getElementById("interceptedResponse").innerHTML += socket.rcvd();
      }
      if(oldOnReadyStateChange) {
        oldOnReadyStateChange();
      }
    }

    if(this.addEventListener) {
      this.addEventListener("readystatechange", onReadyStateChange,
        false);
    } else {
      oldOnReadyStateChange = this.onreadystatechange;
      this.onreadystatechange = onReadyStateChange;
    }
    send.call(this, data);
  }
})(XMLHttpRequest);
""")

while(True):
    time.sleep(3600)

少し長くなりそうなので、「次回(恐らく最終回)に続く」 です
WebSocketの通信とか、動作確認は最後まで終わっている&この投稿時に2/2はほぼ書き上げている状態なので。すぐに投稿出来ると思っています。
次の記事もUpしています。
(Update)
web上で取得されているxhr(XMLHttpRequest)の値をpythonに渡してみた(2/2)

補足

今回のselenium実行時に以下のようなエラーが出ます、がこれはどうもこのスクリプトのせいではなく、webdriver?chromeの不具合っぽいです。以下の参考を見ると、最新版でも出るようです。負荷が重いとレスポンスが悪化して出てるのかな?

[1156:16736:1210/152258.624:ERROR:process_metrics.cc(105)] NOT IMPLEMENTED

参考:https://groups.google.com/forum/#!topic/selenium-users/UsUuDeqBIIo