RubyとRで競馬分析

I-PADの自動投票

※注意 このスクリプトは、現在のI-PATに対応していません。※

JRAの電話投票会員(A-PATや即PAT)になると、パソコンを使ってインターネット経由で馬券を購入できるI-PATを利用できます。
I-PATでは、Internet ExplorerなどのWebブラウザを使って馬券の購入が出来ます。

I-PATの馬券購入サイトは、お世辞にも使いやすくありませんし、DataLab.などを使って予想をしたら、その流れでI-PATでの投票までできるのが理想です。
しかし、DataLab.のJVLinkのように、プログラムからI-PATを利用する仕組みは用意(公開)されていません。

この場合、I-PATの自動投票プログラムを作る主な方法には、(a)Internet ExplorerをCOM経由で制御する方法と、(b)I-PATのWebサーバと直接HTTP通信を行なう方法の2種類が考えられます。

(a)の方法は、人が行なう操作をプログラムで行なう方法です。プログラムは多少面倒になりますが、行なわれている操作がWebブラウザの画面で確認できるほか、操作の途中までを自動化して、その後の操作を人が引き継ぐことができるメリットがあります。

(b)の方法は、Webサーバ間に直接投票する情報を送る方法です。プログラムは簡単になりますが、公開されていないWebサーバとの通信プロトコルを解析する必要があります。

しかし、どちらの方法もJRA側でサイトの作りを少し変えただけでプログラムが正常に動作しなくなる可能性があるという問題があります。
また、お金の支払いが発生する操作なので、すべての操作を自動化するのは危険かもしれません。

私は、IEをCOM経由で制御する方法で、最後の支払い確認の手前までをRubyスクリプト「IPAD.rb」で自動化しています。

このスクリプトはあまり便利ではないかもしれません。購入するデータを、単純とはいえ、Rubyスクリプトの形式で用意しなければならないからです。しかも、このプログラムはI-PADのページで使われている文字列に強く依存しています。
このような構造になっているのは、私が馬券の購入にはそれほど興味がないからです。
私は競馬の計算はしても、馬券を実際に買うことはほとんどありません。私にはいろいろ考えること自体が楽しいのであって、馬券はその計算に緊張感を持たせる程度の意味しかないからです。

このスクリプトはI-PADのサイトを、JRA側が想定していない方法で操作しています。また、リアルなお金が関係する操作でもあります。利用する場合は、十分注意して、自己責任で使ってください。

メモ

IEをCOM経由で制御するには、IEで使われているインターフェイスと、インターフェイスでサポートされているメソッドとプロパティを調べる必要があります。この情報は、MicrosoftのMSDN Libraryで検索できます。主な情報は、次のページから辿っていけるでしょう。

Interfaces and Scripting Objects
http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/mshtml/reference/ifaces/interface.asp

WebBrowser Object
http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/webbrowser/reference/objects/webbrowser.asp

DWebBrowserEvents2 Interface
http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/webbrowser/reference/ifaces/dwebbrowserevents2/dwebbrowserevents2.asp

プログラムを作る上で困ったことは、I-PADのページはログイン後にIEの新しいウインドウを開くのですが、この新しいウインドウをRuby側で制御する方法が見付からなかったことです。

一応、IWebBrowserインターフェイスにNewWindow2イベントを登録することで新規ウインドウの作成を捕捉できるのですが、アプリ側で用意したIEオブジェクトを使わせるにはIEオブジェクトのIDispatchインターフェイスが必要なのですが、RubyのWIN32OLEでOLEオブジェクトのIDispatchインターフェイスを取得する方法が分からなかったのです。

これには本当に困ったのですが、よく考えると、COM経由でIEを制御するだけでなく、読み込んだHTMLを書き換えることも可能なことを思い出しました。
というわけで、FRAMEタグのtargetを_selfに書き換えることで、新しいウインドウに読み込ませないようにして解決しました。また、Javascriptで新規ウインドウを作っていますが、これはNewWindow2イベントでキャンセルするようにしています。

