ワールドカップ ユニフォームカラー

つい先日終了したばかりのワールドカップですがユニフォームの色が気になり、 ロシア大会を含むここ7大会のベスト8のチームのユニフォームの色を調べてみました。

f:id:sanshonoki:20180723225714p:plain

(注) ホームのユニフォームを表示しています。ロシア大会に出場しているチームはロシア大会のを使いまわしているので昔の大会のものは正確でないかも..。また、5位以下の並びは適当です

【各画像の参照元

きっかけ

日本の初戦のコロンビア戦をTV観戦したとき、日本の選手が見にくいなーと思ったのがきっかけです。

日本のユニフォームは「サムライブルー」と言われる爽やかな青色ですが コロンビアのVividな黄色のカラーと比べると明らかに視認性が悪く(芝生の色と明度が近いので)、

あれ? 味方の位置の把握が(瞬間的に相対的に)しにくいんじゃない?

と思った次第です。

色のちから

視認性以外の側面から見てみます。

赤や黄色などの暖色系は 神経の興奮作用があり (程度のこそはあれ)相手の冷静さを失わせることができると考えられます。 また、サポーターは自国の赤や黄色を見て興奮し、熱烈な応援になりそうです。 スペインの闘牛も観客を興奮させるために赤い布を使ってるそうです。 他には、浦和レッズのファンは過激という話もあります。

逆に、青色の寒色系は神経の鎮静作用があり(程度のこそはあれ)相手を冷静させてしまうのではないかと考えられます。 サポーターも冷静になってcoolな応援になってしまいそうです。

白は膨張色なので相手選手に対して自分を大きく見せたりできそうです。また、「長時間見ると目が疲れる」効果もあるそうです。

ということから考えると やはり日本のサムライブルーは不利なんじゃないかと思うわけです。

【参考】

分析?

とりあえず、色ごとにカウントしてみます。

Count
19
赤 + 橙 18
12
7

白赤混合のクロアチアパラグアイは赤としてカウントしました

そもそも赤と白の総数が多いんで上位進出率として統計的に有意とかは全く主張できないですけど予想通りに白と赤+橙、黄が上位に来ました。 青は自分の予想ではもっと少ないかと思っていましたがフランスとイタリアの頑張りによりそこそこありました。 でも、パッと見、目立たないのは目立たないですね。。

ワールドカップ常連国とまでは言えないコスタリカ、ガーナ、韓国、ロシア、パラグアイウクライナセネガルはすべて赤、白、黄なのでひょっとしたら色彩の影響はいくらかあるのかもしれません。

ひとこと

悲願のベスト8進出の確率を少しでも上げるために、次回のカタール大会では日の丸カラーの白と赤を使ったユニフォームにしてはどうでしょうか。。

リモートのJupyter Notebookを自動でブラウザで開く方法

2ヶ月くらい前に「現場で使える! TensorFlow開発入門 Kerasによる深層学習モデル構築手法」という書籍を買ってみたのですが手をつけずに放置してました..。サンプルコードぐらい動かそうということで環境構築をAzure上で行いました。

この本の中では nvidia-docker を使ってコンテナの中でjupyter notebookを起動するので以前にfast.ai用に作ったAzureラッパースクリプトを少し修正しました。

github.com

サンプルプログラムを動かすための環境構築は基本的に翔泳社のサイトからダウンロードできる補足資料のpdfの通りに進めれば何も問題ないです。

が、

↑のラッパースクリプトを使うとjupyter notebookのトークン認証のトークンを毎回手動でコピペして入力する必要がなくなるので毎回の立ち上げのストレスが減ります :-)

個人的にはすごい便利です。

今回はリモートマシンのdockerコンテナで起動しているjupyter notebookのトークンを取得して自動でブラウザで開く方法について書きます。

起動中のjupyter notebookのトークンを取得する

$ jupyter notebook list

で起動しているjupyter notebookのtokenつきのURLが取得できます。

具体的には以下のような出力が得られます。

Currently running servers:
http://localhost:8888/?token=1864c580501c69e7a331707d7901bc2e4263bffd54c70538 :: /notebooks

この出力から "http://localhost:8888/?token=xxx" の部分を抜き出し、localhostをリモートマシンのIPアドレスに置き換えたURLをopenコマンドで開けばokです(Macの場合)。

ちなみに、複数のjupyter notebookインスタンスを起動しているときはhttp://localhost:xxxx/?token=xxx が複数行出力されます。

