tohokuaikiのチラシの裏

技術的ネタとか。

ミライコンパスを使ってる学校のイベント情報をRSSで取得する

全国の私立中高校で説明会などの予約に使われているミライコンパスというのがあるんだけど、これを学校をまとめてRSS化するようにした。

Nodejsで作ってる。

$ node -v
v10.16.0

コード

これを

$ node mirai-compass-rss-index.js

として、見たいのが

学校 イベントURL
女子美術大学付属中学校(東京都) https://mirai-compass.net/usr/joshibij/event/evtIndex.jsf
成立学園中学校(東京都) https://mirai-compass.net/usr/seiritzj/event/evtIndex.jsf
かえつ有明中学校(東京都) https://mirai-compass.net/usr/kariakej/event/evtIndex.jsf
国士舘高等学校(東京都) https://mirai-compass.net/usr/kokushih/event/evtIndex.jsf
常総学院中学校(茨城県 https://mirai-compass.net/usr/josogj/event/evtIndex.jsf
淑徳与野中学校(埼玉県) https://mirai-compass.net/usr/shukuynj/event/evtIndex.jsf

だったら、
http://localhost:3000/?school=seiritzj&school=kariakej&school=kokushih&school=josogj&school=shukuynj
というURLにアクセスする。

最初、どっかのサーバーにおいてサービス提供するかなと思ったけど、「サービス落ちてて説明会予約できなかった!(怒)」とか言われるとヤダなーとか思いつつ作った。

Laravel-AdminLTEを使ってみたメモ

AdminLTEを使いたかったのでこちらを使った。以前は違うの使ってた

インストールとかは簡単なので略

@extends('adminlte::master')

adminlte::は、ServiceProviderで付け加えてるっぽい。

vendor/jeroennoten/laravel-adminlte/src/ServiceProvider.php
<?php
    private function loadViews()
    {
        $viewsPath = $this->packagePath('resources/views');

        $this->loadViewsFrom($viewsPath, 'adminlte');

のadminlteがこのパッケージ(jeroennoten/Laravel-AdminLTE)のresources/viewsを使うPrefixになるみたい。

リンク先とかFormのAction先の変更

あとは、URLとかrouteを変えたら

{{ url(config('adminlte.dashboard_url', 'home')) }}

とかあるんだけど、これは固定なのでポチポチと変更をしていく。んだけど、これって

{{ route('user.login') }}

とかのRouteのname使った方が良いんじゃないかな?

Laravelのauthを追ってみたメモ

$artisan make:auth
したという前提で。

Laravel5.4でマルチ認証(userとadmin)を実装する方法 | 大分のITコンサルタント | 高橋商店 というのをやってみて、認証の中で振り分けをしたかったというケース。

普通にファーストアクセスで認証必要なURLにアクセスした場合

URL→routes→Controllerなので…

最初はController

認証を掛けたいController内でAuthを呼ぶ。

<?php
class UserController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('auth:user,user1,user2,user3');
    }

ってやると、MiddleWareで登録されたauthが起動する。

app/Http/Kernel.php

app/Http/Kernel.php
<?php
protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,

のauthにあるauthとさっきの$this->middleware('authのauthが対応している。auth:user, user1,user2,user3のuser1,user2,user3はMiddleWare(この場合Authenticate)の$guards引数になる。

ちなみに、$routeMiddleWareというのは$middleWareと違ってControllerでこうやって$this->middleware()して任意に起動させるもので、$middleWareは全てのrouteで起動する。

Authenticate.php

で、この起動するインスタンスは app/Http/Middleware/Authenticate.php なので、このインスタンスのhandleメソッドが実行される。

これは、Illuminate\Auth\Middleware\Authenticateを継承しているので、

Illuminate\Auth\Middleware\Authenticate
<?php
    public function handle($request, Closure $next, ...$guards)
    {
        $this->authenticate($request, $guards);

        return $next($request);
    }

がメソッド。で、この$guardsがさっきの$this->middleware('auth:user,user1,user2,user3')した引数になってて、

<?php
$guards = ['user', 'user1', 'user2', 'user3', ];

となっている。

Illuminate\Auth\Middleware\Authenticate

この $guardsはIlluminate\Auth\Middleware\Authenticate->authenticate()で使われて、

Illuminate\Auth\Middleware\Authenticate
<?php
    protected function authenticate($request, array $guards)
    {
        if (empty($guards)) {
            $guards = [null];
        }

        foreach ($guards as $guard) {
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }

        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

となっている。このforeach($guardsに $guards = ['user', 'user1', 'user2', 'user3', ]; が入ってくる。この場合4つのGuardsを使うことになる。

で、この$this->authは Illuminate/Auth/AuthManager.phpインスタンス

Illuminate\Auth\AuthManager と config/auth.php

ここで、check()メソッドを持つインスタンスをconfig/auth.phpに基づいて返す。

auth.phpでは、'guards' => に入っているもので

config/auth.php
<?php
    'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],
   ///////////////////////
    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],
        'user' => [
          'driver' => 'session',
          'provider' => 'custom_users',
        ],
   ///////////////////////
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

となっているので、$this->middleware('auth:user')とすると、driverにsession/providerにcustom_usersを使ったものがcheck()を実行する。
ちなみに、$this->middleware('auth')だと、defaultsのguardは'web'なのでこれまたdriverにsession/providerにusersを使ったものが実行される。

AuthのProviderは、Modelとつながっている

と、ここまでで、普通にControllerで$this->middleware('auth')としたときにはguardsのwebが呼ばれて、そのコードはproviderで示されるusersなのである。

で、Illuminate\Auth\Middleware\Authenticate::authenticate()の

Illuminate\Auth\Middleware\Authenticate::authenticate()
<?php
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }

では、$this->authがIlluminate\Auth\AuthManagerなので、そのguardメソッドを見るとAuthManager::resolve()で$guardに沿ったインスタンスを作っている。

'web'の場合、driverがsessionなのでAuthManager::createSessionDriver()である。これは$this->auth->guard($guard) がIlluminate\Auth\SessionGuardを作っている。

このSessionGuardインスタンスは、以下の経緯で作られる$providerを持っている。

Illuminate\Auth\CreatesUserProviders::createUserProvider($provider)   
で、$providerがeloquentであり、特に$providerに対するカスタムなCreatorが無い   
  ↓  
Illuminate\Auth\CreatesUserProviders::createEloquentProvider($config)   
これはEloquentUserProviderインスタンスをnewする。  
  ↓  
new EloquentUserProvider($this->app['hash'], $config['model']);  

また、SessionGuardインスタンスは、識別子$name(この場合は'user')と$sessionと(Http)Request $requestを持っている。

Illuminate\Auth\SessionGuard のcheck()

は、Illuminate\Auth\GuardHelpers::check()のtraitで、SessionGuard::user()の返り値がNULLでないかをチェックしている。
このuser()メソッドは

  • ログアウトのタイミングならnull
  • SessionGuard::$user (Illuminate\Contracts\Auth\Authenticatable) があればそれを返す。これは既にログイン処理がされている場合にはSessionからRestoreされるっぽい。

とかまぁなんかそんな感じ。

ここまでで、このSessionGuard::$userが得られれば認証通過。

ログインが認可されているセッションCOOKIEを持っている場合

SessionGuard::user()で$this->userを作成する処理を行う。

Illuminate\Auth\SessionGuard::user()
<?php
        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

の部分。以降では逐一DBにアクセスする必要は無くなる。

「永続ログイン」のチェックを入れてログインしていると、

Illuminate\Auth\SessionGuard::user()
<?php
        if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
            $this->user = $this->userFromRecaller($recaller);

の部分で$this->userの復帰をしてくれる。

LaravelはSessionを自前で実装している。

…とここまで読んで、LaravelはPHPの生のセッション機能を使ってなくて独自でSession構築しているということに気が付いた。えー。秘密情報をCOOKIEに入れてるのかな?…あれ?これ以前も「すげー」って思った記憶あるな…いや、それはRailsだったっけ?Laravelのデフォルトだとセッション情報はファイルで storage/framework/sessions に保存してる。

ログインの時の処理。

DBに接続する処理があるのはここの部分だけですね。

Auth\LoginController@login

メソッドとしてはこのメソッドなので
Illuminate\Foundation\Auth\AuthenticatesUsers::login()を見に行く。

Illuminate\Foundation\Auth\AuthenticatesUsers
<?php
   public function login(Request $request)
    {
        $this->validateLogin($request);

        // If the class is using the ThrottlesLogins trait, we can automatically throttle
        // the login attempts for this application. We'll key this by the username and
        // the IP address of the client making these requests into this application.
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }

        // If the login attempt was unsuccessful we will increment the number of attempts
        // to login and redirect the user back to the login form. Of course, when this
        // user surpasses their maximum number of attempts they will get locked out.
        $this->incrementLoginAttempts($request);

        return $this->sendFailedLoginResponse($request);
    }

validateLogin()

ここで、ユーザー名とパスワードの入力チェック。独自のチェックを入れたかったら、ControllerでOverrideすればいい。

attemptLogin()

hasTooManyLoginAttempts()は、多重ログインPOSTの防止かな。あまり読んでない。
ログイン処理のメインはattemptLogin()で

Illuminate\Foundation\Auth\AuthenticatesUsers
<?php
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

で、guard()で得たインスタンス(SessionGuard)のattempt()を実行する。これは

Illuminate\Auth\SessionGuard
<?php
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }

