npb-mcp-server 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/scrapers/playerDetails.d.ts +7 -0
- package/dist/scrapers/playerDetails.d.ts.map +1 -0
- package/dist/scrapers/playerDetails.js +460 -0
- package/dist/scrapers/playerDetails.js.map +1 -0
- package/dist/scrapers/playerDetails.test.d.ts +2 -0
- package/dist/scrapers/playerDetails.test.d.ts.map +1 -0
- package/dist/scrapers/playerDetails.test.js +549 -0
- package/dist/scrapers/playerDetails.test.js.map +1 -0
- package/dist/scrapers/players.d.ts.map +1 -1
- package/dist/scrapers/players.js +123 -20
- package/dist/scrapers/players.js.map +1 -1
- package/dist/scrapers/players.test.d.ts +2 -0
- package/dist/scrapers/players.test.d.ts.map +1 -0
- package/dist/scrapers/players.test.js +385 -0
- package/dist/scrapers/players.test.js.map +1 -0
- package/dist/tools/get-player-details.d.ts +13 -0
- package/dist/tools/get-player-details.d.ts.map +1 -0
- package/dist/tools/get-player-details.js +28 -0
- package/dist/tools/get-player-details.js.map +1 -0
- package/dist/tools/get-player-details.test.d.ts +2 -0
- package/dist/tools/get-player-details.test.d.ts.map +1 -0
- package/dist/tools/get-player-details.test.js +201 -0
- package/dist/tools/get-player-details.test.js.map +1 -0
- package/dist/tools/search-players.d.ts +1 -0
- package/dist/tools/search-players.d.ts.map +1 -1
- package/dist/tools/search-players.js +29 -2
- package/dist/tools/search-players.js.map +1 -1
- package/dist/types/npb.d.ts +113 -0
- package/dist/types/npb.d.ts.map +1 -1
- package/dist/utils/nameNormalizer.d.ts +21 -0
- package/dist/utils/nameNormalizer.d.ts.map +1 -0
- package/dist/utils/nameNormalizer.js +46 -0
- package/dist/utils/nameNormalizer.js.map +1 -0
- package/dist/utils/nameNormalizer.test.d.ts +2 -0
- package/dist/utils/nameNormalizer.test.d.ts.map +1 -0
- package/dist/utils/nameNormalizer.test.js +82 -0
- package/dist/utils/nameNormalizer.test.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- NPB全12球団の情報取得
|
|
12
12
|
- 選手一覧の取得(球団別)
|
|
13
13
|
- 選手検索(名前、ポジション、背番号など)
|
|
14
|
+
- 選手の詳細情報取得(プロフィール、年度別成績、通算成績)
|
|
14
15
|
- データキャッシング機能
|
|
15
16
|
- 2つのトランスポートモード対応(stdio / HTTP)
|
|
16
17
|
|
|
@@ -168,6 +169,9 @@ NPB全12球団の一覧を取得します。
|
|
|
168
169
|
|
|
169
170
|
**パラメータ:**
|
|
170
171
|
- `name` (optional): 選手名(部分一致)
|
|
172
|
+
- スペースの有無に関わらず検索可能(例: 「牧秀悟」「牧 秀悟」「牧 秀悟」)
|
|
173
|
+
- ひらがなでの検索も可能(例: 「まき」「まき しゅうご」)
|
|
174
|
+
- カタカナでの検索も可能(例: 「マキ」)
|
|
171
175
|
- `team_id` (optional): 球団ID
|
|
172
176
|
- `position` (optional): ポジション(`pitcher`, `catcher`, `infielder`, `outfielder`)
|
|
173
177
|
- `number` (optional): 背番号
|
|
@@ -180,6 +184,64 @@ NPB全12球団の一覧を取得します。
|
|
|
180
184
|
}
|
|
181
185
|
```
|
|
182
186
|
|
|
187
|
+
```json
|
|
188
|
+
{
|
|
189
|
+
"name": "まき しゅうご"
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### 4. get_player_details
|
|
194
|
+
選手の詳細情報(プロフィール、年度別成績、通算成績)を取得します。
|
|
195
|
+
|
|
196
|
+
**パラメータ:**
|
|
197
|
+
- `player_id` (required): 8桁の選手ID
|
|
198
|
+
|
|
199
|
+
**選手IDの取得方法:**
|
|
200
|
+
`get_team_players`や`search_players`で取得した選手情報の`playerId`フィールドを使用してください。
|
|
201
|
+
|
|
202
|
+
**例:**
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"player_id": "51155136"
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**レスポンス:**
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"profile": {
|
|
213
|
+
"playerId": "51155136",
|
|
214
|
+
"name": "東 克樹",
|
|
215
|
+
"uniformNumber": "11",
|
|
216
|
+
"team": "横浜DeNAベイスターズ",
|
|
217
|
+
"position": "投手",
|
|
218
|
+
"throwingHand": "左",
|
|
219
|
+
"battingHand": "左",
|
|
220
|
+
"height": "170cm",
|
|
221
|
+
"weight": "80kg",
|
|
222
|
+
"birthDate": "1995年11月29日",
|
|
223
|
+
"career": "愛工大名電高→立命館大",
|
|
224
|
+
"draftInfo": "2017年ドラフト1位"
|
|
225
|
+
},
|
|
226
|
+
"pitchingStats": [
|
|
227
|
+
{
|
|
228
|
+
"year": "2018",
|
|
229
|
+
"team": "DeNA",
|
|
230
|
+
"games": 26,
|
|
231
|
+
"wins": 9,
|
|
232
|
+
"losses": 6,
|
|
233
|
+
...
|
|
234
|
+
}
|
|
235
|
+
],
|
|
236
|
+
"careerPitching": {
|
|
237
|
+
"games": 120,
|
|
238
|
+
"wins": 60,
|
|
239
|
+
"losses": 30,
|
|
240
|
+
"era": 2.43
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
183
245
|
## データソース
|
|
184
246
|
|
|
185
247
|
このサーバーは [NPB公式サイト](https://npb.jp/) からWeb Scrapingでデータを取得しています。
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { serve } from '@hono/node-server';
|
|
|
7
7
|
import { listTeams } from './tools/list-teams.js';
|
|
8
8
|
import { getTeamPlayersHandler } from './tools/get-team-players.js';
|
|
9
9
|
import { searchPlayers } from './tools/search-players.js';
|
|
10
|
+
import { getPlayerDetailsHandler } from './tools/get-player-details.js';
|
|
10
11
|
/**
|
|
11
12
|
* NPB MCP Server
|
|
12
13
|
* 日本プロ野球(NPB)の選手情報を提供するMCPサーバー
|
|
@@ -88,6 +89,20 @@ class NPBServer {
|
|
|
88
89
|
},
|
|
89
90
|
},
|
|
90
91
|
},
|
|
92
|
+
{
|
|
93
|
+
name: 'get_player_details',
|
|
94
|
+
description: '選手の詳細情報(プロフィール、年度別成績、通算成績)を取得します。',
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
player_id: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
description: '8桁の選手ID(例: 51155136)',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: ['player_id'],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
91
106
|
],
|
|
92
107
|
}));
|
|
93
108
|
// ツール実行ハンドラー
|
|
@@ -104,6 +119,11 @@ class NPBServer {
|
|
|
104
119
|
return await getTeamPlayersHandler(args);
|
|
105
120
|
case 'search_players':
|
|
106
121
|
return await searchPlayers(args || {});
|
|
122
|
+
case 'get_player_details':
|
|
123
|
+
if (!args || !args.player_id) {
|
|
124
|
+
throw new McpError(ErrorCode.InvalidParams, 'player_id is required');
|
|
125
|
+
}
|
|
126
|
+
return await getPlayerDetailsHandler(args);
|
|
107
127
|
default:
|
|
108
128
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
109
129
|
}
|
|
@@ -187,6 +207,20 @@ class NPBServer {
|
|
|
187
207
|
},
|
|
188
208
|
},
|
|
189
209
|
},
|
|
210
|
+
{
|
|
211
|
+
name: 'get_player_details',
|
|
212
|
+
description: '選手の詳細情報(プロフィール、年度別成績、通算成績)を取得します。',
|
|
213
|
+
inputSchema: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
player_id: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: '8桁の選手ID(例: 51155136)',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
required: ['player_id'],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
190
224
|
],
|
|
191
225
|
};
|
|
192
226
|
return c.json({
|
|
@@ -211,6 +245,12 @@ class NPBServer {
|
|
|
211
245
|
case 'search_players':
|
|
212
246
|
result = await searchPlayers(args || {});
|
|
213
247
|
break;
|
|
248
|
+
case 'get_player_details':
|
|
249
|
+
if (!args || !args.player_id) {
|
|
250
|
+
throw new McpError(ErrorCode.InvalidParams, 'player_id is required');
|
|
251
|
+
}
|
|
252
|
+
result = await getPlayerDetailsHandler(args);
|
|
253
|
+
break;
|
|
214
254
|
default:
|
|
215
255
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
216
256
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,SAAS,EACT,QAAQ,GACT,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PlayerDetails } from '../types/npb.js';
|
|
2
|
+
export declare function scrapePlayerDetailsFromHTML(html: string, playerId: string): PlayerDetails;
|
|
3
|
+
/**
|
|
4
|
+
* 選手詳細情報を取得(キャッシュあり)
|
|
5
|
+
*/
|
|
6
|
+
export declare function getPlayerDetails(playerId: string): Promise<PlayerDetails>;
|
|
7
|
+
//# sourceMappingURL=playerDetails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playerDetails.d.ts","sourceRoot":"","sources":["../../src/scrapers/playerDetails.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,aAAa,EASd,MAAM,iBAAiB,CAAC;AA4dzB,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CA4BzF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAoB/E"}
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import * as cheerio from 'cheerio';
|
|
2
|
+
import { fetchHTML } from '../utils/http.js';
|
|
3
|
+
import { globalCache } from '../utils/cache.js';
|
|
4
|
+
/**
|
|
5
|
+
* 数値文字列をパースする(空文字列や'-'の場合は0を返す)
|
|
6
|
+
*/
|
|
7
|
+
function parseNumber(text) {
|
|
8
|
+
const trimmed = text.trim();
|
|
9
|
+
if (trimmed === '' || trimmed === '-' || trimmed === '----') {
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
const parsed = parseFloat(trimmed);
|
|
13
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* HTMLから選手プロフィールを抽出
|
|
17
|
+
*/
|
|
18
|
+
function scrapePlayerProfile($, playerId) {
|
|
19
|
+
const profile = {
|
|
20
|
+
playerId,
|
|
21
|
+
};
|
|
22
|
+
// 選手名を取得
|
|
23
|
+
const nameElement = $('h1').first();
|
|
24
|
+
profile.name = nameElement.text().trim();
|
|
25
|
+
// プロフィール情報を抽出
|
|
26
|
+
const profileItems = $('table tr, dl dd, .playerInfo li');
|
|
27
|
+
profileItems.each((_, elem) => {
|
|
28
|
+
const text = $(elem).text().trim();
|
|
29
|
+
// ふりがな(ひらがなと中点のパターン)
|
|
30
|
+
if (text.match(/^[ぁ-ん・]+$/)) {
|
|
31
|
+
profile.nameKana = text;
|
|
32
|
+
}
|
|
33
|
+
// 背番号
|
|
34
|
+
if (text.match(/^\d{1,3}$/)) {
|
|
35
|
+
profile.uniformNumber = text;
|
|
36
|
+
}
|
|
37
|
+
// ポジション(完全一致でチェック)
|
|
38
|
+
if (text === '投手') {
|
|
39
|
+
profile.position = '投手';
|
|
40
|
+
}
|
|
41
|
+
else if (text === '捕手') {
|
|
42
|
+
profile.position = '捕手';
|
|
43
|
+
}
|
|
44
|
+
else if (text === '内野手') {
|
|
45
|
+
profile.position = '内野手';
|
|
46
|
+
}
|
|
47
|
+
else if (text === '外野手') {
|
|
48
|
+
profile.position = '外野手';
|
|
49
|
+
}
|
|
50
|
+
// 投打
|
|
51
|
+
if (text.includes('投') && text.includes('打')) {
|
|
52
|
+
const match = text.match(/([左右両])投([左右両])打/);
|
|
53
|
+
if (match) {
|
|
54
|
+
profile.throwingHand = match[1];
|
|
55
|
+
profile.battingHand = match[2];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 身長体重
|
|
59
|
+
if (text.includes('cm') && text.includes('kg')) {
|
|
60
|
+
const match = text.match(/(\d+)cm[//](\d+)kg/);
|
|
61
|
+
if (match) {
|
|
62
|
+
profile.height = `${match[1]}cm`;
|
|
63
|
+
profile.weight = `${match[2]}kg`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 生年月日
|
|
67
|
+
if (text.match(/\d{4}年\d{1,2}月\d{1,2}日/)) {
|
|
68
|
+
profile.birthDate = text;
|
|
69
|
+
}
|
|
70
|
+
// 経歴
|
|
71
|
+
if (text.includes('高') || text.includes('大')) {
|
|
72
|
+
if (!profile.career) {
|
|
73
|
+
profile.career = text;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ドラフト情報
|
|
77
|
+
if (text.includes('ドラフト')) {
|
|
78
|
+
profile.draftInfo = text;
|
|
79
|
+
// ドラフト情報から入団年を抽出(例: "2017年ドラフト1位" → 2017)
|
|
80
|
+
const yearMatch = text.match(/(\d{4})年ドラフト/);
|
|
81
|
+
if (yearMatch) {
|
|
82
|
+
const year = parseInt(yearMatch[1], 10);
|
|
83
|
+
if (!isNaN(year) && year >= 1965 && year <= 2100) {
|
|
84
|
+
// 妥当な年の範囲をチェック
|
|
85
|
+
profile.joinedYear = year;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// 入団年がまだ取得できていない場合、成績テーブルの最初の年度から推測
|
|
91
|
+
if (!profile.joinedYear) {
|
|
92
|
+
// 投手成績または打撃成績の最初の年度を取得
|
|
93
|
+
const firstYearRow = $('table tbody tr')
|
|
94
|
+
.filter((_, row) => {
|
|
95
|
+
const firstCell = $(row).find('td').first().text().trim();
|
|
96
|
+
return /^\d{4}$/.test(firstCell); // 4桁の数字(年度)
|
|
97
|
+
})
|
|
98
|
+
.first();
|
|
99
|
+
if (firstYearRow.length > 0) {
|
|
100
|
+
const firstYearText = firstYearRow.find('td').first().text().trim();
|
|
101
|
+
const firstYear = parseInt(firstYearText, 10);
|
|
102
|
+
if (!isNaN(firstYear) && firstYear >= 1965 && firstYear <= 2100) {
|
|
103
|
+
// 最初の年度の前年が入団年(ドラフト年)の可能性が高い
|
|
104
|
+
// ただし、確実ではないので、ドラフト情報がない場合はnullのまま
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 球団名を取得
|
|
109
|
+
profile.team = $('title').text().split('|')[1]?.trim() || '';
|
|
110
|
+
return profile;
|
|
111
|
+
}
|
|
112
|
+
// 注意: 表彰歴の抽出機能は削除されました。
|
|
113
|
+
// NPB公式サイトの選手詳細ページには表彰歴セクションが存在しないため、
|
|
114
|
+
// 現時点では取得できません。
|
|
115
|
+
/**
|
|
116
|
+
* テーブルから投手成績を抽出
|
|
117
|
+
*/
|
|
118
|
+
function scrapePitchingStats($) {
|
|
119
|
+
const stats = [];
|
|
120
|
+
let career;
|
|
121
|
+
// 投手成績テーブルを探す
|
|
122
|
+
$('table').each((_, table) => {
|
|
123
|
+
const $table = $(table);
|
|
124
|
+
const headerText = $table.find('th').text();
|
|
125
|
+
// 投手成績のテーブルかチェック
|
|
126
|
+
if (headerText.includes('防御率') ||
|
|
127
|
+
headerText.includes('勝利') ||
|
|
128
|
+
headerText.includes('登板')) {
|
|
129
|
+
// ヘッダー行から列の位置を特定
|
|
130
|
+
const headerRow = $table.find('thead tr, tr:first-child').first();
|
|
131
|
+
const headerCells = headerRow.find('th, td');
|
|
132
|
+
const columnMap = {};
|
|
133
|
+
headerCells.each((index, cell) => {
|
|
134
|
+
const headerText = $(cell).text().trim();
|
|
135
|
+
if (headerText) {
|
|
136
|
+
columnMap[headerText] = index;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
$table.find('tbody tr, tr').each((_, row) => {
|
|
140
|
+
const cells = $(row).find('td');
|
|
141
|
+
if (cells.length === 0)
|
|
142
|
+
return;
|
|
143
|
+
const yearText = $(cells[0]).text().trim();
|
|
144
|
+
// 通算成績の行
|
|
145
|
+
if (yearText.includes('通') || yearText.includes('通算')) {
|
|
146
|
+
career = {
|
|
147
|
+
games: parseNumber($(cells[columnMap['登板'] || 2]).text()),
|
|
148
|
+
wins: parseNumber($(cells[columnMap['勝利'] || 3]).text()),
|
|
149
|
+
losses: parseNumber($(cells[columnMap['敗北'] || 4]).text()),
|
|
150
|
+
saves: parseNumber($(cells[columnMap['セーブ'] || 5]).text()),
|
|
151
|
+
holds: parseNumber($(cells[columnMap['H'] || 6]).text()),
|
|
152
|
+
era: parseNumber($(cells[cells.length - 1]).text()),
|
|
153
|
+
};
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// 年度別成績
|
|
157
|
+
if (yearText.match(/^\d{4}$/)) {
|
|
158
|
+
// セルインデックスを動的に取得(ヘッダー行から列位置を特定)
|
|
159
|
+
const getCellValue = (columnName, defaultIndex) => {
|
|
160
|
+
const index = columnMap[columnName];
|
|
161
|
+
if (index !== undefined && index < cells.length) {
|
|
162
|
+
return parseNumber($(cells[index]).text());
|
|
163
|
+
}
|
|
164
|
+
return parseNumber($(cells[defaultIndex] || cells[0]).text());
|
|
165
|
+
};
|
|
166
|
+
const getCellValueWithDecimal = (columnName, defaultIndex) => {
|
|
167
|
+
const index = columnMap[columnName];
|
|
168
|
+
if (index !== undefined && index < cells.length) {
|
|
169
|
+
let text = $(cells[index]).text().trim();
|
|
170
|
+
// 次のセルが小数部分(.1, .2など)の場合は結合
|
|
171
|
+
if (index + 1 < cells.length) {
|
|
172
|
+
const nextCellText = $(cells[index + 1])
|
|
173
|
+
.text()
|
|
174
|
+
.trim();
|
|
175
|
+
if (nextCellText.match(/^\.\d+$/)) {
|
|
176
|
+
text += nextCellText;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return parseNumber(text);
|
|
180
|
+
}
|
|
181
|
+
// フォールバック: デフォルトインデックスから取得
|
|
182
|
+
if (defaultIndex < cells.length) {
|
|
183
|
+
let text = $(cells[defaultIndex]).text().trim();
|
|
184
|
+
if (defaultIndex + 1 < cells.length) {
|
|
185
|
+
const nextCellText = $(cells[defaultIndex + 1])
|
|
186
|
+
.text()
|
|
187
|
+
.trim();
|
|
188
|
+
if (nextCellText.match(/^\.\d+$/)) {
|
|
189
|
+
text += nextCellText;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return parseNumber(text);
|
|
193
|
+
}
|
|
194
|
+
return 0;
|
|
195
|
+
};
|
|
196
|
+
const stat = {
|
|
197
|
+
year: yearText,
|
|
198
|
+
team: $(cells[columnMap['所属球団'] ?? 1])
|
|
199
|
+
.text()
|
|
200
|
+
.trim(),
|
|
201
|
+
games: getCellValue('登板', 2),
|
|
202
|
+
wins: getCellValue('勝利', 3),
|
|
203
|
+
losses: getCellValue('敗北', 4),
|
|
204
|
+
saves: getCellValue('セーブ', 5),
|
|
205
|
+
holds: getCellValue('H', 6),
|
|
206
|
+
hp: getCellValue('HP', 7),
|
|
207
|
+
completeGames: getCellValue('完投', 8),
|
|
208
|
+
shutouts: getCellValue('完封勝', 9),
|
|
209
|
+
noWalks: getCellValue('無四球', 10),
|
|
210
|
+
winningPercentage: getCellValue('勝率', 11),
|
|
211
|
+
batters: getCellValue('打者', 12),
|
|
212
|
+
innings: getCellValueWithDecimal('投球回', 13),
|
|
213
|
+
hits: getCellValueWithDecimal('安打', 14),
|
|
214
|
+
homeRuns: getCellValue('本塁打', 15),
|
|
215
|
+
strikeouts: getCellValue('三振', 18),
|
|
216
|
+
strikeoutsPer9: getCellValue('奪三振率', 17),
|
|
217
|
+
walks: getCellValue('四球', 16),
|
|
218
|
+
hitByPitch: getCellValue('死球', 17),
|
|
219
|
+
wildPitches: getCellValue('暴投', 19),
|
|
220
|
+
balks: getCellValue('ボーク', 20),
|
|
221
|
+
runsAllowed: getCellValue('失点', 21),
|
|
222
|
+
earnedRuns: getCellValue('自責点', 22),
|
|
223
|
+
era: parseNumber($(cells[columnMap['防御率'] ?? cells.length - 1]).text()),
|
|
224
|
+
};
|
|
225
|
+
// 詳細統計情報を計算
|
|
226
|
+
if (stat.innings > 0) {
|
|
227
|
+
stat.whip = (stat.walks + stat.hits) / stat.innings;
|
|
228
|
+
stat.homeRunsPer9 = (stat.homeRuns * 9) / stat.innings;
|
|
229
|
+
stat.walksPer9 = (stat.walks * 9) / stat.innings;
|
|
230
|
+
if (stat.walks > 0) {
|
|
231
|
+
stat.strikeoutWalkRatio = stat.strikeouts / stat.walks;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (stat.batters > 0) {
|
|
235
|
+
// 被打率 = 被安打 / (打者数 - 与四球 - 与死球)
|
|
236
|
+
const atBatsAgainst = stat.batters - stat.walks - stat.hitByPitch;
|
|
237
|
+
if (atBatsAgainst > 0) {
|
|
238
|
+
stat.battingAverageAgainst = stat.hits / atBatsAgainst;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
stats.push(stat);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
return { stats, career };
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* テーブルから打撃成績を抽出
|
|
250
|
+
*/
|
|
251
|
+
function scrapeBattingStats($) {
|
|
252
|
+
const stats = [];
|
|
253
|
+
let career;
|
|
254
|
+
// 打撃成績テーブルを探す
|
|
255
|
+
$('table').each((_, table) => {
|
|
256
|
+
const $table = $(table);
|
|
257
|
+
const headerText = $table.find('th').text();
|
|
258
|
+
// 打撃成績のテーブルかチェック(投手成績テーブルを除外)
|
|
259
|
+
if ((headerText.includes('打率') || headerText.includes('安打') || headerText.includes('打席')) &&
|
|
260
|
+
!headerText.includes('防御率') &&
|
|
261
|
+
!headerText.includes('勝利')) {
|
|
262
|
+
$table.find('tbody tr, tr').each((_, row) => {
|
|
263
|
+
const cells = $(row).find('td');
|
|
264
|
+
if (cells.length === 0)
|
|
265
|
+
return;
|
|
266
|
+
const yearText = $(cells[0]).text().trim();
|
|
267
|
+
// 通算成績の行
|
|
268
|
+
if (yearText.includes('通') || yearText.includes('通算')) {
|
|
269
|
+
career = {
|
|
270
|
+
games: parseNumber($(cells[2]).text()),
|
|
271
|
+
plateAppearances: parseNumber($(cells[3]).text()),
|
|
272
|
+
atBats: parseNumber($(cells[4]).text()),
|
|
273
|
+
hits: parseNumber($(cells[6]).text()),
|
|
274
|
+
average: parseNumber($(cells[21]).text()),
|
|
275
|
+
};
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// 年度別成績
|
|
279
|
+
if (yearText.match(/^\d{4}$/)) {
|
|
280
|
+
const stat = {
|
|
281
|
+
year: yearText,
|
|
282
|
+
team: $(cells[1]).text().trim(),
|
|
283
|
+
games: parseNumber($(cells[2]).text()),
|
|
284
|
+
plateAppearances: parseNumber($(cells[3]).text()),
|
|
285
|
+
atBats: parseNumber($(cells[4]).text()),
|
|
286
|
+
runs: parseNumber($(cells[5]).text()),
|
|
287
|
+
hits: parseNumber($(cells[6]).text()),
|
|
288
|
+
doubles: parseNumber($(cells[7]).text()),
|
|
289
|
+
triples: parseNumber($(cells[8]).text()),
|
|
290
|
+
homeRuns: parseNumber($(cells[9]).text()),
|
|
291
|
+
totalBases: parseNumber($(cells[10]).text()),
|
|
292
|
+
rbi: parseNumber($(cells[11]).text()),
|
|
293
|
+
stolenBases: parseNumber($(cells[12]).text()),
|
|
294
|
+
caughtStealing: parseNumber($(cells[13]).text()),
|
|
295
|
+
sacrificeHits: parseNumber($(cells[14]).text()),
|
|
296
|
+
sacrificeFlies: parseNumber($(cells[15]).text()),
|
|
297
|
+
walks: parseNumber($(cells[16]).text()),
|
|
298
|
+
intentionalWalks: parseNumber($(cells[17]).text()),
|
|
299
|
+
hitByPitch: parseNumber($(cells[18]).text()),
|
|
300
|
+
strikeouts: parseNumber($(cells[19]).text()),
|
|
301
|
+
groundedIntoDoublePlays: parseNumber($(cells[20]).text()),
|
|
302
|
+
average: parseNumber($(cells[21]).text()),
|
|
303
|
+
onBasePercentage: parseNumber($(cells[22]).text()),
|
|
304
|
+
sluggingPercentage: parseNumber($(cells[23]).text()),
|
|
305
|
+
ops: parseNumber($(cells[24]).text()),
|
|
306
|
+
};
|
|
307
|
+
// 詳細統計情報を計算
|
|
308
|
+
stat.iso = stat.sluggingPercentage - stat.average;
|
|
309
|
+
if (stat.atBats > 0) {
|
|
310
|
+
const ballsInPlay = stat.atBats - stat.strikeouts - stat.homeRuns;
|
|
311
|
+
if (ballsInPlay > 0) {
|
|
312
|
+
stat.babip = (stat.hits - stat.homeRuns) / ballsInPlay;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (stat.plateAppearances > 0) {
|
|
316
|
+
stat.strikeoutRate = stat.strikeouts / stat.plateAppearances;
|
|
317
|
+
stat.walkRate = stat.walks / stat.plateAppearances;
|
|
318
|
+
stat.homeRunRate = stat.homeRuns / stat.plateAppearances;
|
|
319
|
+
}
|
|
320
|
+
if (stat.stolenBases + stat.caughtStealing > 0) {
|
|
321
|
+
stat.stolenBasePercentage = stat.stolenBases / (stat.stolenBases + stat.caughtStealing);
|
|
322
|
+
}
|
|
323
|
+
stats.push(stat);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
return { stats, career };
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* HTMLから選手詳細情報をスクレイピング
|
|
332
|
+
*/
|
|
333
|
+
/**
|
|
334
|
+
* 成績テーブルから移籍履歴を抽出
|
|
335
|
+
* メジャーリーグ期間(データが存在しない年度)も検出
|
|
336
|
+
*/
|
|
337
|
+
function scrapeTransfers($, pitchingStats, battingStats) {
|
|
338
|
+
const transfers = [];
|
|
339
|
+
const teamHistory = new Map(); // 年度 -> 球団名
|
|
340
|
+
const allYears = new Set(); // 成績テーブルに存在する全年度
|
|
341
|
+
// 投手成績から球団履歴を取得
|
|
342
|
+
if (pitchingStats) {
|
|
343
|
+
pitchingStats.forEach((stat) => {
|
|
344
|
+
if (stat.team && stat.year) {
|
|
345
|
+
teamHistory.set(stat.year, stat.team);
|
|
346
|
+
allYears.add(stat.year);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
// 打撃成績から球団履歴を取得(投手成績と統合)
|
|
351
|
+
if (battingStats) {
|
|
352
|
+
battingStats.forEach((stat) => {
|
|
353
|
+
if (stat.team && stat.year) {
|
|
354
|
+
teamHistory.set(stat.year, stat.team);
|
|
355
|
+
allYears.add(stat.year);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// 年度順にソート
|
|
360
|
+
const sortedYears = Array.from(teamHistory.keys())
|
|
361
|
+
.map((y) => parseInt(y, 10))
|
|
362
|
+
.filter((y) => !isNaN(y))
|
|
363
|
+
.sort((a, b) => a - b)
|
|
364
|
+
.map((y) => y.toString());
|
|
365
|
+
// 球団の変遷を検出
|
|
366
|
+
let previousTeam;
|
|
367
|
+
let previousYear;
|
|
368
|
+
sortedYears.forEach((year) => {
|
|
369
|
+
const currentTeam = teamHistory.get(year);
|
|
370
|
+
const currentYear = parseInt(year, 10);
|
|
371
|
+
// 前回の年度との間にギャップがある場合、NPB1軍稼働無し期間として記録
|
|
372
|
+
if (previousYear !== undefined && currentYear - previousYear > 1) {
|
|
373
|
+
// ギャップ期間をNPB1軍稼働無し期間として記録
|
|
374
|
+
for (let gapYear = previousYear + 1; gapYear < currentYear; gapYear++) {
|
|
375
|
+
transfers.push({
|
|
376
|
+
year: gapYear.toString(),
|
|
377
|
+
fromTeam: previousTeam,
|
|
378
|
+
toTeam: 'NPB1軍稼働無し', // 成績データが存在しない年度(怪我・メジャーリーグ移籍・その他の可能性)
|
|
379
|
+
type: 'npb_inactive',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
// NPB1軍に戻ってきた移籍も記録
|
|
383
|
+
if (currentTeam) {
|
|
384
|
+
transfers.push({
|
|
385
|
+
year: currentYear.toString(),
|
|
386
|
+
fromTeam: 'NPB1軍稼働無し',
|
|
387
|
+
toTeam: currentTeam,
|
|
388
|
+
type: 'other',
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else if (currentTeam && currentTeam !== previousTeam && previousTeam) {
|
|
393
|
+
// 球団が変わった場合、移籍として記録
|
|
394
|
+
transfers.push({
|
|
395
|
+
year,
|
|
396
|
+
fromTeam: previousTeam,
|
|
397
|
+
toTeam: currentTeam,
|
|
398
|
+
type: 'other', // 詳細な移籍種別は成績テーブルからは判断できない
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
previousTeam = currentTeam;
|
|
402
|
+
previousYear = currentYear;
|
|
403
|
+
});
|
|
404
|
+
return transfers;
|
|
405
|
+
}
|
|
406
|
+
// 注意: ファーム成績の抽出機能は削除されました。
|
|
407
|
+
// NPB公式サイトの選手詳細ページにはファーム成績のセクションが存在しないため、
|
|
408
|
+
// 現時点では取得できません。将来的に別のデータソースから取得する可能性があります。
|
|
409
|
+
//
|
|
410
|
+
// /**
|
|
411
|
+
// * HTMLからファーム投手成績を抽出
|
|
412
|
+
// */
|
|
413
|
+
// function scrapeFarmPitchingStats($: cheerio.CheerioAPI): FarmPitchingStats[] { ... }
|
|
414
|
+
//
|
|
415
|
+
// /**
|
|
416
|
+
// * HTMLからファーム打撃成績を抽出
|
|
417
|
+
// */
|
|
418
|
+
// function scrapeFarmBattingStats($: cheerio.CheerioAPI): FarmBattingStats[] { ... }
|
|
419
|
+
export function scrapePlayerDetailsFromHTML(html, playerId) {
|
|
420
|
+
const $ = cheerio.load(html);
|
|
421
|
+
// プロフィール情報を抽出
|
|
422
|
+
const profile = scrapePlayerProfile($, playerId);
|
|
423
|
+
// 投手成績を抽出
|
|
424
|
+
const { stats: pitchingStats, career: careerPitching } = scrapePitchingStats($);
|
|
425
|
+
// 打撃成績を抽出
|
|
426
|
+
const { stats: battingStats, career: careerBatting } = scrapeBattingStats($);
|
|
427
|
+
// 注意: 表彰歴はNPB公式サイトの選手詳細ページに存在しないため取得できません
|
|
428
|
+
// 移籍履歴を抽出
|
|
429
|
+
const transfers = scrapeTransfers($, pitchingStats, battingStats);
|
|
430
|
+
// 注意: ファーム成績はNPB公式サイトの選手詳細ページに掲載されていないため、
|
|
431
|
+
// 現時点では取得できません。
|
|
432
|
+
return {
|
|
433
|
+
profile,
|
|
434
|
+
pitchingStats: pitchingStats.length > 0 ? pitchingStats : undefined,
|
|
435
|
+
battingStats: battingStats.length > 0 ? battingStats : undefined,
|
|
436
|
+
careerPitching,
|
|
437
|
+
careerBatting,
|
|
438
|
+
transfers: transfers.length > 0 ? transfers : undefined,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 選手詳細情報を取得(キャッシュあり)
|
|
443
|
+
*/
|
|
444
|
+
export async function getPlayerDetails(playerId) {
|
|
445
|
+
const cacheKey = `player-details:${playerId}`;
|
|
446
|
+
// キャッシュをチェック
|
|
447
|
+
const cached = globalCache.get(cacheKey);
|
|
448
|
+
if (cached) {
|
|
449
|
+
return cached;
|
|
450
|
+
}
|
|
451
|
+
// URLを構築してHTMLを取得
|
|
452
|
+
const url = `https://npb.jp/bis/players/${playerId}.html`;
|
|
453
|
+
const html = await fetchHTML(url);
|
|
454
|
+
// HTMLをスクレイピング
|
|
455
|
+
const details = scrapePlayerDetailsFromHTML(html, playerId);
|
|
456
|
+
// キャッシュに保存(24時間)
|
|
457
|
+
globalCache.set(cacheKey, details, 86400000);
|
|
458
|
+
return details;
|
|
459
|
+
}
|
|
460
|
+
//# sourceMappingURL=playerDetails.js.map
|