dockerコンテナでjupyter notebookを動かしているとき

dockerコンテナでjupyter notebookが起動しているときはnvidia-dockerコマンドを使って

$ sudo nvidia-docker exec YOUR_CONTAINER_NAME jupyter notebook list

でjupyter notebookのtokenつきのURLが取得できます。

ここで "YOUR_CONTAINER_NAME" はjuypter notebookを起動しているdockerコンテナの名前です。

本のサンプルの場合だと、

sudo nvidia-docker run --rm --name tfbook -p 80:8888 -p 6006:6006 -v /notebooks:/notebooks tfbook

というようにtfbookというコンテナ名で jupyter notebookを起動しています。 (--nameで指定しているtfbookがコンテナ名で末尾のtfbookはdockerイメージ名)

このコマンドだと明示的にjupyter notebookを起動していませんが 大元のDockerfileの中で定義されています。

CMD ["/run_jupyter.sh", "--allow-root"] # 最終行

この1行があるのでコンテナ生成時にjupyter notebookが起動します。

ちなみに、nvidia-docker runだと空の出力になってしまいます。

nvidia-docker execを使って既存のコンテナ上でjupyter listコマンドを実行することがポイントです。

リモートマシンのjupyter notebookのトークンを取得

リモートマシンで先のコマンドを実行するにはsshコマンドを使います。

最近まで知らなかったのですが(汗)、ssh ユーザー名@リモート端末のIPアドレス 任意のコマンド とするとリモートマシン上で任意のコマンドを実行できます。

なので、今回の場合、以下のようなコマンドになります。