DBにつなぐところは、 $this->provider->retrieveByCredentials()の所。このproviderはlluminate\Auth\EloquentUserProviderである。このretrieveByCredentialsメソッドが

lluminate\Auth\EloquentUserProvider
<?php
   public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists('password', $credentials))) {
            return;
        }

        // First we will add each credential element to the query as a where clause.
        // Then we can execute the query and, if we found a user, return it in a
        // Eloquent User "model" that will be utilized by the Guard instances.
        $query = $this->newModelQuery();

        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }

            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }

        return $query->first();
    }

となっている。newModelQuery()でQueryを取っているが、このORMはauth.phpに記述した 'providers' => ['users' => [ のmodelのApp\User::classとなる。

ちなみにこの定義が無いとデフォルトのグローバルの\Illuminate\Database\Eloquent\Builderがnewされて作られる。…が、ほとんど意味が無いだろう。テーブル持って無いし。ちゃんと定義しろということですね。これで、メールアドレスを指定でDBから1行を抜き出す。

で、その後は更にhasValidCredentials()に流れて、

Illuminate\Auth\SessionGuard
<?php
    protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }

で、この$this->providerはIlluminate\Auth\EloquentUserProviderのインスタンスで、validateCredentialsでパスワードと比較している。

Illuminate\Auth\EloquentUserProvider
<?php
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }

