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の場合と同じく検索結果の中から名前が一致する馬を探します。


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