ssh -i ${privateKeyPath} ubuntu@${instanceId} 'sudo nvidia-docker exec tfbook jupyter notebook list'`

あとは このコマンド出力からtokenつきのURLの箇所を抜き出して、localhost をリモートマシンのIPアドレスに置換してブラウザに渡せばokです。

完成形スクリプト

最終的には以下のシェルスクリプトを実行することによってリモートマシン上で起動しているjupyter notebookを自動的にブラウザで開くことができます。

list=`ssh -i ${privateKeyPath} ubuntu@${instanceId} 'sudo nvidia-docker exec tfbook jupyter notebook list'`
url=`echo $list | grep http | sed -e "s/localhost/${instanceId}/g" | sed -e "s/:8888//g" | awk '{print $4}'`
echo $url
open $url

また、リモートマシンに明示的にログインせずにリモートマシン上でjupyter notebookを起動するには、同様にして

ssh -i ${privateKeyPath} -o "StrictHostKeyChecking no" ubuntu@${instanceId} "sudo nvidia-docker run --rm --name tfbook -p 80:8888 -p 6006:6006 -v /notebooks:/notebooks tfbook"

となります。

docker使わない場合は、もっとシンプルに

ssh -i ${privateKeyPath} -o "StrictHostKeyChecking no" ubuntu@${instanceId} "cd /path/to/notebook_dir; jupyter notebook"

です。

変動係数でPOGデータの分析

せっかく集めたデータなので変動係数(Coefficient of Variance)を使って粘って解析してみます。 データは5/3時点で集計したものを引き続き使っています。

変動係数(Coefficient of Variance)とは

定義

CV = 標準偏差 / 平均値

何を意味する?

相対的なばらつきを表します。 平均値が異なる二つの集団のばらつきを比較する場合に用いられます。

具体例

例えば、りんごが5個、いちごが5個ありそれぞれの重さ(g)が以下であったとします

りんご いちご
100 10
100 10
110 15
110 15
100 10

それぞれの標準偏差は次のようになります。

りんごの標準偏差=4.90、いちごの標準偏差=2.45

りんごの標準偏差の方が大きいので一見、りんごの方がバラつきが大きいように見えますがこれはりんごの1個1個の重さがいちごに比べて大きいためであり、

変動係数CVを計算すると

りんごの変動係数= 4.90(標準偏差) / 104(平均値) = 0.047

いちごの変動係数= 2.45(標準偏差) / 12(平均値) = 0.20 >> 0.047

となり、いちごの方がバラつきが大きいことがわかります。

POGのデータで解析

それでは、POGのデータでいろいろ見てみます。

見方としては、変動係数が大きい = リスクが高い ということになりますので 「平均値が高く、変動係数が小さい」ところを狙うのが統計的には良い戦略となります。

牡馬・牝馬

f:id:sanshonoki:20180530215652p:plain

やはりというか牡馬が平均値が高くリスクも低いという結果でした。 素直に牡馬の割合を多くしたほうが良さそうです。

種牡馬

リーディング上位の15頭をプロットしています。

f:id:sanshonoki:20180530215953p:plain

ディープインパクトが断トツなことがわかります。これはもう逆らえないですね。。

ダイワメジャーよりキングカメハメハハーツクライルーラーシップのほうに行きたくなりますが産駒全体としてはダイワメジャーのほうが平均的に稼ぐようです。なるほど、そうなのか。

キンシャサノキセキが健闘していて下位指名で妙味がありそうです。

ロードカナロアオルフェーヴルの新種牡馬は育成ノウハウがたまってくる来年以降、変動係数も下がってくるのではないでしょうか。注目したいです

BMS(母父)

リーディング上位の15頭をプロットしています。

f:id:sanshonoki:20180530220144p:plain

Storm Catキングカメハメハが抜けています。 なお、フォーティナイナー皐月賞馬のエポカドーロ効果です。

記憶に新しいところでは今年のダービー馬 ワグネリアンは母父キングカメハメハ、 2013年のダービー馬 キズナは母父Storm Catでした。 どちらも父はディープインパクト

生産者

リーディング上位20牧場をプロットしています。

f:id:sanshonoki:20180530221208p:plain

(注:エポカドーロ効果で生産者の田上徹さんは表外に突き抜けてプロットされています)

ノーザンファーム社台ファーム白老ファームの社台系はさすがに外せないですが

ケイアイファームはロードホースクラブやダノンとの結びつきがあり、社台系以外で狙うならこれではないでしょうか。

それ以外では、グラフから

も健闘していることが読み取れます。

下位で遊びつつも賞金も狙いたいというときによいかもしれません。

調教師

リーディング上位の40人をプロットしました。

f:id:sanshonoki:20180530223458p:plain

桜花賞馬アーモンドアイを出した国枝厩舎はさすがに変動係数が大きくなっています。 それを考えるとエポカドーロの藤原英厩舎、ダノンプレミアムの中内田厩舎はすごい気がします。

友道厩舎も重賞馬出していてこの変動係数の低さはすごいと思います。

あとはリーディング上位常連の藤沢和、池江厩舎、音無厩舎、角居厩舎、手塚厩舎と若手の木村厩舎ですか (角居と手塚はプロットが重なっています)

藤岡厩舎、南井厩舎、浅見厩舎はデータにすると私のイメージ以上の活躍でした。侮っていてすみません。。

須貝、矢作、石坂厩舎は毎年のように活躍馬を出しているイメージですが0勝に終わる馬も多いようで平均値が低くなっていることがわかりました。

勝ち上がり馬率

その、登録されている3歳馬のうち1勝以上あげている馬の割合です。

藤沢厩舎はすごいですね。 堀厩舎の安定感はイメージ通り。

このデータからは庄野厩舎、高野厩舎もマークしておきたいです。

f:id:sanshonoki:20180530223642p:plain

栗東美浦

f:id:sanshonoki:20180530222105p:plain

素直に栗東の割合を多くしましょう

誕生日

f:id:sanshonoki:20180530221718p:plain

このグラフと次のグラフは縦軸が賞金です。

ある程度以上の活躍する馬は1/1から数えて120日ぐらいが目安でしょうか。 つまり5月生まれはやはり不利なことがわかります。

デビュー時点での馬体重

f:id:sanshonoki:20180530221832p:plain

450-500Kgが良さそうですね。 本当に稼ぐ馬は470-480Kg前後ぐらいでしょうか。

使ったコード

今回の分析で使ったjupyter notebookはこちらになります。 csvファイルもあります。

github.com

「やはり」を連発してしまいましたが、自分で数字出せるとなかなか面白いです。 来年度、またやってみようと思います。(もうちょっと深い分析ができるとよいな)

RandomForestでPOGの賞金予測をする

結論から書けば玉砕でした。。

そりぁそうですよ

全兄弟で同じ厩舎でも一方はG1馬、他方は未勝利馬ということもあるわけですから。

数千サンプルのデータと二十次元程度の特徴量から予測するなんておこがましかったわけです。

いちおうトライしたのでやったことを書いておこうと思います。

データ

先日実装したnetkeibaスクレイパーを使って現3歳馬のデータを抽出しました。

  • 2018/5/3時点での皐月賞後が終わったあとデータ
  • 5962頭から 地方調教師に所属する馬を除いて 全3923頭

他に調教師、生産者、種牡馬、母父、母馬のデータも抽出し、結合して使います

特徴量ベクトル(説明変数)

調教師、生産者、種牡馬、母父、母馬のデータを結合し、 19次元のベクトルを入力として用います。

  • debut_weight:デビュー戦の体重
  • birth_date_from_beginning_of_year:1/1を基準にした誕生日までの日数
  • crop_win_count:母馬の産駒の合計勝利数
  • crop_grade_horse_count:母馬の産駒の重賞馬の合計頭数
  • crop_grade_win_count:母馬の産駒の重賞勝ちの合計数
  • win_count_trainer:調教師の勝利数
  • prize_trainer:調教師の獲得賞金額
  • win_count_breeder:生産者の勝利数
  • prize_breeder:生産者の獲得賞金額
  • win_ratio:種牡馬の勝率
  • earning_index:種牡馬のearning index
  • prize_sire:種牡馬の獲得賞金額
  • win_ratio_bms:母父の勝率
  • earning_index_bms:母父のearning index
  • sex_セ、sex_牝、sex_牡: 性別(sexをone-hot encoding)
  • stable_trainer_栗東、stable_trainer_美浦: 所属(stable_trainerをone-hot encoding)

今年デビュー予定馬のデビュー時体重のデータを得るのは難しいかもしれませんが 大型とか小型とかある程度はPOG本から情報仕入れられるのと予測に役に立ちそうなファクターな気がしたので入れています。

目的変数

もちろん獲得賞金額になりますがlogをとって対数スケールにしました。

使った機械学習アルゴリズム

手っ取り早い RandomForest です。 パラメータチューニングなしで学習させました。

学習データと検証データの比率は 7:3 でランダムに分割しました

学習結果

学習データでの予測結果のグラフです (logスケールなので軸の目盛りは0-10です)

f:id:sanshonoki:20180523223843p:plain

R2スコアは 0.83です

このグラフを見ると淡い期待が。。

競馬で言えば、最後の直線に入り「そのまま、そのまま」と叫ぶのに似た気持ちです

検証データでの予測結果

f:id:sanshonoki:20180523223919p:plain

R2スコアは 0.07 ...

全部正解だったときのスコアが1.0、ランダムに当てずっぽうに答えたときのスコアが0.0なので非常に辛い結果です

心の目で見るとわずかに右上がりの傾向が見えないこともないけど?(強がり)

Feature Importance

夢は潰え終戦しました

が、最後にFeature Importanceを出力し、各特徴量の学習への寄与度を見てみます。

f:id:sanshonoki:20180523225001p:plain

上位8位の特徴量
  1. 調教師の獲得賞金額
  2. 1/1を基準にした誕生日までの日数
  3. 母馬の産駒の合計勝利数
  4. デビュー戦の体重
  5. 母父のearning index
  6. 生産者の獲得賞金額
  7. 種牡馬の勝率
  8. 母父の勝率

これはPOGをやったことある人ならうなずける結果ではないでしょうか

このデータを頭に入れてドラフト会議に臨みたいと思います

2018-2019年度POG用の2歳馬リストを作る

先日作ったスクリプトで自分でも2歳馬リストが作れることが分かったので作ってみました。

方法
  1. 必要なデータをスクリプトで取得

    $ python get_horse_data.py --age 2 -o pog2018_1.csv --include_no_debut
    $ python get_horse_additional_data.py -i pog2018_1.csv -o pog2018_2.csv
    
  2. 2つのファイルをマージする

     import pandas as pd
    
     df1 = pd.read_csv("pog2018_1.csv")
     df2 = pd.read_csv("pog2018_2.csv")
    
     df = pd.merge(df1, df2, on='id')
    
     # 不要なカラムを削除
     df.drop(['birth_year', 'trainer_id', 'owner_id', 'breeder_id', 'prize', 'name_y', 'race_result'], axis=1, inplace=True)
     df.drop(df.loc[:, df.columns.str.contains('^Unnamed')], inplace=True, axis=1)
    
     df.to_csv("pog2018_list.csv")
    
出力項目
  • id
  • 馬名
  • 性別
  • 厩舎
  • 母父
  • オーナー
  • 生産者
  • 毛色
  • 生年月日
  • セリ価格
  • 近親馬

以下のサイトで2歳馬リストがダウンロードできますがセルフで作ってみたいという方はやってみてください。。 (スクリプトの拡張大歓迎)

fast.aiのPractical Deep Learning For Coders, Part 1 (2018 edition)

fast.aiのPractical Deep Learning For Coders, Part 1 (2018 edition) を細々と続けていましたがようやく一通り終えた...! というところまでいきました。

7 weeksのコースなので2倍ぐらいの時間がかかっちゃった計算になります。。

さらにブログの筆もなかなか進まず.. (;´Д`)