ちなみに、この時UserNameとして何を使いたいかというと、Illuminate\Foundation\Auth\AuthenticatesUsersのusername()メソッドをControllerで上書きすればよい。デフォルトだと以下のようにemailが使われる。

Illuminate\Foundation\Auth\AuthenticatesUsers
<?php
    public function username()
    {
        return 'email';
    }

ログインに失敗した時

単純に権限のない所に入ってしまった場合

これは、Illuminate\Auth\Middleware\Authenticate->authenticate()で例外AuthenticationExceptionを投げられる。

これは、app\Exceptions\Handler::unauthenticated() でキャッチされる。

app\Exceptions\Handler
<?php
    protected function unauthenticated($request, AuthenticationException $exception)
    {
      if ($request->expectsJson()) {
        return response()->json(['error' => 'Unauthenticated.'], 401);
      }

      //追加
      if (in_array('admin', $exception->guards())) {
        return redirect()->guest('admin/login');
      }
      //追加

      return redirect()->guest('login');
    }

エラーハンドリング

で、なんでAuthenticationException エラーの時だけunauthenticatedメソッドが使われるかというと、最終的にはapp\Exceptions\Handler::render()でキャッチされるのだけど、 Illuminate\Foundation\Exceptions\Handlerで、

Illuminate\Foundation\Exceptions\Handler
<?php
    public function render($request, Exception $e)
    {
        if (method_exists($e, 'render') && $response = $e->render($request)) {
            return Router::toResponse($request, $response);
        } elseif ($e instanceof Responsable) {
            return $e->toResponse($request);
        }

        $e = $this->prepareException($e);

        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            return $this->convertValidationExceptionToResponse($e, $request);
        }

        return $request->expectsJson()
                        ? $this->prepareJsonResponse($request, $e)
                        : $this->prepareResponse($request, $e);
    }

となっていて、AuthenticationExceptionの例外の場合はunauthenticated()メソッドが実行されるため。

権限のないところに入ったら、所定のログイン場所に飛ばしたい

投げられる認証の例外AuthenticationExceptionのnewはapp\Http\Middleware\Authenticateの継承元である

