JVDataのレコードとレコードフォーマット

JVDataレコードの例

JVGets()で取得した競馬データJVDataのレコードは、次のような文字コードShift-JISのByte型配列のデータ構造をしています。RAVM2015049920150831173848.jvdという実際のjvdファイルのレコードの1つを載せます。

82 65 55 50 48 49 53 48 52 48 54 50 48 49 53 48 52 48 52 48 54 48 51 48 51 48 57 49 48 48 48 48 142 82 144 129 143 220 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 89 65 77 65 66 85 75 73 32 83 72 79 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 142 82 144 129 143 220 129 64 129 64 129 64 129 64 129 64 129 64 129 64 142 82 144 129 143 220 129 64 129 64 129 64 142 82 144 129 143 220 48 48 48 48 69 32 49 50 65 48 52 51 48 48 48 48 48 53 48 48 48 48 48 48 48 48 53 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 129 64 50 50 48 48 48 48 48 48 49 56 48 48 66 32 32 32 48 48 49 48 48 48 48 48 48 48 48 52 48 48 48 48 48 48 48 50 53 48 48 48 48 48 48 49 53 48 48 48 48 48 48 49 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 50 55 51 48 48 48 48 48 48 55 56 48 48 48 48 48 48 51 57 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 49 52 51 53 48 48 48 48 49 50 49 50 49 50 50 49 48 49 50 55 49 49 51 49 49 55 49 50 55 49 50 55 49 50 56 49 51 48 49 50 49 49 49 55 49 49 53 49 49 54 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 51 53 55 52 56 52 51 52 56 52 54 57 49 49 49 48 45 50 45 49 50 40 55 44 49 49 41 45 57 40 49 44 53 41 40 56 44 54 41 45 52 45 51 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 50 49 49 48 45 50 45 49 50 45 49 49 44 55 44 57 40 49 44 53 41 45 40 56 44 54 41 45 52 45 51 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 51 49 49 48 44 50 40 49 50 44 49 49 41 57 40 55 44 49 44 53 41 54 40 56 44 52 41 51 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 52 49 40 49 48 44 42 50 44 49 50 41 49 49 40 55 44 49 44 53 41 57 45 40 56 44 54 44 52 41 51 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 48 13 10 0

このレコードをデコードして文字に直すとこうなります。

RA720150406201504040603030910000山吹賞                                                                                       YAMABUKI SHO                                                                                                                                                                                                                                                                                                                                                            山吹賞       山吹賞   山吹賞0000E 12A043000005000000005                              220000001800B   00100000000400000002500000015000000100000000000000000000000000000000000000000000000000000000000000002730000007800000039000000000000000000000000000000000000000001435000012121221012711311712712712813012111711511600000000000000000000000000000000000000000000003574843484691110-2-12(7,11)-9(1,5)(8,6)-4-3                                         2110-2-12-11,7,9(1,5)-(8,6)-4-3                                         3110,2(12,11)9(7,1,5)6(8,4)3                                            41(10,*2,12)11(7,1,5)9-(8,6,4)3                                         0 

JVDataはこのように固定長の1次元テキストのレコードになっていて、こういうレコードを約100から10000個を1つのjvdファイルにまとめて、1986年から現在までのjvdファイルが4000ほどあります。新しいレースが開催されるとJRAが新たなjvdファイルを配信します。

文字コードShift-JISをデコードするには、文字コード表 シフトJISのようなマップを使います。具体的には次のVBAの関数を使ってデコードしました。

' 文字コードShift-JISのByte型配列EncodedChars()のインデックスPosition - 1からPosition + Length - 1個の要素をデコードしてUnicode文字列を返す
' 1 <= Position, Length <= Ubound(EncodedChars)
Function DecordShiftJIS(ByRef EncodedChars() As Byte, ByRef Position As Long, ByRef Length As Long) As String

  DecordShiftJIS = StrConv(MidB(EncodedChars, Position, Length), vbUnicode)

End Function

JVGetsメソッドで取得したByte型配列のレコードRecord()を丸ごとデコードするのにこのDecordShiftJIS関数を使ってDecorded = DecordShiftJIS(Record, 1, UBound(Record))としました。

レコードの解読

レコードの先頭2 Bytesはレコード種別IDになっていて、そのレコードがどのレコードフォーマットのものかわかるようになっています。上記の例のレコードだと、レコード種別IDはRAです。

RAというレコード種別IDがどんなレコードの種類かというと、JVData仕様書を読むと、レース詳細という情報だということがわかります。

同じくJVData仕様書にRAレコードのレコードフォーマットが載っていて、何の項目のデータがレコードのどの位置にあるのかを知ることができます。

RAレコードのレコードフォーマットをJVData仕様書から抜粋します。下表はJVData仕様書.pdfのレコードフォーマット表をTabula: Extract Tables from PDFsを使ってcsvファイルに変換してExcelワークシートに貼り付けて編集したものです。Excelからhtmlへの変換はCreate LaTeX tables onlineを使いました[1]