良かった点は

  • v1(2017 edition)の復習ができた
  • 特にBatchNormalization、Resnetについて理解が深まった
  • 新しいテクニック SGDR (stochastic gradient descent with restarts) 、TTA (test time augmentation) を知れた

一方で残念だったところは

  • fast.aiのライブラリでかなり抽象化されているのでPyTorchでのコーディング力がついた感なし
  • 正直、v1(2017 edition)以上にforumに頼らないと進めない/分からないことがある

コーディング力をつけるためにはfast.aiのライブラリの中身をちゃんと読んでそこで何をやっているのか詳しく追っていく必要がありそうです。。

あとは発展途上ということもあって情報不足であったり動かないコードも多くforumにはかなりお世話になりました。 この辺が改善されると全受講者の無駄?な時間が減ると思います。

ということで、躓いた箇所や参考にしたforum情報をまとめておきたいと思います。

(注:本家のコードは随時更新されているので既に役に立たなくなっている情報もあるかもしれません)

躓いた箇所や参考になったforum記事のまとめ

lesson1
lesson1-vgg
lesson1-breeds
lesson2-image_models
lesson3-rossman
lesson4-imdb
lesson6-rnn
lesson6-sgd
  • ffmpegのインストール
  • Gradient Descent - Classificationのセクションで accuracyが上がらない
    • 以下のコードに修正したらaccuracyが上がるようになった

        # loss = nll(y_hat,y)
        loss = -1.0 * nll(y_hat, y) # loss must be a positive value.
      