ちなみに、スクリプトの作成にはIPAD体験サイトhttp://www.jra.go.jp/IPAT_TAIKEN/が役に立ちました。
ただし、体験サイトと本物のサイトでは、プログラム的に異なる部分もあります。

投票データ

購入する馬券のデータは、「IPAD_tohyo.rb」ファイルに、次のような形式で用意することにします。
コードを見れば分かりますが、これはRubyスクリプトそのものです。このコードは、IPAT投票入力 ページに移動した後に実行されます。

IPAD_tohyo.rb

# 場所, レース, 式別, 方式, 金額(100円単位), 馬番(枠番)1, 馬番(枠番)2, 馬番(枠番)3, 馬番(枠番)4
tohyo('中山(日)', '12', '馬単', '通常', "1", "02", "04", nil, nil)

「場所→レース→式別→方式→金額→馬券の種類→セット ボタンをクリック」の順番に設定します。
これらの設定はJavascriptで制御されているので、金額以外の順番を変更することは出来ません。
設定する文字列の全角半角は、ページで使われている文字と厳密に一致していなければなりません。

場所は、「場所名」+「曜日」です。
場所名は、「"札幌","函館","福島","新潟","東京","中山","中京","京都","阪神","小倉"」のいずれかです。
曜日は、「"(日)","(月)","(火)","(水)","(木)","(金)","(土)"」のいずれかです。

レースは、「"1"〜"12"」(有効なレース番号まで)です。数字ではなく、文字列で指定します。

式別、方式、馬番(枠番)は、次のように指定します。
馬番(枠番)は、数字ではなく、文字列で指定します。また、枠連だけは、"1"〜"8"で指定し、それ以外は"01"〜"13"で指定します。指定する必要がない場合は、nilを指定します。

