2019年11月18日月曜日

[RaspberryPI]ラズベリーPI4 (raspbian Buster)にbazelをインストールしてみる

 最近、32ビットをサポートしないソフトが増えてきた。ここで新しいPCを新調すれば良いのだが、(何を思ったか)ラズベリーPIというワンボード・コンピュータを買うという暴挙に出てしまった。面積はキャッシュカード位で高さはコネクター類が付いているから1.5cm位。Raspberry PI 4といやつで4コアだ。あちこちから色々なセットが発売されているが、今回は4G RAMモデルのボードにヒートシンクとファンに、ちょっと容量大きめの電源アダプタを選んだ。
 今使っているのは2コアで2G RAMなので、単純にみれば2倍だ。得物にraspbian-busterというものを入れ込んで(基本的にラズベリーPIはMicroSDをストレージとしているので、raspbian-busterをMicroSDに書き込み、そのMicroSDを本体にセット)、電源を入れると...ラズベリーPI用のDebian Linuxが立ち上がる......。

で、ここからが本題!
 bazelというツールがある。これは、mavenやらmake(?)等と同じようにソースからプログラムをビルドするためのツールで、最初はconfigureというシェルスクリプトでMakefileを自動生成して後はmakeコマンドでプログラムをビルドするというもので良かったが、段々とMavenなどの複雑なツールへと代わって行った。使う人間にとってはあまり関係ないが、作る側にとってプログラムは作成、修正、変更、検証の繰り返しであるからそれらをなるべく一元的に管理できるような仕組みが必要になる。
 bazelはGoogle由来のオープンプロジェクトのビルドに結構使われている。ところがbazelは結構バージョン替わりが早くて、raspberry(向け)に用意されている(bazelの)パッケージでは必要なプログラムをビルドするために必要なバージョンと合わない事がしばしばある。で、bazelをソースからビルドすることにした。このビルドが旨く行ったら、次に設定ファイルなどを書き変えて32bit用の実行ファイルが作れればそれこそ一石二鳥。

 途中で色々なエラーが発生して、結構な時間を費やしてしまったが結局ビルドに成功した。

まず、githubにあるbazelのリリースページからbazel-0.27.1-dist.zipをダウンロードしてホームディレクトリーに置いておく(初めてビルドする場合は-dist付きのをダウンロードする必要があることに注意)。
次にjdk(今回はopenjdk8)とビルド環境(gcc,g++,make等)をパッケージインストールする。その他にpythonやらpython3が必要なようだがraspbian-busterには最初から入っているので今回は特にインストールすることはしない。


cd ~
mkdir bazel
cd bazel
unzip ../bazel-0.27.1-dist.zip
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-armhf
export EXTRA_BAZEL_ARGS="--host_javabase=@local_jdk//:jdk --jobs=2"
export BAZEL_JAVAC_OPTS="-J-Xms256m -J-Xmx512m"
./compile.sh


bazelのビルド情報は数多くあり、その方法は(raspbianのバージョンやツールの組み合わせによって)様々で、色々試したがほとんどが旨くいかなかった。やっと旨くいったのが上記のように環境変数をセットする方法。どのバージョンからそうなったのかは調べていないが、ビルドファイルが整備されてビルドが簡単になったのだと思う。ここでポイントとなるのは--host_javabase=@local_jdk//:jdkで、これがないとよく判らないエラーに悩まされる。3時間ほどでビルドが終わりoutputディレクトリの下にbazelが生成される。
生成されたものがちゃんと動くかどうかをチェックするため、output/bazelを他のところ(例えば/tmp等)に移動させて、そのフルパスを環境変数BAZELにセットしてから、もう一度compile.shを実行する。エラーなしでoutput/bazelが生成されれば多分問題なしと思われる。
これを/usr/local/bin/の下あたりにコピーしてインストール完了。
結構疲れた。

追記(2020/01/11) bazel 2.0.0(えらく飛ばしてしまったけど)も、この方法でビルドすることができた。

2019年10月15日火曜日