lesson7-cifar10
planet_cv
nasnet
nlp
lang-model
  • データセットが見つからない
  • いろいろとエラーが出る..!
    • 以下のように修正したらとりあえず動いた

        #md = LanguageModelData(PATH, TEXT, **FILES, bs=bs, bptt=bptt, min_freq=10) # This does not work.
        md = LanguageModelData.from_text_files(PATH, TEXT, **FILES, bs=bs, bptt=bptt, min_freq=10)
      
        #learner = md.get_model(SGD_Momentum(0.7), bs, em_sz, nh, nl) # This does not work.
        learner = md.get_model(SGD_Momentum(0.7), em_sz, nh, nl)
      
        #learner.fit(10, 1, wds=1e-6, reg_fn=reg_fn, clip=clip) # fit() got multiple values for keyword argument 'reg_fn'
        learner.reg_fn = reg_fn
        learner.clip = clip
        learner.fit(10, 1, wds=1e-6)
      
cifar10-simplenet
  • ImageClassifierData.from_csv でエラー
    • とりあえず以下のコードにすれば動く

        # data = ImageClassifierData.from_csv(PATH, 'train', PATH/'train.csv', tfms=tfms, bs=bs)
        data = ImageClassifierData.from_paths(PATH, val_name='test', tfms=tfms, bs=bs)
      


これらの情報やその他メモを詰め込んだ自分の作業用ノートブックはこちらです。 github.com

netkeibaのデータのスクレイパー

今週末は桜花賞。いよいよクラシックシーズンの開幕です。

ということは、、

来年度のPOGの足音も聞こえてきたってことになります。

今年こそは機械学習を使って一人勝ち!!

その第一歩としてnetkeibaの各種ランキングのスクレイパーを実装しました。

github.com

取得できるデータは

  • ○歳馬の賞金順
  • ○年度の調教師リーディング
  • ○年度の生産者リーディング
  • ○年度の馬主リーディング
  • ○年度の騎手リーディング

あと、これらのページを補完する情報として

  • 各馬の生年月日、セリ取引価格、通算成績
    f:id:sanshonoki:20180403220124p:plain:w300
  • 母馬の繁殖成績
    f:id:sanshonoki:20180403220020p:plain:w300

も抽出できるようにしました。これらをCSV形式で出力します。

抽出処理自体はBeautifulSoup4を使ってゴリゴリやっていて特段に工夫した点はないですがやや手こずったところもあったのでメモを残します。