式別 方式 馬番 ---- ---- ---- 備考
"単勝" "通常" "01"〜"13" nil nil nil
式別 方式 馬番 ---- ---- ---- 備考
"複勝" "通常" "01"〜"13" nil nil nil
式別 方式 枠番1 枠番2 ---- ---- 備考
"枠連" "通常" "1"〜"8","99" "1"〜"8","99" nil nil "99"は、ゾロ目
"ながし" 相手1 相手2 相手3
"1"〜"8" "1"〜"8" "1"〜"8",nil "1"〜"8",nil
"ボックス" 枠番1 枠番2 枠番3 枠番4
"1"〜"8" "1"〜"8",nil "1"〜"8",nil "1"〜"8",nil
"フォーメーション" 枠1 (1) 枠1 (2) 枠2 (1) 枠2 (2)
"1"〜"8" "1"〜"8",nil "1"〜"8" "1"〜"8",nil
式別 方式 馬番1 馬番2 ---- ---- 備考
"馬連" "通常" "01"〜"13" "01"〜"13" nil nil
"ながし" 相手1 相手2 相手3
"01"〜"13" "01"〜"13" "01"〜"13",nil "01"〜"13",nil
"ボックス" 馬番1 馬番2 馬番3 馬番4
"01"〜"13" "01"〜"13" "01"〜"13",nil "01"〜"13",nil
"フォーメーション" 馬1 (1) 馬1 (2) 馬2 (1) 馬2 (2)
"01"〜"13" "01"〜"13",nil "01"〜"13" "01"〜"13",nil
式別 方式 馬番1 馬番2 ---- ---- 備考
"ワイド" "通常" "01"〜"13" "01"〜"13" nil nil
"ながし" 相手1 相手2 相手3
"01"〜"13" "01"〜"13" "01"〜"13",nil "01"〜"13",nil
"ボックス" 馬番1 馬番2 馬番3 馬番4
"01"〜"13" "01"〜"13" "01"〜"13",nil "01"〜"13",nil
"フォーメーション" 馬1 (1) 馬1 (2) 馬2 (1) 馬2 (2)
"01"〜"13" "01"〜"13",nil "01"〜"13" "01"〜"13",nil
式別 方式 1着 2着 ---- ---- 備考
"馬単" "通常" "01"〜"13" "01"〜"13" nil nil
"1着ながし" 1着軸 相手1 相手2 相手3
"01"〜"13" "01"〜"13",nil "01"〜"13",nil "01"〜"13",nil
"2着ながし" 2着軸 相手1 相手2 相手3
"ボックス" 馬番1 馬番2 馬番3 馬番4
"01"〜"13" "01"〜"13" "01"〜"13",nil "01"〜"13",nil
"フォーメーション" 1着 (1) 1着 (2) 2着 (1) 2着 (2)
"01"〜"13" "01"〜"13",nil "01"〜"13" "01"〜"13",nil
式別 方式 1着 2着 3着 ---- 備考
"3連複" "通常" "01"〜"13" "01"〜"13" "01"〜"13" nil
"軸1頭ながし" 相手1 相手2 相手3
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"軸2頭ながし" 軸1 軸2 相手1 相手2
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"ボックス" 馬番1 馬番2 馬番3 馬番4
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"フォーメーション" 馬1 馬2 馬3 (1) 馬3 (2)
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
式別 方式 1着 2着 3着 ---- 備考
"3連単" "通常" "01"〜"13" "01"〜"13" "01"〜"13" nil
"1着ながし" 1着軸 相手1 相手2 相手3
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"2着ながし" 2着軸 相手1 相手2 相手3
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"3着ながし" 3着軸 相手1 相手2 相手3
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"1,2着ながし" 1着 2着 3着 (1) 3着 (2)
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"1,3着ながし" 1着 2着 (1) 2着 (2) 3着
"01"〜"13" "01"〜"13" "01"〜"13",nil "01"〜"13"
"2,3着ながし" 1着 (1) 1着 (2) 2着 3着
"01"〜"13" "01"〜"13",nil "01"〜"13" "01"〜"13"
"ボックス" 馬番1 馬番2 馬番3 馬番4
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil
"フォーメーション" 1着 2着 3着 (1) 3着 (2)
"01"〜"13" "01"〜"13" "01"〜"13" "01"〜"13",nil

どの設定でも、その時点においてページ上で選択できない設定はエラーになります。

使い方

IPAD.rbの先頭で定義している加入者番号、INET-ID、P-ARS番号を自分の値に書き換えます。初期状態では空になっています。

購入したい馬券のデータを、IPAD_tohyo.rbに用意します。

IPAD.rbを実行します。オプションはありません。

>ruby IPAD.rb

スクリプトを実行すると、Internet Explorerの画面が開き、I-PADのサイトが読み込まれて、自動的に処理が進んでいきます。

エラーなく実行されると、最後にIPAT合計金額入力のページが開きます。
このページには、加入者番号、暗証番号、P-ARS番号、合計金額を入力しますが、スクリプトでは加入者番号とP-ARS番号だけを設定するので、暗証番号と合計金額は自分で入力し、投票ボタンをクリックしてください。

スクリプト自体はこの段階で終了しているので、あとはI-PADページを通常通り利用してください。

IPAD.rb

#I-PADで自動投票

require 'win32ole'

#******************************************************
#加入者番号
NUMBER = ''
print "加入者番号 = ", NUMBER, "\n"

#INET-ID
INETID = ''
print "INET-ID    = ", INETID, "\n"

#P-ARS番号
PARS = ''
print "P-ARS番号  = ", PARS, "\n"
#******************************************************

#定数をロードする名前空間を用意するために InternetExplorer モジュールを定義
module InternetExplorer
end

#Internet Explorerを起動する
$ie = WIN32OLE.new("InternetExplorer.Application")
$ie.visible = true

#Internet Explorerの定数をロードする
WIN32OLE.const_load($ie, InternetExplorer)

#Internet Explorerのイベント オブジェクトを作る
event = WIN32OLE_EVENT.new($ie, "DWebBrowserEvents2")

