Search by

bvp / scraper-stadiums

shimomo

BVP Scraper Stadiums

0.1.2 2026-10-18 05:11 UTC

This package is auto-updated.

Last update: 2026-10-18 05:14:21 UTC


README

php stable license

test lint psalm audit smoke keepalive

概要

BVP Scraper Stadiums は、各ボートレース場が自前の公式サイトで公開している情報を取得するための PHP ライブラリです。現在の版(0.x)が読むのは選手コメントで、大村・唐津・芦屋に対応しています。

boatrace.jp を読む bvp/scraper とは別パッケージです。場ごとにサイトの構造も保持期間も違い、boatrace.jp のように全場を同じ形で読めないため、ソースを場ごとに登録する別の仕組みを持たせました。

選手コメントの単位は**「日×選手」**です。3 場とも、その日の出場選手全員が登録番号つきで 1 ページに載っていて、レース番号も艇番も載っていません。そのため引数は (日付, 場番号)、返り値は登録番号をキーにした選手の配列です。

レースへの割り当ては、利用側が出走表の登録番号と突き合わせて行ってください。

このライブラリが持たないものは次のとおりです。

  • 永続化、cron、キャッシュ(同じ日を 2 回呼べば 2 回取りに行きます)
  • boatrace.jp の読み取り(出走表や結果は bvp/scraper を使ってください)

📦 Requirements

  • php: ^8.3
  • nesbot/carbon: ^2.63 || ^3.0
  • symfony/browser-kit: ^7.0 || ^8.0
  • symfony/css-selector: ^7.0 || ^8.0
  • symfony/http-client: ^7.0 || ^8.0

💾 Installation

composer require bvp/scraper-stadiums

⚡ Usage

サポートメソッド一覧

メソッド 説明 引数
scrapeRacerComments($date, $stadiumNumber, $source = null) 選手コメントを 1 日ぶん取得。返り値は下記のレスポンス形式の配列、または null $date : Carbon 対応日付文字列または Carbon インスタンス
$stadiumNumber : 1〜24
$source : ソースのキー(省略時はその場の先頭のソース)
supports($kind, $stadiumNumber) その種類のソースがその場にあるか(bool) $kind : Kind::RacerComments
$stadiumNumber : 1〜24
sources($kind, $stadiumNumber = null) 記述子(SourceDescriptor)の一覧 $kind : Kind::RacerComments
$stadiumNumber : 省略時は全ソース

基本的な使い方

<?php

require __DIR__ . '/vendor/autoload.php';

use BVP\Scraper\Stadiums\Kind;
use BVP\Scraper\Stadiums\Scraper;

$scraper = new Scraper();

// 1日ぶん(1リクエスト)。ソースを省略すると、その場に登録された先頭のソースを使う
$day = $scraper->scrapeRacerComments('2026-10-17', 24);

// ソースを明示する
$day = $scraper->scrapeRacerComments('2026-10-17', 24, source: 'omura');

// メタデータ
$scraper->supports(Kind::RacerComments, 24);   // bool
$scraper->sources(Kind::RacerComments, 24);    // list<SourceDescriptor>(その場のソース)
$scraper->sources(Kind::RacerComments);        // list<SourceDescriptor>(全ソース)

Scraper のコンストラクタは、すべて省略可能な 4 つの引数 httpBrowser / rateLimiter / retryPolicy / registry を取ります。レート制御は実際に叩く URL のホストごとです。

  • 既定のホスト別の呼び出し間隔は 3.0 秒(下限 1.0 秒)です。変えるときは rateLimiter 引数に new HostRateLimiter(5.0) のように渡します。
  • 対応するソースが無い場を渡すと UnsupportedSourceException を投げます(再試行しません)。
  • scrapeRacerComments() と supports() は、場番号が 1〜24 の範囲外なら ValueError を投げます(sources() は場番号を検証しません)。
  • $date は Carbon 対応の日付文字列または Carbon インスタンスです。

記述子

sources() が返す SourceDescriptor は、呼び出し側が自前のスケジュールを組むための情報です。公開フィールドは次のとおりです。

  • key: ソースのキー
  • kind: 種類(Kind)
  • stadiumNumbers: 対応する場番号のリスト
  • hosts: 叩くホストのリスト
  • archive: 保持範囲(Archive)
  • liveOnly: 締切前の窓にしか存在しないか
  • datedPage: ページが日付を名乗るか

Archive::covers($date, $today) は、その日付が保持範囲に入るかを true / false で返し、事前に分からないときは null を返します。Archive::label() は保持範囲の文字列表現です。

ソース一覧

この表は Registry::default() の記述子と各ソースの実装から起こしています。

キー 場 入口 URL 保持 ライブ限定 日付を名乗るか コメントの種類と label
omura 24 大村 https://www.omurakyotei.jp/yosou/comment.php?day={YYYYMMDD} 開催中の節のみ いいえ 名乗る 本文 → previous_day
赤字(当日の更新)→ same_day
赤字が「レース後」で始まり、末尾に [HH:MM] の時刻があるもの → after_race
(same_day も、末尾に [HH:MM] があれば time に入る。「レース後」で始まっても時刻が無ければ same_day)
karatsu 23 唐津 https://www.boatrace-karatsu.jp/sp/index.php?page=raceinfo-racers_comment&day={YYYYMMDD} 未測定 いいえ 名乗る 前日 → previous_day(label_source は「前日」)
直前 → same_day(label_source は「直前」。time は末尾の「(HH:MM現在)」から取る)
ashiya 21 芦屋 https://www.boatrace-ashiya.com/modules/raceinfo/?page=index_racers_comment 当日のみ いいえ 名乗る previous_day のみ(常にこの値。公式サイトの注記「コメントは前日のものになります」に基づく固定値で、label_source は null)
  • 「ライブ限定」は、締切前の窓にしか存在しないかどうかです。3 ソースとも「いいえ」です。
  • 「保持」は Archive::label() の文字列です。「開催中の節のみ」と「未測定」は、その日付を要求して意味があるか事前には分からないため、実際に取りに行きます。「当日のみ」のソースは、当日以外の日付では取りに行かずに null を返します。
  • 大村と唐津の day はクエリに入れる日付で、芦屋の入口 URL に日付は入りません。
  • 3 ソースとも、ページが自分の日付を名乗っていて、要求した日付と食い違うときは null を返します。返り値が null でないとき、page_date / page_date_source に突合したページ側の日付が入ります。
  • 大村と唐津はモーター評価も載せていますが、現在の版では読みません。