競走馬検索のページング

リーディング情報ではGETで http://db.netkeiba.com/?pid=trainer_leading&year=2017&page=2 というようにpage番号のクエリパラメータをつければ簡単に2ページ目以降の情報が簡単に取得できますが競走馬検索ではpageのパラメータがなくこのままでは1ページ目以外取得できません。

取得方法
  1. 最初の検索結果のレスポンスのhtmlにserialが埋め込まれているのでこのデータを取得

     <input type="hidden" name="serial" value="a:19:{s:3:&quot;pid&quot;;s:10:&quot;horse_list&quot;;s:4:&quot;word&quot;;s:0:&quot;&quot;;s:4:&quot;sire&quot;;s:0:&quot;&quot;;s:5:&quot;keito&quot;;s:0:&quot;&quot;;s:4:&quot;mare&quot;;s:0:&quot;&quot;;s:3:&quot;bms&quot;;s:0:&quot;&quot;;s:7:&quot;trainer&quot;;s:0:&quot;&quot;;s:5:&quot;owner&quot;;s:0:&quot;&quot;;s:7:&quot;breeder&quot;;s:0:&quot;&quot;;s:9:&quot;under_age&quot;;s:1:&quot;4&quot;;s:8:&quot;over_age&quot;;s:1:&quot;4&quot;;s:9:&quot;prize_min&quot;;s:0:&quot;&quot;;s:9:&quot;prize_max&quot;;s:0:&quot;&quot;;s:4:&quot;sort&quot;;s:5:&quot;prize&quot;;s:4:&quot;list&quot;;s:3:&quot;100&quot;;s:9:&quot;style_dir&quot;;s:17:&quot;style/netkeiba.ja&quot;;s:13:&quot;template_file&quot;;s:15:&quot;horse_list.html&quot;;s:9:&quot;style_url&quot;;s:18:&quot;/style/netkeiba.ja&quot;;s:6:&quot;search&quot;;s:14:&quot;年齢[4歳~4歳]&quot;;}" />
    

    のような感じで初回の検索条件がエンコードされています

  2. 2ページ目以降はこのserialの値とpageをPOSTパラメータとしてリクエストする

コードとしては以下のようになります。

def getSerial(html):
    soup = BeautifulSoup(html, "html.parser")
    serial = soup.select('input[name="serial"]')

def getPageBySerial(serial, page=2):
    url = 'http://db.netkeiba.com'
    params = {
        'pid': 'horse_list',
        'sort_key': 'prize',
        'sort_type': 'desc',
        'page': page,
        'serial': serial.encode('euc-jp')}
    res = requests.post(url, data=params)
    res.encoding = res.apparent_encoding
    html = res.text
    return html

serial は euc-jpでエンコードしないとうまくいかないのが注意点です。(馬名で検索するときも同様)

謎の挙動。。

一部の○○IIという馬が検索できない

IIがついていてもサンデースマイルIIのように正常に検索できる馬がいる一方でスノーフレークII等、IIがつく数頭の馬で完全一致で検索すると「馬名[スノーフレークII]、年齢[無指定~無指定]では見つかりませんでした。」となってしまいます。

対策

IIを除いた「スノーフレーク」で検索すると正常に検索できるのでIIがつく場合はIIを取り除いて部分一致で検索するようにしました。 ただし、この場合、2頭以上にヒットした場合に以下のように複数の候補が出て来るのでその中から名前が完全に一致する馬のIDを抽出します。

f:id:sanshonoki:20180403220955p:plain

Debit Or Creditで検索すると 「この db.netkeiba.com ページが見つかりません」という結果が返ってくる

普通なら検索でヒットしなかった場合、「馬名[コンナウマイナイヨ]、年齢[2歳~無指定]では見つかりませんでした。」というようにページ自体は表示されるのですがこの馬だけは404のエラーレスポンスだけが返ってきてブラウザが出力する404エラーページが表示されます..。これはサーバーサイドのバグなのでしょうか。。

対策

最後の単語を省いてDebit Orで検索すると404エラーとならずにDebit Or Creditの検索結果がちゃんと返ってきました。 スペースを含む馬名でうまく結果が得られなかった場合は最後の単語を除いて部分一致で検索するように実装しています。

何頭かの候補が返ってきた場合は、○○IIの場合と同じく検索結果の中から名前が一致する馬を探します。


準備は整いました。何か予測できるように頑張らないと。。