#新規ウィンドウのイベントを受け取り、新規ウィンドウをキャンセルする
event.on_event_with_outargs("NewWindow2") {|ppdisp, cancel, args|
  args[1] = true
}

#**メソッド定義****************************************
#ビジー ループ
def ie_busy_loop(title)
  while ($ie.document.title == title) \
        || $ie.busy \
        || ($ie.ReadyState != InternetExplorer::READYSTATE_COMPLETE)
    WIN32OLE_EVENT.message_loop
  end
end

#コントロール取得(nameで探す)
def ie_tag_item(tagname, itemname)
  control = nil
  collection = $ie.document.all.tags(tagname)
  collection .each { |coll|
    control = coll if coll["name"] == itemname
  }
  print tagname, ":", itemanem, " が見付かりません。" if control == nil
  return control
end

#コントロール取得(valueで探す)
def ie_tag_item_value(tagname, itemvalue)
  control = nil
  collection = $ie.document.all.tags(tagname)
  collection .each { |coll|
    control = coll if coll["value"] == itemvalue
  }
  print tagname, ":", itemvalue, " が見付かりません。" if control == nil
  return control
end

#コントロール取得(name, valueで"INPUT"を探す)
def ie_item_name_value(itemname, itemvalue)
  control = nil
  collection = $ie.document.all.tags("INPUT")
  collection .each { |coll|
    control = coll if (coll["name"] == itemname) && (coll["value"] == itemvalue)
  }
  print "INPUT:name=", itemname,  ":value=" + itemvalue, " が見付かりません。" if control == nil
  return control
end

#コンボボックス 選択
def ie_select_value(control, value)
  collection = control.options
  count = 0
  while count < collection.length
    option = collection.item(count)
    if option.innerText == value
      control.selectedIndex = count
      control.fireEvent("onchange") # Javascriptの関数を実行
      return false
    end
    count = count + 1
  end
  print value, "を選択できません。"
  return true
end
#******************************************************

#I-PAD投票トップページを開く
#$ie.navigate("http://www.jra.go.jp/IPAT_TAIKEN/pw_010_i.html?")   # テスト用
$ie.navigate("https://www.ipat.jra.go.jp/")
WIN32OLE_EVENT.message_loop while $ie.busy || ($ie.ReadyState != InternetExplorer::READYSTATE_COMPLETE)

#ログイン ページ
if $ie.document.title != 'IPAT投票ログイン'
  print 'スクリプトで想定している IPAT投票ログイン ページではありません。投票の申込みを受け付けていない時間かもしれません。'
  exit
end

#別ウインドウを開かないようにする
form = $ie.document.all.tags("FORM").item(0);
form.target = "_self"

#IPAD-ID入力欄を探す
txt = ie_tag_item("INPUT", "inetid")
exit if txt == nil
txt.Value = INETID

# ログイン ボタンを探す
button = ie_tag_item_value("INPUT", 'ログインする')
exit if button == nil

#ログイン ボタンをクリック
button.click
ie_busy_loop('IPAT投票ログイン')

#重要なお知らせ ページ
if $ie.document.title == 'IPATお知らせ'
  #OK ボタンを検索する
  button = ie_tag_item_value("INPUT", ' O K ')
  exit if button == nil

  #OK ボタンをクリック
  button.click
  ie_busy_loop('IPATお知らせ')
end


#IPAT投票メインメニュー ページ
if $ie.document.title != 'IPAT投票メインメニュー'
  print '想定している IPAT投票メインメニュー ページではありません。'
  exit
end

#投票入力 ボタンを探す
button = ie_tag_item_value("INPUT", ' 投票入力 ')
exit if button == nil

#投票入力 ボタンをクリック
button.click
ie_busy_loop('IPAT投票メインメニュー')

#IPAT投票入力 ページ
if $ie.document.title != 'IPAT投票入力'
  print '想定している IPAT投票入力 ページではありません。'
  exit
end

