2014年7月23日水曜日

一つのレースだけが中止になることもある

Yahoo競馬からデータの取得を続けていると、なぜか、解析できないレースがあった。それにアクセスしてみると、出馬表にリダイレクトされる。転送先の出馬表を見てみると、「強風のため中止」とある。で、このレースは、「第126回中山大障害(J・GI)」。他のレースはできるが、このレースは障害レースであり、他の平地のレースは強風でもできたということだ。

HttpGet hg = new HttpGet(url);
List<Header> headers = new ArrayList<Header>();
headers.add(new BasicHeader("Accept-Charset","utf-8"));
headers.add(new BasicHeader("User-Agent", "HorseRaceAnalyzer"));
RequestConfig config = RequestConfig.custom().setRedirectsEnabled(false).build();
HttpClient hc = HttpClientBuilder.create()
.setDefaultRequestConfig(config)
.setDefaultHeaders(headers).build();

HttpResponse hr = hc.execute(hg);
int status = hr.getStatusLine().getStatusCode();
if (status != HttpStatus.SC_OK) {
return null;
}
return EntityUtils.toString(hr.getEntity(), "UTF-8");

Webページをhttp-getする際に、リダイレクトを止めることにした。そして、このレースのデータはエラーとして取り扱い、データとして取得しないことにする。その部分を含めて、http-getの部分のソースは上記。

2014年7月22日火曜日

例外的なデータ

Webページを解析するで、書いたように、Yahoo競馬のWebページをもとに競馬のデータを取得しようとしていて、前後の文字列を使用した。これで、データを取れるのだが、レースによってエラーが出る。すべてのデータを確認して、仕様を予測したのではないので、例外的なデータの場合があるからだ。まずややこしいのは払戻金の情報。昔は、三連単は2004年から三連複は2002年から始まったので、それより昔は当然ない。馬連は8頭立て以下では存在しないとか、競馬をわかっている人には当たり前のことではあるが、ややこしい。同着も存在する。それらを考慮してデータ構造を作った上で、取り出さなければならない。リスト型を使い、ない場合を含めていくつになることもありうるようにした。

他、競争中止になったときには、着順やタイム、上がり3ハロンなどの情報もなくなる。着順には数字が書かれるかわりに中止書かれている。競争除外になれば、それに加えて、オッズや馬体重、人気、通過順位もなくなることになる。値が空欄になったり、"-"になったりする。エラーが出るごと、これらを受け入れられるように修正した。

降着があるのも注意点。"5(3)"のように書かれている。こう書かれている場合は、もともとは3着だったのが5着に変更されたということのようだ。降着の取り扱いをデータ上どうするか悩んだが、そのレースの他の馬については降着で訂正される前の着順は書かれていない。なので、降着を反映した後のデータを取り扱うのがいいのだろう。なので、上記の例では、(3)の部分は捨てることになる。

 String[] strorders = strorder.split("\\(");
 horse.order = Integer.parseInt(strorders[0].replaceAll("[^0-9]", ""));

上記のようにして、括弧の前の部分だけを取り出し。"("でsplitするときは、"\\"を前につける必要がある。このことを最初知らずに少しだけはまった。

2014年7月21日月曜日

Webページを解析する(2)

Webページからデータを取得するということは、表示上の値を取り扱わないと行けないということである。払戻金は、たとえば、1,020円のように、3桁ごとに","が打たれている。これらは取り除かないと行けない。

 Integer.parseInt(money.replaceAll("[^0-9]", ""));

上記のようにString#replaceAllを使うことで、数字だけにした上で、intに変換できる。

また、タイムは1.09.6のような形で記述されている。最小の単位が0.1秒であるため、これをベースとした整数値として取り扱うことにし、以下のように変換した。

 String[] times = time.split("\\.");
 horse.time = Integer.parseInt(times[0]) * 600 + Integer.parseInt(times[1]) * 10 + Integer.parseInt(times[2]);

"."でsplitするときは、"\\"でエスケープする必要がある。ところで、Macでは、¥と\は区別される。"\"は[option]+[¥]で入力できる。

2014年7月20日日曜日

Webページを解析する

Yahoo競馬からデータを取得すると決めた。APIが用意されているわけではないので、HTMLを解析し、必要な部分を抜き出さなくてはならない。

public HtmlAnalyzer(String html) {
buf = new StringBuffer(html); 

}
public String getData(String begin, String end) {
int ibegin = buf.indexOf(begin);
if (ibegin == -1) return null;
ibegin += begin.length();
buf.delete(0, ibegin);
int iend = buf.indexOf(end);
if (iend == -1) return null;
String ret = buf.substring(0, iend);
buf.delete(0, iend + 1);
return ret;

}

上記のように、HTMLをStringBufferに入れて、解析した部分から消していくことにした。解析として、ある文字列とある文字列の間の文字列を取り出す関数を作った。そして、HTMLのソースファイルを見ながら、必要な部分の前後の文字列を切り出し、上記関数getDataの引数として与えるようにした。
<p id="raceTitDay" class="fntSS">2000年1月5日(水) <span>|</span> 1回中山1日 <span>|</span> 10:00発走</p> 
HTMLのソースファイルで、日付と開催日と場所の情報は上記のように書かれている。そこで、以下のようにgetDataを呼び出してデータを取得するようにした。

HtmlAnalyzer ha = new HtmlAnalyzer(html);
Race race = new Race();
race.horses = new ArrayList<Horse>();
String date = ha.getData("<p id=\"raceTitDay\" class=\"fntSS\">", "<span>");
SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日");
race.date = format.parse(date);
race.termplaceday = ha.getData("</span>", "<span>").trim();