Illuminate\Auth\Middleware\Authenticate
<?php
   protected function authenticate($request, array $guards)
    {
        // 省略
        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

なので、$this->redirectTo($request)がキーになるので、app\Http\Middleware\AuthenticateのredirectTo先を変更してやると良い。

…が、このMiddlewareの$thisは自分がadminなのかuserなのかが分からないので、どこにredirectするべきかが分からない…

というのは、

Illuminate\Auth\Middleware\Authenticate
<?php
    protected function authenticate($request, array $guards)
    {
        /// 省略
        throw new AuthenticationException(
            'Unauthenticated.', $guards, $this->redirectTo($request)
        );
    }

とnewしているところで$guardsを渡してくれてない…

ということで、Http\Middleware\Authenticateにguardsを渡してその先頭を取る

どのguardsで引っかかったかというのはコアのAuthenticateでは取れないので、

app\Http\Middleware\Authenticate
<?php
    /**
     * @var Array
     */
    private $guards ;

    public function handle($request, Closure $next, ...$guards)
    {
        $this->guards = $guards;

として、handle時に持たせてやり、configのauthにredirectoToを生やして

config/auth.php
<?php
   'guards' => [
        'user' => [
            'driver' => 'session',
            'provider' => 'users',
            'redirectTo' => 'user.login'
        ],

redirectoToで使用した。

app\Http\Middleware\Authenticate
<?php
    protected function redirectTo($request)
    {
        $redirectTo = sprintf('auth.guards.%s.redirectTo', current($this->guards));
        $redirectTo = \Config::get($redirectTo) ?? '';
        if (!$request->expectsJson()) {
            return route($redirectTo);
        }
    }

んー。まぁ、こんな感じで。

Auth::routes()でのRouteの追加

routes/web.phpに Auth::routes(); を記載すると、Illuminate\Routing\Router->auth()が実行される。

Illuminate\Routing\Router
<?php
    public function auth(array $options = [])
    {
        // Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');

        // Registration Routes...
        if ($options['register'] ?? true) {
            $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
            $this->post('register', 'Auth\RegisterController@register');
        }

        // Password Reset Routes...
        if ($options['reset'] ?? true) {
            $this->resetPassword(); // このメソッドで以下の4つ追加
//        $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
//        $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
//        $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
//        $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update');
        }

        // Email Verification Routes...
        if ($options['verify'] ?? false) {
            $this->emailVerification(); // このメソッドで以下の3つ追加
//        $this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice');
//        $this->get('email/verify/{id}', 'Auth\VerificationController@verify')->name('verification.verify');
//        $this->get('email/resend', 'Auth\VerificationController@resend')->name('verification.resend');
        }
    }

となっているので、手動で追加したい場合はこのrouteを記載する。

Login時のSessionGuardをうまく働かせる

ログインした状態で認証をパスする場合、Illuminate\Auth\SessionGuard->user()でセッションを見るんだけど、この場合

$this->middleware('auth:user')

で保存しなければならない。チェックする時が「auth:user」でなので。これを指定せずにいると、デフォルトのwebが使われてセッションに保存されてしまって認証状態ではないと判断してしまう。

SessionGuard::login() でSessionにデータを保存する処理を追ってみる

結局、SessionGuardをnewする所で$nameを与えているので、

Illuminate\Foundation\Auth\AuthenticatesUsers.php
<?php
    protected function guard()
    {
        return Auth::guard();
    }

ここで$nameを与えてないので仕方ない…

ということで、このAuth::gurad($name)っていう感じのClassを作ってOverrideする。

app\Http\Controllers\Auth\LoginController
<?php
// use Illuminate\Foundation\Auth\AuthenticatesUsers
class LoginController extends Controller
{
    use AuthenticatesUsers;

    protected $guard_name = 'user';

とControllerで$nameを指定できるようにしオリジナルのを使わないようにして、自前のAuthenticatesUsersを作る。

で、同じディレクトリに

app\Http\Controllers\Auth\AuthenticatesUsers
<?php
namespace App\Http\Controllers\Auth;

use Illuminate\Foundation\Auth\AuthenticatesUsers as AuthenticatesUsersOrig;
use Illuminate\Support\Facades\Auth;

trait AuthenticatesUsers
{
    use  AuthenticatesUsersOrig;

    protected function guard()
    {
        return Auth::guard($this->guard_name);
    }
}

とやっておく。

2019年度 卓球日本リーグ選手データ

チーム紹介 | 日本卓球リーグ実業団連盟からデータを取得してまとめた。一覧はこちら

利き手

男子は4人に1人が左。意外と多い。

全体 206名 男子 111名 女子 95名
165 80% 86 77% 79 83%
41 20% 25 23% 16 17%

ラケット

もはやペンは絶滅寸前。しかし、ペン平均年齢は26歳(全体平均年齢24歳)と意外と年寄りばかりではない。*1

全体 206名 男子 111名 女子 95名
シェーク 199 97% 107 96% 92 97%
ペン 7 3% 4 4% 3 3%

ラバー

燦然と輝く一枚表は、森美紗樹選手。

全体 206名 男子 111名 女子 95名
裏裏 150 73% 99 89% 51 54%
裏表 38 18% 4 4% 34 36%
表裏 5 2% 2 2% 3 3%
裏粒 10 5% 4 4% 6 6%
2 1% 2 2% 0 0%
一枚表 1 0% 0 0% 1 1%

戦型

女子の異質攻撃は鈴木理彩選手。 安藤みなみ選手はJTTAサイトでは「攻撃」と書いてあって、なんだかよくわからない…

全体 206名 男子 111名 女子 95名
ドライブ 158 77% 102 92% 56 59%
カット 18 9% 5 5% 13 14%
前陣速攻 28 14% 4 4% 24 25%
異質攻撃 1 1% 0 0% 1 1%
攻撃 1 1% 0 0% 1 1%

*1:吉田海偉選手がひとりで2歳おしあげているので実質24歳で変わらない。

Tリーグの観客数(2018年度)

先週末から2年目のTリーグが始まった。

tleague.jp

Yahoo!ニュースとかにも出てなかったので気になって観客数を調べてみた。

こちらに結果のまとめデータを出すために作ったスクリプトを置いておきます。

去年の開幕戦は、両国国技館で盛大にやったのでこんな感じ。

試合日 曜日 開始時刻 男女 ホームチーム 観客数 アウェイチーム 会場
2018/10/24 19:00 男子 TT彩たま 5624 KM東京 墨田区両国国技
2018/10/25 19:00 女子 名古屋 4572 日本生命 墨田区両国国技

先週の2年目開幕戦は沖縄宜野湾市なのでこんな感じ。

試合日 曜日 開始時刻 男女 ホームチーム 観客数 アウェイチーム 会場
2019/10/19 18:00 男子 琉球 812 岡山 宜野湾宜野湾市
2019/10/20 13:00 男子 琉球 971 KM東京 宜野湾宜野湾市

うーん、厳しい・・・しかし、宜野湾市ということを勘案すると

試合日 曜日 開始時刻 男女 ホームチーム 観客数 アウェイチーム 会場
2018/12/3 19:00 男子 琉球 1720 KM東京 宜野湾宜野湾市
2018/12/4 19:00 男子 琉球 814 TT彩たま 宜野湾宜野湾市
2018/12/24 14:00 男子 琉球 1002 岡山 宜野湾宜野湾市
2018/12/25 19:00 男子 琉球 1207 KM東京 宜野湾宜野湾市
2019/2/21 19:00 男子 琉球 510 岡山 宜野湾宜野湾市
2019/2/22 19:00 男子 琉球 615 TT彩たま 宜野湾宜野湾市
2019/10/19 18:00 男子 琉球 812 岡山 宜野湾宜野湾市
2019/10/20 13:00 男子 琉球 971 KM東京 宜野湾宜野湾市

こんな感じ。開幕カードはさすがにアレとしても去年のKM東京との試合が平日に行われているのを勘案してもうーん…。

なんか厳しそうね。

ついでに

クロス集計で。

会場別

会場 累積観客 試合日数 1日平均
墨田区両国国技 20351 4 5088
横浜市横浜文体 4677 2 2339
渋谷区青山学院 3751 2 1876
仙台市ゼビオA 3166 2 1583
越谷市越谷総合 2757 2 1379
春日部ウィング 2651 2 1326
福井市福井県 2579 2 1290
立川市立川立飛 15990 13 1230
金沢市いしかわ 2410 2 1205
さいたまサイデン 2239 2 1120
岡山市岡山武道 8785 8 1098
名古屋武田テバ 11573 11 1052
帯広市帯広総合 2073 2 1037
西尾市西尾総合 1987 2 994
宜野湾宜野湾市 5868 6 978
羽曳野はびきの 3779 4 945
北九州市北九州 1826 2 913
大阪市大阪府 1784 2 892
高砂市高砂総合 5060 6 843
岸和田岸和田総 1550 2 775
貝塚市貝塚総合 1488 2 744
苫小牧苫小牧総 1305 2 653
豊見城豊見城市 1110 2 555
大阪市おおアリ 1019 2 510

チーム別

男子

ホーム主催 累積観客
KM東京 20505
TT彩たま 17902
岡山 10059
琉球 7772
アウェイ 累積観客
KM東京 19271
岡山 15966
琉球 12488
TT彩たま 8513
合計 累積観客
KM東京 39776
TT彩たま 26415
岡山 26025
琉球 20260

女子

ホーム主催 累積観客
KA神奈川 22788
名古屋 14962
日本生命 8559
ニッペM 7231
アウェイ 累積観客
日本生命 24036
ニッペM 11276
KA神奈川 9154
名古屋 9074
合計 累積観客
日本生命 32595
KA神奈川 31942
名古屋 24036
ニッペM 18507

土日・平日(開幕戦・最終戦除く)

土日・平日 平均観客数
土日 1241
平日 908