#場名   name='JYO'
#「"札幌","函館","福島","新潟","東京","中山","中京","京都","阪神","小倉"のいずれか」
#+「"(日)","(月)","(火)","(水)","(木)","(金)","(土)"のいずれか」
$jyo = ie_tag_item("SELECT", 'JYO')
exit if $jyo == nil

#レース name='Race'
#「1〜12、全レース」
$race = ie_tag_item("SELECT", 'Race')
exit if $race == nil

#式別   name='Siki'
#「"単勝","複勝","枠連","馬連","ワイド","馬単","3連複","3連単"」
$siki = ie_tag_item("SELECT", 'Siki')
exit if $siki == nil

#方式   name='Kumi'
#"通常","ながし","ボックス","フォーメーション"
#"通常","1着ながし","2着ながし","ボックス","フォーメーション"
#"通常","軸1頭ながし","軸2頭ながし","ボックス","フォーメーション"
#"通常","1着ながし","2着ながし","3着ながし","1,2着ながし","1,3着ながし","2,3着ながし","ボックス","フォーメーション"
$kumi = ie_tag_item("SELECT", 'Kumi')
exit if $kumi == nil

#金額(100円単位) name='maisuu'
$maisuu = ie_tag_item("INPUT", 'maisuu')
exit if $maisuu == nil

#セット name='MS'
$set_button = ie_tag_item("INPUT", 'MS')
exit if $set_button == nil

#**投票用関数*******************************
def button_click(itemvalue, itemname)
  return if itemname == nil
  button = ie_item_name_value(itemvalue, itemname)
  exit if button == nil
  button.click
end

