※これ書いていたら、大種牡馬ディープインパクトが本日亡くなったとのこと、ご冥福と子供たちのますますの発展を祈ります
前回はOCRを駆使して読もうと試みたが、Pyocr+TessaractOCRの組み合わせだと、精度いまいちで完敗感いっぱいだったので、今回はいわゆる「スクレイピング技術+正規表現で読む」をやってみた。
【参考】
・分かりやすいpythonの正規表現の例
ところで、JRAサイトのレース結果サイトをスクレイピングしようとしたが、このページDBに情報をためておいて、小出しに出力するように作成されていて、こんなのなかなか持ってこれない。。。ということで、ソースを読み込むこととしました。
基本は、おまけに掲載したような構造になっています。
まずは、以下のページをJRAのページからデータを取得して、再現することを目標にします。
###やったこと
・正規表現で読む
・一つずつ解釈する
・得られたデータを保存する
・データから画面を作成する
###・正規表現で読む
最初の一歩として参考のコードを動かします。
最初のコードは動きましたが、特に使わないので以下から話を進めます。
import re
html = '''<div id="songs-list">
<h2 class="title">songs</h2>
<p class="introduction">
classic songs
</p>
<ul id="list" class="list-group">
<li data-view="4" class="active">
<a href="/again.mp3" singer="Yui">again</a>
</li>
<li data-view="6"><a href="/Darling.mp3" singer="西野カナ">Darling</a></li>
<li data-view="5"><a href="/手をつなぐ理由.mp3" singer="西野カナ">手をつなぐ理由</a></li>
</ul>
</div>'''
pattern = '<li.*?singer="(.*?)">(.*?)</a>'
# findall
results = re.findall(pattern, html, re.S)
# Type:list
print(type(results))
# 抽出
for result in results:
print(result[0], result[1])
肝心なところは、(参考のコメントから引用を#⃣で表します)
①htmlは'''でくくる
②pattern = '<li.*?singer="(.*?)">(.*?)</a>'
として
results = re.findall(pattern, html, re.S)
で抽出する
③resultはlist
ってところで、肝心なのは②のpatternをどのように定義する(見る)かです。
④抽出したい部分を(.*?)
と()でくくる#⃣()で取りたい文字を
⑤#⃣ .*?
でなるべく少ない文字をマッチするため
⑥#⃣ re.Sをしていたため、.*
は改行を識別できる
以上の情報と参考の正規表現の表をたよりにおまけに記載したようなhtmlから情報取得します
###・一つずつ解釈する
一番簡単そうな例として、<td class="f_time">39.2</td>
を取得します。
これは以下のコードで取得できます。
#<td class="f_time">37.9</td>
pattern='<td class="f_time">(.*?)(.*?)</td>'
# findall
results = re.findall(pattern, html, re.S)
# Type:list
print(type(results))
# 抽出
for result in results:
print(result[1])
上記で以下が取得できました。
<class 'list'>
37.0
37.6
...
39.2
理由はわかりませんが、pattern='<td class="f_time">(.*?)</td>'
と一個で取得しようとするとうまくいきません。また、数値だということでpattern='<td class="f_time">(.\d+)(.\d+)</td>'
では、37と.6がresult[0]とresult[1]に分離してしまいます。ということで、上記のコードにたどり着きました。
つぎに、馬名を取得したいと思います。いろいろ情報が付加していますが、肝心な馬名は以下の部分です。
<a href="#" onclick="return doAction('/JRADB/accessU.html','pw01dud002014100977/D1');">ディープスピリッツ</a>
これに対応するのは以下です。
pattern='<a href="#" onclick="return doAction(.*?);">(.*?)</a>'
実際やってみると、以下のように騎手も調教師も出てきてしまいます。
これでもいいかと思いますが、騎手のところに特別な減量などの情報が入っています。これを取り除きたいが。。。
リフトトゥヘヴン
C.ルメール
加藤 征弘
...
ホルンカズマ
<span title="1kg減量">☆</span>横山 武史
栗田 徹
ということで、まずは馬名のために以下のパターンに変更します。
pattern='<td class="horse">.*?<a href="#" onclick="return doAction(.*?);">(.*?)</a>.*?</td>'
これでうまく取得できました。このコードは以下のhtml見るとわかるように、馬名の前のより広い範囲で指定してその必要ない部分も含めて取得することにより馬名を取得することにしました。
<div class="horse">
<span class="horse_icon"><img src="/JRADB/img/kigo/maru-chi.png" alt="マルチ"></span><a href="#" onclick="return doAction('/JRADB/accessU.html','pw01dud002014100977/D1');">ディープスピリッツ</a>
<div class="icon blinker"><img src="/JRADB/img/kigo/icon_blinker.png" alt="ブリンカー"></div>
</div>
ここで、取得したいのは馬名なのでdoActionの後の()は不要な気がします。しかしこれを外すと馬名の全体が取得できませんでした。
ということで上記のコードで取得して書き出しは以下のようにします。
つまり、最初の文字列を捨てます。
for result in results:
print(result[1])
こうして、以下のように取得できました。
リフトトゥヘヴン
ライジングドラゴン
...
ディープスピリッツ
調教師や騎手も同様ですが、騎手はあの特別減量の記載があるのであれをどうにかしたいと思います。
これには参考の以下のものを使いました。
# subでaを除き
html = re.sub('<a.*?>|</a>', '', html)
今回は、騎手減量の部分のhtmlが以下のとおりなのでspanを取り除くとあとは馬名と同じコードで行けそうです。
<td class="jockey"><a href="#" onclick="return doAction('/JRADB/accessK.html','pw04kmk001180/D0');"><span title="3kg減量">▲</span>団野 大成</a></td>
ということで以下としました。
html = re.sub('<span.*?>|</span>', '', html)
pattern='<td class="jockey"><a href="#" onclick="return doAction(.*?);">(.*?)</a></td>'
実は一番難儀をしたのが、増減付き馬体重です。htmlは以下のようになっています。
つまり、隙間だらけなんですね。。。
<td class="h_weight">
458<span>(-2)</span>
</td>
これは以下のpatternで取得できました。
pattern='<td class="h_weight">(.*?\s)(.*?(\d+))<span>((.*?))</span>(.*?)</td>'
486 (+2)
446 (+20)
...
458 (-2)
※しかし二着馬+20だったんだね。。。
最後に謎の(.*?)
をくっつけて、無事に人気順位<td class="pop">6</td>
まで取得できました。
pattern='<td class="pop">(.*?)(.*?)</td>'
# findall
results = re.findall(pattern, html, re.S)
# Type:list
print(type(results))
# 抽出
for result in results:
print(result[1])
###・得られたデータを保存する
m(__)m
今回はここさぼって以下のようにだらだら書きました。
list
使えば、もう少し簡単に書けるはずですが、。。。最後のwriter.writerow(map(lambda x: x, result_1))
のイメージもわかないので。。
ということで、コード。。。
for result in results:
print(result[1])
Result.append(result[1])
print(Result, len(Result))
i=0
result_0=[]
for j in range(12):
re=Result[j+i*12]
result_0.append(re)
i=1
result_1=[]
for j in range(12):
re=Result[j+i*12]
result_1.append(re)
...
with open('./Keiba/keiba_results_.csv', 'w+', newline='') as f:
writer = csv.writer(f)
writer.writerow(map(lambda x: x, result_0))
writer.writerow(map(lambda x: x, result_1))
...
writer.writerow(map(lambda x: x, result_9))
###・データから画面を作成する
ということで、以下のような絵が描けました。これならもとのリザルトでいいのではという問題でなく、一応上記によりデータ取得できたので今後予測に役立てよう。。
###コードは以下に置きました
ほとんどhtmlで埋め尽くされていますが、。。
・read_keiba_paper/find_sentence.py
###まとめ
・JRAサイトのレース結果のデータを読み取ってcsvファイルに保存できた
・htmlをコード埋め込みから読込みに変更しようと思う
・コードがやっつけなので整理する
・同じように競馬新聞データを読んで予測に利用しようと思う
###おまけ
<class 'list'>
リフトトゥヘヴン
ライジングドラゴン
オウケンスターダム
キングスクロス
フクノワイルド
ボルンカズマ
インナーハート
ロードソリスト
カリブメーカー
ウォーターファラオ
スプリングフット
ディープスピリッツ
<class 'list'>
486 (+2)
446 (+20)
478 (-4)
472 (-4)
480 (-2)
488 (-6)
522 (+16)
456 (+4)
510 (0)
446 (+4)
490 (+10)
458 (-2)
<class 'list'>
1:44.5
1:45.4
1:45.8
1:45.9
1:46.3
1:46.6
1:46.6
1:46.7
1:46.8
1:46.9
1:47.3
1:47.3
<class 'list'>
37.0
37.6
37.8
37.9
40.3
39.0
39.1
38.9
38.9
38.6
37.9
39.2
<class 'list'>
C.ルメール
武 豊
三浦 皇成
藤岡 康太
加藤 祥太
☆横山 武史
岩田 康誠
▲団野 大成
吉田 隼人
菱田 裕二
原田 和真
丹内 祐次
<class 'list'>
牡5
57.0
牡4
57.0
牡5
57.0
牡4
57.0
牡3
54.0
牡3
53.0
牡3
54.0
牡5
54.0
牝4
55.0
牡3
54.0
牡5
57.0
牡5
57.0
<class 'list'>
加藤 征弘
吉田 直弘
国枝 栄
大久保 龍志
杉山 晴紀
栗田 徹
吉村 圭司
和田 勇介
河内 洋
岡田 稲男
小桧山 悟
羽月 友彦
<class 'list'>
1
5
9
7
2
3
4
11
10
12
8
6
欲しい情報がある、肝心な部分は以下の繰り返しになっています。
<tr>
<td class="place">12</td>
<td class="waku"><img src="/JRADB/img/waku/6.png" alt="枠6緑"></td>
<td class="num">8</td>
<td class="horse">
<div class="horse">
<span class="horse_icon"><img src="/JRADB/img/kigo/maru-chi.png" alt="マルチ"></span><a href="#" onclick="return doAction('/JRADB/accessU.html','pw01dud002014100977/D1');">ディープスピリッツ</a>
<div class="icon blinker"><img src="/JRADB/img/kigo/icon_blinker.png" alt="ブリンカー"></div>
</div>
</td>
<td class="age">牡5</td>
<td class="weight">57.0</td>
<td class="jockey"><a href="#" onclick="return doAction('/JRADB/accessK.html','pw04kmk001091/7D');">丹内 祐次</a></td>
<td class="time">1:47.3</td>
<td class="margin">クビ</td>
<td class="corner">
<div class="corner_list">
<ul>
<li title="1コーナー通過順位">8</li>
<li title="2コーナー通過順位">8</li>
<li title="3コーナー通過順位">10</li>
<li title="4コーナー通過順位">10</li>
</ul>
</div>
</td>
<td class="f_time">39.2</td>
<td class="h_weight">
458<span>(-2)</span>
</td>
<td class="trainer"><a href="#" onclick="return doAction('/JRADB/accessC.html','pw05cmk001091/C0');">羽月 友彦</a></td>
<td class="pop">6</td>
</tr>