レスポンス形式

[
    'source' => 'omura',
    'url' => 'https://www.omurakyotei.jp/yosou/comment.php?day=20261017',   // 実際に取得した URL(監査用)
    'fetched_at' => '2026-10-18 12:20:31',           // 取得時刻(Asia/Tokyo)
    'date' => '2026-10-17',                          // 要求した日付
    'stadium_number' => 24,
    'page_date_source' => '20261017',                // ページが名乗る日付の生文字列
    'page_date' => '2026-10-17',
    'racers' => [
        3473 => [                                    // キーは登録番号。並びはページどおり
            'number_source' => '3473', 'number' => 3473,
            'name' => '石川 真二',                     // 「姓 名」。載っていなければ null
            'rank_source' => 'A1', 'rank_number' => 1, // 級別。載らないソースでは両方 null
            'comments' => [
                ['label_source' => null,     'label' => 'previous_day', 'text' => '回り足とかピット離れも…', 'time_source' => null,      'time' => null],
                ['label_source' => 'レース後', 'label' => 'after_race',   'text' => '前半レースは重かったです。…', 'time_source' => '[18:10]', 'time' => '18:10'],
            ],
        ],
        // … その日の出場選手ぶん
    ],
]
  • comments はリストです。ソースごとにコメントの種類が違うため、固定列にすると「無い」と「そのソースに無い」が混ざります。
  • label は previous_day / same_day / after_race のいずれか、または null です。label_source はページ上の手がかりの生文字列で、ページ全体の注記から決めた場合は null です。
  • after_race は事後情報です。レース前の時点で使える情報に限りたい用途では落としてください。
  • 改行は \n に正規化して残します。空白は U+3000 を含めて畳み、選手名は「姓 名」に揃えます。
  • 各フィールドは生の文字列(*_source)と変換済みの値のペアで返ります。

「取れない」と「無い」の区別

状況 振る舞い
接続失敗、5xx UnexpectedPageException(RuntimeException)。リトライを使い切るまで再試行し、尽きたら呼び出し側へ伝播する(接続失敗は getPrevious() に元の例外が付く)
2xx だがページの骨格が想定と違う、見出し行(列構成)が想定と違う、行はあるのに選手が 1 人も読めない UnexpectedPageException。再試行しない(テンプレート変更は待っても直らないため)
4xx(403 も 404 も。場のサイトでは非公開・非開催・保持範囲外のどれでも返る) null。再試行しない
唐津の「非開催」表示(hikaisai) null
コメントの表が空(大村・唐津・芦屋)、表が無い(芦屋) null
表が無い(大村・唐津) UnexpectedPageException(骨格が無いものとして扱う。芦屋だけは null)
大村で、日付が合い表に選手の行はあるのに 1 人も読めない UnexpectedPageException
ページが名乗る日付が無い、または要求と食い違う null。大村は節の外の日付を要求すると最新日を返すため、これで弾く
唐津の PC 版は day を無視して当日を返す day を見る SP 版を入口にしている
芦屋は当日のみ(day を無視する) 当日以外を要求すると取りに行かずに null
2xx で骨格は正しく、選手の一部にコメントが無い 返す。その選手の comments は空リスト
  • 各ソースは、ページの骨格と表の見出し行を固定して持ち、想定と違えば UnexpectedPageException にします。全項目が null の配列を返す経路はありません。
  • URL の移動や廃止は 4xx になり、ライブラリの中では非開催と区別できません。これを検知するのは smoke(後述)で、開催していたと分かっている場を叩いて、null や空の結果なら失敗にします。

⚠️ Notes

  • 利用時は、各場の公式サイトの利用規約を遵守してください。
  • 大村は、開催中の節の外の日付を要求すると、その日付ではなく最新日のページを返します。ページが名乗る日付で弾くため、このライブラリは節の外では null を返します。
  • 唐津の直前コメントの markup は、2026-10-18 に実ページで確認しています(<p class='cComment u-accent'> で、末尾に「(HH:MM現在)」の時刻が付きます)。
  • スクレイピング対象のサイトの構造が変更された場合、正しくデータを取得できなくなる可能性があります。通常のテストは固定した fixture を読むため、その検知は smoke が担います。
  • smoke(vendor/bin/phpunit --testsuite=Smoke)は、各ソースが保持しているはずの日を実際に叩く唯一のテストで、GitHub Actions で毎朝 09:30 JST に回します。GitHub のホステッドランナーは日本国外の IP のため、場のサイトに遮断されることがあります。その場合は smoke を手動実行(workflow_dispatch のみ)に切り替えます。
  • ライブラリはキャッシュを持ちません。蓄積や再利用が要る場合は、利用側で保存してください。
  • 0.x の間は、返り値の形が変わる変更を minor で行い、この欄に記載します。

📄 License

Scraper Stadiums は MIT license の元で公開されています。