# 場所, レース, 式別, 方式, 金額(100円単位), 馬番(枠番)1, 馬番(枠番)2, 馬番(枠番)3, 馬番(枠番)4
def tohyo(jyo_name, race_name, siki_name, kumi_name, maisuu_txt, uma_1, uma_2, uma_3, uma_4)
  # 場所
  exit if ie_select_value($jyo, jyo_name)

  # レース
  exit if ie_select_value($race, race_name)

  # 式別
  exit if ie_select_value($siki, siki_name)

  # 方式
  if kumi_name == '通常'
    exit if ie_select_value($kumi, '通常       ')
  else
    exit if ie_select_value($kumi, kumi_name)
  end

  #金額(100円単位)
  $maisuu.value = maisuu_txt

  #投票選択
  if (siki_name == "単勝") || (siki_name == "複勝")
    if kumi_name == "通常"
      button_click("uma", uma_1)
    else
      print "不明な方式(", kumi_name, ")\n"
      exit
    end
  elsif (siki_name == "枠連") || (siki_name == "馬連") || (siki_name == "ワイド") || (siki_name == "馬単")
    if kumi_name == "通常"
      if siki_name != "馬単"
        button_click("uma", uma_1)
        button_click("uma", uma_2)
      else
        #1着
        button_click("uma", uma_1)
        #2着
        button_click("umb", uma_2)
      end
    elsif  kumi_name == "ながし"     #馬単なし
      #軸
      button_click("uma", uma_1)
      #相手
      button_click("umb", uma_2)
      if (siki_name == "馬連") || (siki_name == "ワイド")
        button_click("umb", uma_3)
        button_click("umb", uma_4)
      end
    elsif kumi_name == "1着ながし"  #馬単のみ
      #1着軸
      button_click("uma", uma_1)
      #相手
      button_click("umb", uma_2)
      button_click("umb", uma_3)
      button_click("umb", uma_4)
    elsif kumi_name == "2着ながし"  #馬単のみ
      #2着軸
      button_click("uma", uma_1)
      #相手
      button_click("umb", uma_2)
      button_click("umb", uma_3)
      button_click("umb", uma_4)
    elsif  kumi_name == "ボックス"
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      button_click("uma", uma_3)
      button_click("uma", uma_4)
    elsif  kumi_name == "フォーメーション"
      #枠1(枠連) or 馬1(馬連,ワイド) or 1着(馬単)
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      #枠2(枠連) or 馬2(馬連,ワイド) or 2着(馬単)
      button_click("umb", uma_3)
      button_click("umb", uma_4)
    else
      print "不明な方式(", kumi_name, ")\n"
      exit
    end
  elsif siki_name == "3連複"
    if kumi_name == "通常"
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      button_click("uma", uma_3)
    elsif kumi_name == "軸1頭ながし"
      #軸
      button_click("uma", uma_1)
      #相手
      button_click("umb", uma_2)
      button_click("umb", uma_3)
      button_click("umb", uma_4)
    elsif kumi_name == "軸2頭ながし"
      #軸
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      #相手
      button_click("umb", uma_3)
      button_click("umb", uma_4)
    elsif kumi_name == "ボックス"
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      button_click("uma", uma_3)
      button_click("uma", uma_4)
    elsif kumi_name == "フォーメーション"
      #馬1
      button_click("uma", uma_1)
      #馬2
      button_click("umb", uma_2)
      #馬3
      button_click("umc", uma_3)
      button_click("umc", uma_4)
    else
      print "不明な方式(", kumi_name, ")\n"
      exit
    end
  elsif siki_name == "3連単"
    if kumi_name == "通常"
      #1着
      button_click("uma", uma_1)
      #2着
      button_click("umb", uma_2)
      #3着
      button_click("umc", uma_3)
    elsif (kumi_name == "1着ながし") || (kumi_name == "2着ながし") || (kumi_name == "3着ながし")
      #1着軸 or 2着軸 or 3着軸
      button_click("uma", uma_1)
      #相手
      button_click("umb", uma_2)
      button_click("umb", uma_3)
      button_click("umb", uma_4)
    elsif kumi_name == "1,2着ながし"
      #1着
      button_click("uma", uma_1)
      #2着
      button_click("umb", uma_2)
      #3着
      button_click("umc", uma_3)
      button_click("umc", uma_4)
    elsif kumi_name == "1,3着ながし"
      #1着
      button_click("uma", uma_1)
      #2着
      button_click("umb", uma_2)
      button_click("umb", uma_3)
      #3着
      button_click("umc", uma_4)
    elsif kumi_name == "2,3着ながし"
      #1着
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      #2着
      button_click("umb", uma_3)
      #3着
      button_click("umc", uma_4)
    elsif kumi_name == "ボックス"
      button_click("uma", uma_1)
      button_click("uma", uma_2)
      button_click("uma", uma_3)
      button_click("uma", uma_4)
    elsif kumi_name == "フォーメーション"
      #1着
      button_click("uma", uma_1)
      #2着
      button_click("umb", uma_2)
      #3着
      button_click("umc", uma_3)
      button_click("umc", uma_4)
    else
      print "不明な方式(", kumi_name, ")\n"
      exit
    end
  else
    print "不明な式別(", siki_name, ")\n"
    exit
  end

  # セット ボタンをクリック
  $set_button.click
end
#*******************************************

#*******************************************
require 'IPAD_tohyo.rb'
#*******************************************

#入力終了 name ='submit3' ボタンをクリックする
submit_button = ie_tag_item("INPUT", 'submit3')
exit if submit_button == nil
submit_button.click
ie_busy_loop('IPAT投票入力')

#IPAT合計金額入力 ページ
if $ie.document.title != 'IPAT合計金額入力'
  print 'スクリプトで想定している IPAT合計金額入力 ページではありません。'
  print $ie.document.title
  exit
end

#加入者番号 name="i"
txt = ie_tag_item("INPUT", "i")
txt.value = NUMBER

#P-ARS番号  name="r"
txt = ie_tag_item("INPUT", "r")
txt.value = PARS

#暗証番号(name="p")、合計金額(name="s")は手入力する
#投票(value="投    票")ボタンは自分で押す

Creative Commons License
この作品は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

(C) 2006 NARITA, Takuro
E-Mail:narita@a1.mbn.or.jp