bvp / scraper-stadiums
BVP Scraper Stadiums
Requires
- 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
Requires (Dev)
- bvp/scraper: ^12.1
- ergebnis/composer-normalize: ^2.52
- laravel/pint: ^1.30
- phpunit/phpunit: ^12.5.8 || ^13.0
- vimeo/psalm: ^6.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
概要
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 の元で公開されています。