親項目名子項目名位置(Bytes)繰返回数長さ(Bytes)
レコード種別ID12
データ区分31
データ作成年月日48
開催年124
開催月日164
競馬場コード202
開催回222
開催日目242
レース番号262
曜日コード281
特別競走番号294
競走名本題3360
競走名副題9360
競走名カッコ内15360
競走名本題欧字213120
競走名副題欧字333120
競走名カッコ内欧字453120
競走名略称10文字57320
競走名略称6文字59312
競走名略称3文字6056
競走名区分6111
重賞回次6123
グレードコード6151
変更前グレードコード6161
競走種別コード6172
競走記号コード6193
重量種別コード6221
競走条件コード2歳条件6233
競走条件コード3歳条件6263
競走条件コード4歳条件6293
競走条件コード5歳以上条件6323
競走条件コード最若年条件6353
競走条件名称63860
距離6984
変更前距離7024
トラックコード7062
変更前トラックコード7082
コース区分7102
変更前コース区分7122
本賞金71478
変更前本賞金77058
付加賞金81058
変更前付加賞金85038
発走時刻8744
変更前発走時刻8784
登録頭数8822
出走頭数8842
入線頭数8862
天候コード8881
芝馬場状態コード8891
ダート馬場状態コード8901
ラップタイム891253
障害マイルタイム9664
前3ハロン9703
前4ハロン9733
後3ハロン9763
後4ハロン9793
コーナー通過順位982472
コーナー通過順位コーナー11
コーナー通過順位周回数21
コーナー通過順位各通過順位370
レコード更新区分12701
レコード区切12712

RAレコードは1272 Bytesあります。色々と項目があって、最後の2 Bytesはレコード区切りのための改行コードCrLfです。1レコードが1レースに対応していて、いつ、どこで開催されたレースかわかるように項目 開催年開催月日競馬場コード開催回開催日目レース番号のデータがあります。

また、レースに名前がある場合は競走名本題データがあったり、グレードを表すグレードコード、競争条件を表す競争条件コード本賞金出走頭数芝馬場状態コードなどのレースの条件を表す項目があります。

レコード種別IDが全部で37個あるので、レコードフォーマットも37個あります。1つのレコードフォーマットは1つのレコード種別IDのレコードの項目と位置を定義しています。

レコードフォーマットに従ってByte型配列のバイナリデータを切り出して、デコードすればテキストデータを抽出することができます。

例えば、開催年は位置12 Bytesで長さ4 Bytesなので、Shift-JISのバイナリデータとデコードした文字列はこうなります。

' バイナリデータ
50 48 49 53
' テキストデータ
2015

例のRAレコードが表すレースの開催年は2015年ということです。

レコードフォーマットを使う上で1つ注意することは、上のレコードフォーマット表には繰返回数という列があって、項目の繰り返しを省略していることです。例えば、コーナー通過順位項目がそうで、コーナー1~4までのコーナー通過順位を1行のレコードにまとめるために、982から72 Bytes毎に同じ項目が4回繰り返されています。コーナー通過順位の次の項目のレコード更新区分の位置1270 Bytesは、コーナー通過順位の位置982 + 長さ72 * 繰返回数4と一致します。

親項目名子項目名位置(Bytes)繰返回数長さ(Bytes)
コーナー通過順位982472
コーナー通過順位コーナー11
コーナー通過順位周回数21
コーナー通過順位各通過順位370
レコード更新区分12701

つまり、レコードフォーマット表は

親項目名子項目名位置(Bytes)繰返回数長さ(Bytes)

は省略形で、繰返回数から項目のインデックスを作ると完全になります。(コーナー通過順位は子項目インデックスはないのですが、子項目インデックスがある項目があります。)

親項目名親項目インデックス子項目名子項目インデックス位置(Bytes)長さ(Bytes)
コーナー通過順位1コーナー9821
コーナー通過順位1周回数9831
コーナー通過順位1各通過順位98470
コーナー通過順位2コーナー10541
コーナー通過順位2周回数10551
コーナー通過順位2各通過順位105670
コーナー通過順位3コーナー11261
コーナー通過順位3周回数11271
コーナー通過順位3各通過順位112870
コーナー通過順位4コーナー11981
コーナー通過順位4周回数11991
コーナー通過順位4各通過順位120070

私は、上のようなインデックスを加えたレコードフォーマット表をプログラムで作成して、パースに利用しています。


  1. こんなことしなくてもJVData仕様書はExcelファイルで配布されていました。 ↩︎

Kosuke Maeda / まえだこうすけ

「機械学習で競馬予想して勝てるのか?」をテーマに活動中! QiitaにはR、VBAなどのTipsを投稿しています。