多くのデータがあるため、かなり面倒だが、基本的には同じように書いて行けばよい。難しいのは、どこまで可変でどこまで固定なのかということだ。HTML自体は、データを埋め込んで自動生成しているのだろうから、基本的には同じ枠組みで作られているはずだ。しかし、どこが埋め込み部分なのか判断が難しい場合もあるので、試行錯誤が必要だろう。

2014年7月19日土曜日

どのプログラミング言語で作るか

久しぶりに、競馬予想ソフトを作ると決めたものの、どのプログラミングで作るのがいいのか。それを決めるための最も大きな要件は、どのデバイスで使うのか、ということになるだろう。後は、自分の得意・不得意もあるし、開発工数が少なくてすむというのもある。昔作ったときは、確か、C#で作ったのだが、そのときはWindowsで動かすことしか考えていなかったし、サーバー側で処理することも考えていなかった。今回の要件は以下のようになるだろうか。


  • 開発環境 MacBook Air + 無料の開発環境
  • Androidから見られるようにしたい
  • 最初はMac上でいろいろ試したい
  • 完成したもののロジックはクラウドで(Google App Engine?)
Macで使うことを考えると、Objective CもしくはSwiftという選択肢がある。速度的にはこれが一番早いだろう。他のデバイスで動かすことには適さない。マルチプラットフォームという話では、HTML/JavaScriptがいいのだろうな。無料で開発できるし。node.jsを動かしたようなクラウドサービスを使えば、これをクラウド側に展開することも可能だ。しかし、Google App Engineは対応していない。これからはHTML5だ、とか、口では言うのだが、あまり優秀な開発環境がないからか、書きにくくて好きではない。UI部分はまだしもロジックを書くのには向かないように思う。結局、Javaを使うか。これなら、Google App Engineも対応しているので最終的にロジック部分を全部移行することが可能だろうし、Eclipseで開発ができる。データベースをどうするかだが、めんどくさいので、最初のうちは、計算時は全部メモリ上において動作させるか、富豪プログラミング的に。最近はoracleとかのデータベースもインメモリが使われているしな。毎回、Yahooのページからデータを取るわけに行かないから、取ってきたデータをメモリ展開したものをファイルに保存することにする。

2014年7月18日金曜日

競馬のデータ

競馬の予想アプリを作ろうで書いたように、JRA-VANから生データは手に入れられないし、MAC用のSDKは現在はないようである。でも、データがないことには、どうもならん。ということで、何らか方法を考える。APIを提供してくれているところないかなー、と思ったが、さすがにそれはないようだ。しかし、競馬の結果を見ることができるWebサイトはいくつかある。そのデータを利用することにする。どこのサイトにしようかと思ったが、Yahooの競馬にすることに決めた。どこでも、大差ないと思うが、大手の安心感というやつか。で、当たり前だが、仕様書があるわけではない。したがって、仕様を推測しながら作ることになる。まずはURL。
結果のサイトは、

http://keiba.yahoo.co.jp/race/result/1402020101/

のようなURLになっている。どうもこれは、

http://keiba.yahoo.co.jp/race/result/[year - 2000][競馬場ID][x回][x日][xレース]/

という形式らしい。競馬場IDは、

01:札幌, 02:函館, 03:福島, 04:新潟, 05:東京, 06:中山, 07:中京, 08:京都, 09:阪神, 10:小倉

のようだ。昔、JRA-VANのデータを使ったときも同じだった気がするな。0はなくて10まで。ということで、この形式でURLを生成すれば、結果のページを取得できる。対応するレースが存在しない場合は、404エラーが帰ってくるので、404エラーが帰ってくるまで繰り返せば、すべてのデータを取ることが可能だ。

2014年7月17日木曜日

競馬の予想アプリを作ろう

完全にほったらかしになっていたのだが、少しずつ書いていきたい。

GWに友人に連れられてひさしぶりに競馬に行った。天皇賞含め、いくつかのレースを買ったのだが、惨敗。ひとつも取れず。昔は、自身で予想ソフトを作って、それをもとに予想していたが、新聞だけを読んで予想も楽しいものだった。

その後、競馬はしてないのだが、今、予想ソフトを作ってみると当時と違うことが出来るだろうか、と思い出した。で、作ってみることにした。

まず、データをどこからか取らなければ行けない。昔は、たしかnifty経由でJRA-VANからデータが取れたんだったっけ。しかし、今は違う。JRA-VANのサイトを見ると、JRA-VANデータラボの会員になる必要があるらしい。これは、月額2,052円(こっちのページには1,995円になっている、消費税が5%->8%であがってこちらは修正漏れか)。高いと言えば高いが、まあ、それで楽しめるのであればいいと思ったのだが、このサービスはデータをくれるのではなく、SDKベースでデータにアクセスできるようになるものらしい。

しかし、このSDKはMacで使えなさそうである。Q&Aでは、



Macintosh版のSDK(JV-Link)はありますか?
JRA-VAN SDKの中にMacintosh版も含まれております。
Macintosh版のJV-Linkは、Carbonインターフェースとして作成されており CodeWarriorやOS標準でバンドルされているXcodeで開発可能です。
と書かれているのだが、JV-Link質問箱では、

申し訳ございませんが、Mac版のJV-Link、SDKの更新は
現在行っておらず、Cocoaによる開発にも対応しておりません。


になっている。ということで、現在は使えないのだろう。どちらにしろ、Android版はなさそうだ。SDKベースではなくデータそのものを配布してくれればいいのに。