JRAのWEBページをpythonでスクレイピングしてみる(その1)

 ふとJRAの出走馬データやらオッズデータやらが欲しくなった。Winを使っているならばJRA-VANなどに登録して、JRAのデータを自分が使っているデータベース(SQLServer、Oracle、MySQL、PostgreSQL、SQLiteとか)に落としてくれるアプリケーションをダウンロードしてくれば割と簡単に入手できるのだが、いかんせん普段Winを使わないのでこの方法は用いたくない。

 そんなワケで最近よく使っているpythonを使ってJRAのWEBページからデータを取得することを試してみることにした。

 まずは、"hello, World"代わりに、以下の様なコードを実行してみた。



 ここで使用しているパッケージはrequests、beautifulsoup4で無い場合はあらかじめ用意しておく必要があるだろう。また、beautifulsoup4の中でlxmlを使用するので(BeautifulSoupオブジェクト作成時に’lxml’といパラメータを指定している為)lxmlも必要となる。

 成功するとJRAのトップベージがdata/jra_jp.htmlというファイルに保存された。正直なところファイル保存する前までの処理はブラウザにある「ページのソースを表示」と同じようなものなのでそれほど嬉しくない。

 保存されたファイル(data/jra_jp.html)からオッズのページへのリンクと思われる箇所を探してみると、以下の様な部分が見つかった。



 どうやらdoActionというJavaScript関数でページを遷移させている模様。通常こういった共通関数っぽいものは別ファイルに纏められて<script>タグなどによって読み込まれるので保存されたファイルには現れない。そこでブラウザのF12機能(F12キー押下で起動するからこう呼んでいるが、所謂"開発者ツール"というやつ)を使ってdoActionなる関数の定義を探す。第1引数のアドレスに対して第2引数をパラメータとしてサブミットしているらしい。

 トップページ以降のページはGETなりPOSTなりで取得する必要がありそうだが幸い、requestsには容易にこれらのリクエストを発行する機能が用意されている。

 getは既に使用したように引数にURLを渡すが、キーワード引数としてparams=辞書のような形で指定してやることによってサーバにパラメータを渡すことが出来る。

>>> d = {'name1':'value1', 'name2':'value2'}
>>> r = requests.get('http://localhost/get', params=d)

 リクエストがどの様にエンコードされたかを見るには以下の様にする。

>>> print(r.url)
u'http://localhost/get?name1=vlue1&name2=value2'

 postの場合はキーワード引数にdata=辞書のような形で指定する。
>>> d = {'name1':'value1', 'name2':'value2'}
>>> r = requests.post('http://localhost/post', data=d)

 requestsのクイック・スタートというドキュメントを見るとこの辺のことが色々書いてある。今回は使用しなかったがログインが必要なページなどにアクセスする際にはこのドキュメントに書かれていることが役に立つだろう。

 ここまでで大体の素材は揃ったので以下の様な関数を書いてみた。



 先に見つけた
<a href="#" onClick="doAction('/JRADB/accessO.html','pw15oli00/6D')">オッズ</a>

ならば、JRADoAction('/JRADB/accessO.html', 'pw15oli00/6D')を呼び出す事によって同等な処理を行うことになる(筈?(^^;))。
 ここまで結構、順調に事が進んで来たように書いてきたが色々あった。

 まず、実際のサイトで試す前にそのサイトのデータをごっそりファイルに落としてローカルなマシン上にapachなどのサーバを立て落としたファイルに擬似的にアクセスするようにした。実際のサイトでは開催前、開催レース中、開催レース後、開催後、開催後決定(これは自分的に名付けたので本当は別の名前があるのかも知れないが)日々または時々変わるのでモデル的なデータを固定しておかないと何が良いのか何が悪いのかが判りにくくなる。
 ローカルなサーバであるのでJRADoAction()を呼び出した時に発生したときのリクエストに対応するべき処理(所謂CGI)を 作って本物のサイトと(自分がリクエストする範囲内で)同じになるように調整した。

 それと、ここまで色々な邪念があった…意外と日々これ一つだけやってる訳ではないのでSeleniumとかを使えばもっと楽になるのではないか?とか、doActionの第二引数に指定される謎のキーワード(この場合は'pw15oli00/6D')は規則性があるみたいだからちょっと解析してみようか当等。意外とこれらは簡単ではなく挫折した。

 結局、まじめにトップのページからエレメントを取り出して、まじめにリクエストを発行するのが一番無難だという結論に達した次第。

 次に取り組む必要に迫られるのは、読み出したコンテンツから必要なエレメントを取り出す方法なのだが、これは次に書くことにする。