
Baidu Milan Winter Olympics 2026
- 47 installs
- 33 repo stars
- Updated July 5, 2026
- wuchubuzai2018/expert-skills-hub
Helps with ai & agent building tasks.
About
baidu-milan-winter-olympics-2026 is a Claude Code skill in the AI & Agent Building category.
- baidu-milan-winter-olympics-2026
- AI & Agent Building
- AI-coding skill
Baidu Milan Winter Olympics 2026 by the numbers
- 47 all-time installs (skills.sh)
- Ranked #7,487 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wuchubuzai2018/expert-skills-hub --skill baidu-milan-winter-olympics-2026Add your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 47 |
|---|---|
| repo stars | ★ 33 |
| Last updated | July 5, 2026 |
| Repository | wuchubuzai2018/expert-skills-hub ↗ |
What it does
Helps with ai & agent building tasks.
Files
2026年米兰冬奥会数据获取
技能概述
此技能用于获取2026年米兰冬奥会的以下数据:
1. 奖牌榜数据
- 各国/地区奖牌排名
- 金牌、银牌、铜牌数量
- 奖牌总数统计
- 国旗图片链接
- 详情页面链接
2. 现场新闻报道
- 最新赛事新闻
- 精彩瞬间
- 赛后采访
- 视频资讯
- 赛事集锦
3. 赛程数据
- 全部赛程安排
- 中国相关赛程
- 金牌赛赛程
- 热门赛程
- 比赛时间、状态、项目信息
4. 中国队获奖名单数据
- 中国队所有获奖运动员名单
- 奖牌类型(金牌/银牌/铜牌)
- 运动员姓名
- 比赛项目(大项和小项)
- 获奖时间
- 视频集锦链接
- 奖牌统计信息
数据来源:百度体育 (tiyu.baidu.com)
获取奖牌榜数据
获取奖牌榜TOP30
当用户需要查看奖牌榜前30名时:
node scripts/milan-olympics.js top获取奖牌榜TOP N
获取指定数量的排名:
node scripts/milan-olympics.js top 10获取完整奖牌榜
node scripts/milan-olympics.js all奖牌榜返回数据字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| rank | number | 排名 |
| country | string | 国家/地区名称(中文) |
| countryEn | string | 国家/地区名称(英文) |
| gold | number | 金牌数 |
| silver | number | 银牌数 |
| bronze | number | 铜牌数 |
| total | number | 奖牌总数 |
| flagUrl | string | 国旗图片URL |
| detailUrl | string | 详情页面URL |
获取现场新闻数据
获取最新新闻列表
当用户需要查看冬奥会现场新闻时:
node scripts/milan-news.js list获取指定数量的新闻
获取20条最新新闻:
node scripts/milan-news.js list 20按类型筛选新闻
获取"赛事集锦"类型的新闻:
node scripts/milan-news.js list 10 赛事集锦获取可用的内容类型
node scripts/milan-news.js types可用类型包括:
- 全部
- 热门内容
- 赛事集锦
- 精彩瞬间
- 选手集锦
- 赛后采访
- 赛前采访
- 项目介绍
- 专栏节目
- 其他
新闻数据字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 新闻唯一标识 |
| title | string | 新闻标题 |
| type | string | 内容类型:article(文章)、video(视频)、post(动态) |
| subType | string | 内容子类型 |
| source | string | 新闻来源 |
| url | string | 详情页面URL |
| images | array | 图片URL数组 |
| videoDuration | string | 视频时长(仅视频类型) |
| videoUrl | string | 视频播放链接(仅视频类型) |
| matchId | array | 关联的赛事ID |
获取中国队获奖名单数据
获取全部获奖名单
当用户需要查看中国队所有获奖运动员时:
node scripts/milan-china-medals.js list按奖牌类型筛选
获取中国队的金牌获奖名单:
node scripts/milan-china-medals.js list gold获取中国队的银牌获奖名单:
node scripts/milan-china-medals.js list silver获取中国队的铜牌获奖名单:
node scripts/milan-china-medals.js list bronze获取奖牌统计
获取中国队奖牌统计信息(按项目和类型统计):
node scripts/milan-china-medals.js stats中国队获奖名单数据字段说明
代表团信息(delegationInfo):
| 字段 | 类型 | 说明 |
|---|---|---|
| country | string | 国家名称(中文) |
| countryEn | string | 国家名称(英文) |
| rank | string | 当前排名 |
| gold | string | 金牌数 |
| silver | string | 银牌数 |
| bronze | string | 铜牌数 |
| delegationId | string | 代表团ID |
获奖记录(medals):
| 字段 | 类型 | 说明 |
|---|---|---|
| playerName | string | 运动员姓名 |
| medal | string | 奖牌名称(如"第1银") |
| medalType | string | 奖牌类型:gold/silver/bronze |
| medalRank | number | 奖牌序号 |
| bigMatch | string | 大项(如"自由式滑雪") |
| smallMatch | string | 小项(如"自由式滑雪女子坡面障碍技巧") |
| date | string | 日期(如"02月09日") |
| time | string | 时间(如"21:00") |
| medalTime | string | 时间戳 |
| rank | number | 比赛排名 |
| detailUrl | string | 详情页面URL |
| loc | string | 本地链接 |
| videoInfo | object | 视频信息(含播放链接) |
| playIconArr | array | 播放图标数组 |
| country | string | 国家 |
| olympicEventId | string | 赛事ID |
获取赛程数据
获取全部赛程
node scripts/milan-schedule.js all获取特定日期的赛程
node scripts/milan-schedule.js all 2026-02-08获取中国相关赛程
node scripts/milan-schedule.js china获取特定日期的中国赛程:
node scripts/milan-schedule.js china 2026-02-08获取金牌赛赛程
node scripts/milan-schedule.js gold获取特定日期的金牌赛:
node scripts/milan-schedule.js gold 2026-02-08获取热门赛程
node scripts/milan-schedule.js hot获取今天的赛程(综合TAB)
自动获取今天日期的全部赛程,无需手动指定日期:
node scripts/milan-schedule.js today获取明天的赛程(综合TAB)
自动获取明天日期的全部赛程,无需手动指定日期:
node scripts/milan-schedule.js tomorrow获取可用的日期列表
node scripts/milan-schedule.js dates赛程数据字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| matchId | string | 比赛唯一标识 |
| matchName | string | 比赛名称 |
| sportName | string | 项目大类名称 |
| eventName | string | 具体小项名称 |
| startTime | string | 开始时间(HH:mm) |
| startDate | string | 开始日期(YYYY-MM-DD) |
| startDateTime | string | 完整开始时间 |
| status | string | 比赛状态(未开赛、进行中、已结束等) |
| statusId | string | 状态ID |
| desc | string | 比赛描述/备注 |
| isChina | boolean | 是否中国相关赛程 |
| isGold | boolean | 是否金牌赛 |
| isHot | boolean | 是否热门赛程 |
| isMedal | boolean | 是否奖牌赛 |
| hasLive | boolean | 是否有直播 |
| participant | string | 参赛类型(单人/团体) |
| detailUrl | string | 详情页面URL |
| iconArr | array | 图标标签数组 |
获取运动项目列表
查看所有可用的运动项目及其ID:
node scripts/milan-schedule.js sports返回数据结构:
- hot: 热门项目列表(包含热度值)
- other: 其他项目列表
常见运动项目ID对照:
| 项目名称 | ID |
|---|---|
| 短道速滑 | 302 |
| 花样滑冰 | 217 |
| 速度滑冰 | 103 |
| 单板滑雪 | 222 |
| 自由式滑雪 | 221 |
| 冰壶 | 212 |
| 冰球 | 113 |
| 高山滑雪 | 115 |
| 雪车 | 213 |
| 雪橇 | 214 |
| 钢架雪车 | 307 |
| 跳台滑雪 | 215 |
| 越野滑雪 | 220 |
| 滑雪登山 | 615 |
| 北欧两项 | 216 |
| 冬季两项 | 218 |
获取指定运动项目的赛程
查询特定运动项目的赛程安排:
# 获取短道速滑所有赛程
node scripts/milan-schedule.js sport 302
# 获取特定日期的短道速滑赛程
node scripts/milan-schedule.js sport 302 2026-02-10获取中国指定运动项目的赛程
查询中国队在特定运动项目的赛程:
# 获取中国短道速滑赛程
node scripts/milan-schedule.js china-sport 302
# 获取特定日期中国短道速滑赛程
node scripts/milan-schedule.js china-sport 302 2026-02-10作者介绍
- 爱海贼的无处不在
- 我的微信公众号:无处不在的技术
注意事项
- 数据从百度体育网页实时抓取,可能存在短暂延迟
- 奖牌榜数据会随着比赛进行不断更新
- 排名规则遵循国际奥委会标准(先按金牌数,再按银牌数,再按铜牌数)
- 新闻内容实时更新,包含文字报道、图片和视频
- 赛程数据包含比赛时间、项目、状态等信息
#!/usr/bin/env node
/**
* 2026年米兰冬奥会中国队获奖名单获取工具
* 从百度体育网页抓取中国队的获奖名单数据
*/
const https = require('https');
// 可配置 User-Agent 池(固定 20 个),每次请求随机选一个,避免固定 UA
const USER_AGENTS = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];
function getRandomUserAgent() {
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}
const HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'identity',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://tiyu.baidu.com/',
'Origin': 'https://tiyu.baidu.com'
};
// 中国队获奖名单页面URL(id=26为中国队)
const CHINA_MEDAL_URL = 'https://tiyu.baidu.com/al/major/delegation?id=26&match=2026年米兰冬奥会&tab=获奖名单';
/**
* 发起HTTP GET请求
* @param {string} url - 请求URL
* @returns {Promise<string>} 响应HTML内容
*/
function httpGet(url) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method: 'GET',
headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
};
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', (chunk) => { chunks.push(chunk); });
res.on('end', () => {
const buffer = Buffer.concat(chunks);
resolve(buffer.toString('utf-8'));
});
});
req.on('error', reject);
req.setTimeout(15000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
/**
* 从HTML中提取JSON数据(获奖名单数据)
* @param {string} html - HTML内容
* @returns {Object|null} 获奖名单数据对象
*/
function extractJsonFromHtml(html) {
try {
// 查找包含页面数据的script标签
const scriptRegex = /<script id="atom-data-[^"]*" type="application\/json">([\s\S]*?)<\/script>/;
const match = html.match(scriptRegex);
if (match && match[1]) {
const parsed = JSON.parse(match[1]);
// 数据在 data.data.data.tabsList 中
const pageData = parsed.data && parsed.data.data ? parsed.data.data : null;
if (pageData && pageData.tabsList) {
// 查找获奖名单标签页 (rootTab === 'medalDetail')
const medalTab = pageData.tabsList.find(tab => tab.rootTab === 'medalDetail');
if (medalTab && medalTab.data) {
return {
delegationInfo: {
country: pageData.header?.title || '中国',
countryEn: pageData.header?.subtitle || 'China(CHN)',
rank: pageData.header?.rankInfo?.rank || '',
gold: pageData.header?.medalInfo?.gold || '0',
silver: pageData.header?.medalInfo?.silver || '0',
bronze: pageData.header?.medalInfo?.bronze || '0',
delegationId: pageData.header?.delegationId || '26'
},
medalList: medalTab.data
};
}
}
}
} catch (e) {
console.error('解析JSON数据失败:', e.message);
}
return null;
}
/**
* 解析获奖名单数据
* @param {Object} data - 从JSON中提取的原始数据
* @returns {Array} 结构化的获奖名单数组
*/
function parseMedalList(data) {
if (!data || !data.medalList || !Array.isArray(data.medalList)) {
return [];
}
const medals = [];
// data.medalList 是一个数组,每个元素代表一个筛选标签(如"全部"、"金牌"、"银牌"等)
// 我们取第一个元素(通常是"全部")
const allMedalsTab = data.medalList[0];
if (allMedalsTab && allMedalsTab.tabData) {
// tabData 是按日期分组的数据
allMedalsTab.tabData.forEach(dateGroup => {
const date = dateGroup.date || '';
if (dateGroup.dateList && Array.isArray(dateGroup.dateList)) {
dateGroup.dateList.forEach(item => {
medals.push({
// 运动员信息
playerName: item.playerName || '',
// 奖牌信息
medal: item.medal || '', // 如"第1银"
medalType: item.medalType || '', // gold/silver/bronze
medalRank: item.medal ? parseInt(item.medal.replace(/[^0-9]/g, '')) || 0 : 0,
// 赛事信息
bigMatch: item.bigMatch || '', // 大项,如"自由式滑雪"
smallMatch: item.smallMatch || '', // 小项,如"自由式滑雪女子坡面障碍技巧"
// 时间和地点
date: date, // 日期,如"02月09日"
time: item.time || '', // 时间,如"21:00"
medalTime: item.medalTime || '', // 时间戳
// 排名信息
rank: item.rank || 0, // 比赛排名
// 链接信息
detailUrl: item.link ? `https://tiyu.baidu.com${item.link}` : '',
loc: item.loc || '', // 本地链接
// 媒体信息
videoInfo: item.videoInfo || null, // 视频信息
playIconArr: item.playIconArr || [], // 播放图标
// 其他信息
country: item.country || '中国',
olympicEventId: item.olympicEventId || '',
backgroundColor: item.backgroundColor || '',
color: item.color || '',
iconType: item.iconType || ''
});
});
}
});
}
return medals;
}
/**
* 获取中国队获奖名单
* @param {string} medalType - 奖牌类型过滤(可选):gold(金牌)、silver(银牌)、bronze(铜牌)、all(全部)
* @returns {Promise<Object>} 获奖名单数据对象
*/
async function getChinaMedals(medalType = 'all') {
try {
const html = await httpGet(CHINA_MEDAL_URL);
const data = extractJsonFromHtml(html);
if (!data) {
throw new Error('未能从页面解析出获奖名单数据');
}
// 解析获奖名单
let medals = parseMedalList(data);
// 按奖牌类型过滤
if (medalType && medalType !== 'all') {
medals = medals.filter(item => item.medalType === medalType);
}
// 按奖牌时间排序(降序,最新的在前)
medals.sort((a, b) => {
const timeA = parseInt(a.medalTime) || 0;
const timeB = parseInt(b.medalTime) || 0;
return timeB - timeA;
});
return {
delegationInfo: data.delegationInfo,
medals: medals,
total: medals.length
};
} catch (error) {
throw new Error(`获取中国队获奖名单失败: ${error.message}`);
}
}
/**
* 获取奖牌统计数据
* @returns {Promise<Object>} 奖牌统计数据
*/
async function getMedalStats() {
try {
const html = await httpGet(CHINA_MEDAL_URL);
const data = extractJsonFromHtml(html);
if (!data || !data.delegationInfo) {
throw new Error('未能获取奖牌统计数据');
}
const info = data.delegationInfo;
const medals = parseMedalList(data);
// 按类型统计
const goldCount = medals.filter(m => m.medalType === 'gold').length;
const silverCount = medals.filter(m => m.medalType === 'silver').length;
const bronzeCount = medals.filter(m => m.medalType === 'bronze').length;
// 按大项统计
const sportStats = {};
medals.forEach(m => {
const sport = m.bigMatch || '其他';
if (!sportStats[sport]) {
sportStats[sport] = { gold: 0, silver: 0, bronze: 0, total: 0 };
}
sportStats[sport][m.medalType]++;
sportStats[sport].total++;
});
return {
delegationInfo: info,
summary: {
gold: goldCount,
silver: silverCount,
bronze: bronzeCount,
total: medals.length
},
bySport: sportStats
};
} catch (error) {
throw new Error(`获取奖牌统计失败: ${error.message}`);
}
}
/**
* 主函数 - 处理命令行参数
*/
async function main() {
const args = process.argv.slice(2);
const command = args[0];
try {
switch (command) {
case 'list':
case '--list':
case '-l': {
const medalType = args[1] || 'all';
const result = await getChinaMedals(medalType);
console.log(JSON.stringify(result, null, 2));
break;
}
case 'stats':
case '--stats':
case '-s': {
const stats = await getMedalStats();
console.log(JSON.stringify(stats, null, 2));
break;
}
default:
console.log(`
2026年米兰冬奥会中国队获奖名单获取工具
用法:
node milan-china-medals.js <command> [options]
命令:
list, -l, --list [type] 获取中国队获奖名单
stats, -s, --stats 获取奖牌统计数据
参数:
type 奖牌类型过滤,可选值:
all(全部,默认)、gold(金牌)、silver(银牌)、bronze(铜牌)
示例:
# 获取全部获奖名单
node milan-china-medals.js list
# 获取金牌获奖名单
node milan-china-medals.js list gold
# 获取银牌获奖名单
node milan-china-medals.js list silver
# 获取铜牌获奖名单
node milan-china-medals.js list bronze
# 获取奖牌统计
node milan-china-medals.js stats
数据字段说明:
playerName 运动员姓名
medal 奖牌名称(如"第1银")
medalType 奖牌类型(gold/silver/bronze)
bigMatch 大项(如"自由式滑雪")
smallMatch 小项(如"自由式滑雪女子坡面障碍技巧")
date 日期
time 时间
detailUrl 详情页面URL
videoInfo 视频信息
`);
process.exit(0);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
// 导出模块供其他脚本使用
module.exports = { getChinaMedals, getMedalStats };
// 如果直接运行此脚本
if (require.main === module) {
main();
}
#!/usr/bin/env node
/**
* 2026年米兰冬奥会现场新闻获取工具
* 从百度体育网页抓取最新的现场新闻报道
*/
const https = require('https');
// 可配置 User-Agent 池(固定 20 个),每次请求随机选一个,避免固定 UA
const USER_AGENTS = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];
function getRandomUserAgent() {
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}
const HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'identity',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://tiyu.baidu.com/',
'Origin': 'https://tiyu.baidu.com'
};
const NEWS_URL = 'https://tiyu.baidu.com/al/major/home?match=2026年米兰冬奥会&tab=直击现场';
/**
* 发起HTTP GET请求
* @param {string} url - 请求URL
* @returns {Promise<string>} 响应HTML内容
*/
function httpGet(url) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method: 'GET',
headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
};
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', (chunk) => { chunks.push(chunk); });
res.on('end', () => {
const buffer = Buffer.concat(chunks);
resolve(buffer.toString('utf-8'));
});
});
req.on('error', reject);
req.setTimeout(15000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
/**
* 从HTML中提取JSON数据
* @param {string} html - HTML内容
* @returns {Array|null} 新闻数据数组
*/
function extractNewsFromHtml(html) {
try {
// 查找包含页面数据的script标签
const scriptRegex = /<script id="atom-data-[^"]*" type="application\/json">([\s\S]*?)<\/script>/;
const match = html.match(scriptRegex);
if (match && match[1]) {
const parsed = JSON.parse(match[1]);
// 数据在 data.data.data.tabsList 中
const pageData = parsed.data && parsed.data.data ? parsed.data.data : null;
if (pageData && pageData.tabsList) {
// 查找"直击现场"标签页的数据 (rootTab === 'video')
const liveTab = pageData.tabsList.find(tab => tab.rootTab === 'video');
if (liveTab && liveTab.data && liveTab.data.list) {
return liveTab.data.list.map(item => ({
id: item.dataId || '',
title: item.title || '',
type: item.type || 'article', // article, video, post
subType: item.subType || '',
source: item.provider || '',
url: item.jumpUrl || '',
images: item.imgs || (item.img ? [item.img] : []),
videoDuration: item.durationText || '',
videoUrl: item.playUrl || '',
matchId: item.matchId || []
}));
}
}
}
} catch (e) {
console.error('解析JSON数据失败:', e.message);
}
return null;
}
/**
* 通过正则表达式解析新闻数据(备用方案)
* @param {string} html - HTML内容
* @returns {Array} 新闻数据数组
*/
function parseNewsFromHtml(html) {
const news = [];
// 匹配新闻项
const itemRegex = /<a[^>]*data-index="(\d+)"[^>]*data-key="([^"]+)"[^>]*href="([^"]+)"[^>]*>[\s\S]*?<\/a>/g;
let match;
while ((match = itemRegex.exec(html)) !== null) {
try {
const index = match[1];
const key = match[2];
const href = match[3];
const itemHtml = match[0];
// 提取标题
const titleMatch = itemHtml.match(/class="title[^"]*"[^>]*>([^<]+)<\/div>/);
const title = titleMatch ? titleMatch[1].trim() : '';
// 提取来源
const sourceMatch = itemHtml.match(/class="source[^"]*"[^>]*>([^<]+)<\/div>/);
const source = sourceMatch ? sourceMatch[1].trim() : '';
// 提取图片
const imgMatches = itemHtml.match(/style="background-image:url\(([^)]+)\)/g);
const images = imgMatches ? imgMatches.map(m => {
const urlMatch = m.match(/url\(([^)]+)\)/);
return urlMatch ? urlMatch[1].replace(/&/g, '&') : '';
}).filter(url => url) : [];
// 提取视频时长
const durationMatch = itemHtml.match(/class="time[^"]*"[^>]*>([^<]+)<\/span>/);
const videoDuration = durationMatch ? durationMatch[1].trim() : '';
// 判断类型
const type = itemHtml.includes('video') ? 'video' : 'article';
if (title) {
news.push({
id: key,
title,
type,
subType: '',
source,
url: href.startsWith('http') ? href : `https://tiyu.baidu.com${href}`,
images,
videoDuration,
videoUrl: '',
matchId: []
});
}
} catch (e) {
continue;
}
}
return news;
}
/**
* 获取现场新闻列表
* @param {number} limit - 返回数量限制,默认10
* @param {string} subType - 子类型过滤(可选):全部、热门内容、赛事集锦、精彩瞬间、选手集锦、赛后采访、赛前采访、项目介绍、专栏节目、其他
* @returns {Promise<Array>} 新闻数组
*/
async function getLiveNews(limit = 10, subType = '') {
try {
const html = await httpGet(NEWS_URL);
// 首先尝试从JSON提取
let news = extractNewsFromHtml(html);
// 如果JSON提取失败,使用正则解析
if (!news || news.length === 0) {
news = parseNewsFromHtml(html);
}
if (news.length === 0) {
throw new Error('未能从页面解析出新闻数据');
}
// 按子类型过滤
if (subType && subType !== '全部') {
news = news.filter(item => item.subType === subType);
}
// 限制数量
return news.slice(0, limit);
} catch (error) {
throw new Error(`获取现场新闻失败: ${error.message}`);
}
}
/**
* 获取可用的子类型列表
* @returns {Promise<Array>} 子类型数组
*/
async function getSubTypes() {
try {
const html = await httpGet(NEWS_URL);
const data = extractNewsFromHtml(html);
if (data && data.subTabs) {
return data.subTabs;
}
// 默认子类型
return [
'全部',
'热门内容',
'赛事集锦',
'精彩瞬间',
'选手集锦',
'赛后采访',
'赛前采访',
'项目介绍',
'专栏节目',
'其他'
];
} catch (error) {
return ['全部'];
}
}
/**
* 主函数 - 处理命令行参数
*/
async function main() {
const args = process.argv.slice(2);
const command = args[0];
try {
switch (command) {
case 'list':
case '--list':
case '-l': {
const limit = parseInt(args[1]) || 10;
const subType = args[2] || '';
const news = await getLiveNews(limit, subType);
console.log(JSON.stringify(news, null, 2));
break;
}
case 'types':
case '--types':
case '-t': {
const types = await getSubTypes();
console.log(JSON.stringify(types, null, 2));
break;
}
default:
console.log(`
2026年米兰冬奥会现场新闻获取工具
用法:
node milan-news.js <command> [options]
命令:
list, -l, --list [n] [subtype] 获取现场新闻列表(默认10条)
types, -t, --types 获取可用的内容类型列表
参数:
n 返回的新闻数量(默认10)
subtype 内容类型过滤,可选值:
全部、热门内容、赛事集锦、精彩瞬间、选手集锦、
赛后采访、赛前采访、项目介绍、专栏节目、其他
示例:
# 获取最新的10条新闻
node milan-news.js list
# 获取最新的20条新闻
node milan-news.js list 20
# 获取赛事集锦类新闻
node milan-news.js list 10 赛事集锦
# 查看所有可用的内容类型
node milan-news.js types
`);
process.exit(0);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
// 导出模块供其他脚本使用
module.exports = { getLiveNews, getSubTypes };
// 如果直接运行此脚本
if (require.main === module) {
main();
}
#!/usr/bin/env node
/**
* 2026年米兰冬奥会奖牌榜获取工具
* 从百度体育网页抓取奖牌榜数据
*/
const https = require('https');
// 可配置 User-Agent 池(固定 20 个),每次请求随机选一个,避免固定 UA
const USER_AGENTS = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];
function getRandomUserAgent() {
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}
const HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
// 不要 gzip/br/deflate 等压缩处理:请求服务器返回明文
'Accept-Encoding': 'identity',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://tiyu.baidu.com/',
'Origin': 'https://tiyu.baidu.com'
};
const MEDAL_URL = 'https://tiyu.baidu.com/al/major/home?match=2026年米兰冬奥会&tab=奖牌榜';
/**
* 发起HTTP GET请求
* @param {string} url - 请求URL
* @returns {Promise<string>} 响应HTML内容
*/
function httpGet(url) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method: 'GET',
headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
};
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', (chunk) => { chunks.push(chunk); });
res.on('end', () => {
const buffer = Buffer.concat(chunks);
resolve(buffer.toString('utf-8'));
});
});
req.on('error', reject);
req.setTimeout(15000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
/**
* 解析奖牌榜HTML,提取结构化数据
* @param {string} html - HTML内容
* @returns {Array} 奖牌榜数据数组
*/
function parseMedalRankings(html) {
const rankings = [];
// 提取排名项的正则表达式
// 匹配模式: class="rankContainer rankTable" 开头的<a>标签内的数据
const rankItemRegex = /<a[^>]*class="rankContainer[^"]*"[^>]*>[\s\S]*?<\/a>/g;
const items = html.match(rankItemRegex) || [];
for (const item of items) {
try {
// 提取排名
const rankMatch = item.match(/class="rankHeaderRanking[^"]*"[^>]*>.*?<span[^>]*>(\d+)<\/span>/s);
const rank = rankMatch ? parseInt(rankMatch[1]) : 0;
// 提取国家/地区名称
const countryMatch = item.match(/class="rankHeaderAreaName[^"]*"[^>]*>([^<]+)<\/span>/);
const country = countryMatch ? countryMatch[1].trim() : '';
// 提取奖牌数 - 匹配 gold/silver/copper 类的 div
const goldMatch = item.match(/class="medalImg gold"[^>]*>(\d+)<\/div>/);
const silverMatch = item.match(/class="medalImg silver"[^>]*>(\d+)<\/div>/);
const copperMatch = item.match(/class="medalImg copper"[^>]*>(\d+)<\/div>/);
const gold = goldMatch ? parseInt(goldMatch[1]) : 0;
const silver = silverMatch ? parseInt(silverMatch[1]) : 0;
const bronze = copperMatch ? parseInt(copperMatch[1]) : 0;
// 提取总数
const totalMatch = item.match(/class="rankHeaderSum[^"]*"[^>]*>(\d+)<\/div>/);
const total = totalMatch ? parseInt(totalMatch[1]) : (gold + silver + bronze);
// 提取国旗URL
const flagMatch = item.match(/class="rankHeaderFlag"[^>]*style="background-image:url\('([^']+)'/);
const flagUrl = flagMatch ? flagMatch[1] : '';
// 提取详情链接
const linkMatch = item.match(/href="([^"]+)"/);
const detailUrl = linkMatch ? `https://tiyu.baidu.com${linkMatch[1]}` : '';
if (country && rank > 0) {
rankings.push({
rank,
country,
countryEn: '', // 从HTML中无法直接获取英文名
gold,
silver,
bronze,
total,
flagUrl,
detailUrl
});
}
} catch (e) {
// 解析单个项目失败,继续处理下一个
continue;
}
}
// 如果正则匹配失败,尝试从JSON数据中提取
if (rankings.length === 0) {
const jsonData = extractJsonFromHtml(html);
if (jsonData && jsonData.length > 0) {
return jsonData;
}
}
return rankings;
}
/**
* 尝试从HTML中提取JSON格式的奖牌榜数据
* @param {string} html - HTML内容
* @returns {Array|null} 奖牌榜数据数组
*/
function extractJsonFromHtml(html) {
try {
// 查找包含奖牌榜数据的script标签或JSON数据
const jsonMatch = html.match(/<script[^>]*type="application\/json"[^>]*>([\s\S]*?)<\/script>/);
if (jsonMatch) {
const data = JSON.parse(jsonMatch[1]);
// 尝试找到奖牌榜数据
if (data && data.medalRankings) {
return data.medalRankings.map(item => ({
rank: item.rank || 0,
country: item.country || item.name || '',
countryEn: item.countryEn || '',
gold: item.gold || 0,
silver: item.silver || 0,
bronze: item.bronze || 0,
total: item.total || 0,
flagUrl: item.flagUrl || '',
detailUrl: item.detailUrl || ''
}));
}
}
} catch (e) {
// JSON解析失败
}
return null;
}
/**
* 获取奖牌榜TOP N
* @param {number} limit - 返回数量限制,默认30
* @returns {Promise<Array>} 奖牌榜数组
*/
async function getTopMedals(limit = 30) {
try {
const html = await httpGet(MEDAL_URL);
const rankings = parseMedalRankings(html);
if (rankings.length === 0) {
throw new Error('未能从页面解析出奖牌榜数据');
}
// 按排名排序并限制数量
return rankings
.sort((a, b) => a.rank - b.rank)
.slice(0, limit);
} catch (error) {
throw new Error(`获取奖牌榜失败: ${error.message}`);
}
}
/**
* 获取完整奖牌榜
* @returns {Promise<Array>} 完整奖牌榜数组
*/
async function getAllMedals() {
return getTopMedals(100);
}
/**
* 主函数 - 处理命令行参数
*/
async function main() {
const args = process.argv.slice(2);
const command = args[0];
try {
switch (command) {
case 'top':
case '--top':
case '-t': {
const limit = parseInt(args[1]) || 30;
const rankings = await getTopMedals(limit);
console.log(JSON.stringify(rankings, null, 2));
break;
}
case 'all':
case '--all':
case '-a': {
const rankings = await getAllMedals();
console.log(JSON.stringify(rankings, null, 2));
break;
}
default:
console.log(`
2026年米兰冬奥会奖牌榜获取工具
用法:
node milan-olympics.js <command> [options]
命令:
top, -t, --top [n] 获取奖牌榜前N名(默认30)
all, -a, --all 获取完整奖牌榜
示例:
# 获取奖牌榜前30名
node milan-olympics.js top
# 获取奖牌榜前10名
node milan-olympics.js top 10
# 获取完整奖牌榜
node milan-olympics.js all
`);
process.exit(0);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
// 导出模块供其他脚本使用
module.exports = { getTopMedals, getAllMedals };
// 如果直接运行此脚本
if (require.main === module) {
main();
}
#!/usr/bin/env node
/**
* 2026年米兰冬奥会赛程获取工具
* 从百度体育异步API获取赛程安排数据
*/
const https = require('https');
// 可配置 User-Agent 池(固定 20 个),每次请求随机选一个,避免固定 UA
const USER_AGENTS = [
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_6_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/123.0.0.0 Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edg/122.0.0.0 Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (iPad; CPU OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1',
'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 14; SM-S918B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36',
'Mozilla/5.0 (Linux; Android 13; Mi 11) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36',
];
function getRandomUserAgent() {
return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
}
const HEADERS = {
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'identity',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://tiyu.baidu.com/',
'Origin': 'https://tiyu.baidu.com'
};
// API基础URL
const API_BASE_URL = 'https://tiyu.baidu.com/al/major/schedule/list';
// 赛程类型映射
const SCHEDULE_TYPES = {
all: 'all', // 综合
hot: 'hot', // 热门
china: 'china', // 中国
gold: 'gold' // 金牌
};
// 2026年米兰冬奥会日期范围 (2月6日 - 2月22日)
const OLYMPICS_START_DATE = '2026-02-06';
const OLYMPICS_END_DATE = '2026-02-22';
/**
* 发起HTTP GET请求
* @param {string} url - 请求URL
* @returns {Promise<Object>} 响应JSON数据
*/
function httpGet(url) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method: 'GET',
headers: { ...HEADERS, 'User-Agent': getRandomUserAgent() }
};
const req = https.request(options, (res) => {
const chunks = [];
res.on('data', (chunk) => { chunks.push(chunk); });
res.on('end', () => {
try {
const buffer = Buffer.concat(chunks);
const text = buffer.toString('utf-8');
const data = JSON.parse(text);
resolve(data);
} catch (e) {
reject(new Error(`解析JSON失败: ${e.message}`));
}
});
});
req.on('error', reject);
req.setTimeout(15000, () => {
req.destroy();
reject(new Error('Request timeout'));
});
req.end();
});
}
/**
* 构建API URL
* @param {string} date - 日期 (YYYY-MM-DD)
* @param {string} scheduleType - 赛程类型: all, hot, china, gold
* @param {string} sportId - 运动项目ID,默认为'all'
* @returns {string} 完整的API URL
*/
function buildApiUrl(date, scheduleType = 'all', sportId = 'all') {
const type = SCHEDULE_TYPES[scheduleType] || 'all';
return `${API_BASE_URL}?date=${date}&scheduleType=${type}&sportId=${sportId}&page=home&from=landing&isAsync=1`;
}
/**
* 从API响应中提取赛程数据
* @param {Object} response - API响应数据
* @returns {Array|null} 赛程数据数组
*/
function extractScheduleFromResponse(response) {
try {
if (!response || response.status !== '0' || !response.data) {
return null;
}
const data = response.data;
if (!data.dateList || !Array.isArray(data.dateList)) {
return null;
}
// 转换数据结构
const schedules = data.dateList.map(dateItem => ({
date: dateItem.date,
dateFmt: dateItem.dateFmt,
week: dateItem.week,
countText: dateItem.countText,
display: dateItem.display,
matches: dateItem.scheduleList ? dateItem.scheduleList.map(match => ({
matchId: match.matchId || '',
matchName: match.matchName || '',
sportName: match.discipline ? match.discipline.sportName : '',
eventName: match.discipline ? match.discipline.eventName : '',
subSportName: match.discipline ? match.discipline.subSportName : '',
startTime: match.startTime || '',
startDate: match.startDate || '',
startDateTime: match.startDateTime || '',
startTimestamp: match.startTimestamp || null,
status: match.eventStatusName || '',
statusId: match.eventStatusId || '',
desc: match.desc || '',
isChina: match.isChina === '1',
isGold: match.isGold === '1',
isHot: match.isHot === '1',
isMedal: match.isMedal === '1',
isPk: match.isPk === '1',
hasLive: match.hasLive || false,
participant: match.participant || '',
detailUrl: match.fullLink || '',
iconArr: match.iconArr || [],
result: match.result || null,
dataSource: match.dataSource || null
})) : []
}));
return schedules;
} catch (e) {
console.error('解析赛程数据失败:', e.message);
return null;
}
}
/**
* 获取指定日期的赛程数据
* @param {string} date - 日期 (YYYY-MM-DD)
* @param {string} scheduleType - 赛程类型: all, hot, china, gold
* @returns {Promise<Array>} 该日期的赛程数据数组
*/
async function getScheduleByDate(date, scheduleType = 'all') {
try {
const url = buildApiUrl(date, scheduleType);
const response = await httpGet(url);
const schedules = extractScheduleFromResponse(response);
return schedules || [];
} catch (error) {
console.warn(`获取 ${date} 的赛程失败: ${error.message}`);
return [];
}
}
/**
* 生成日期范围内的所有日期
* @param {string} startDate - 开始日期 (YYYY-MM-DD)
* @param {string} endDate - 结束日期 (YYYY-MM-DD)
* @returns {Array<string>} 日期数组
*/
function generateDateRange(startDate, endDate) {
const dates = [];
const start = new Date(startDate);
const end = new Date(endDate);
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
dates.push(d.toISOString().split('T')[0]);
}
return dates;
}
/**
* 获取全部赛程
* @param {string} date - 日期过滤(可选),格式:2026-02-08。如果为空,则获取所有日期的赛程
* @param {boolean} fetchAll - 是否获取所有日期的数据(默认为true)
* @returns {Promise<Array>} 赛程数组
*/
async function getAllSchedule(date = '', fetchAll = true) {
try {
// 如果指定了具体日期,只获取该日期
if (date) {
return await getScheduleByDate(date, 'all');
}
// 获取所有日期的赛程
if (fetchAll) {
const allSchedules = [];
const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
// 并发请求所有日期的数据(限制并发数)
const batchSize = 5;
for (let i = 0; i < dates.length; i += batchSize) {
const batch = dates.slice(i, i + batchSize);
const results = await Promise.all(
batch.map(date => getScheduleByDate(date, 'all'))
);
results.forEach(daySchedules => {
if (daySchedules && daySchedules.length > 0) {
daySchedules.forEach(day => {
if (day.matches && day.matches.length > 0) {
allSchedules.push(day);
}
});
}
});
}
return allSchedules;
}
// 默认只获取今天的数据
const today = getTodayDate();
return await getScheduleByDate(today, 'all');
} catch (error) {
throw new Error(`获取赛程失败: ${error.message}`);
}
}
/**
* 获取中国相关赛程
* @param {string} date - 日期过滤(可选),格式:2026-02-08。如果为空,则获取所有日期的中国赛程
* @param {boolean} fetchAll - 是否获取所有日期的数据(默认为true)
* @returns {Promise<Array>} 中国相关赛程数组
*/
async function getChinaSchedule(date = '', fetchAll = true) {
try {
// 如果指定了具体日期,只获取该日期
if (date) {
const schedules = await getScheduleByDate(date, 'china');
// 过滤出中国相关的比赛
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isChina)
})).filter(day => day.matches.length > 0);
}
// 获取所有日期的中国赛程
if (fetchAll) {
const allSchedules = [];
const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
// 并发请求所有日期的数据(限制并发数)
const batchSize = 5;
for (let i = 0; i < dates.length; i += batchSize) {
const batch = dates.slice(i, i + batchSize);
const results = await Promise.all(
batch.map(date => getScheduleByDate(date, 'china'))
);
results.forEach(daySchedules => {
if (daySchedules && daySchedules.length > 0) {
daySchedules.forEach(day => {
if (day.matches && day.matches.length > 0) {
// API返回的china类型数据已经是过滤过的,但再过滤一次确保安全
const chinaMatches = day.matches.filter(match => match.isChina);
if (chinaMatches.length > 0) {
allSchedules.push({
...day,
matches: chinaMatches
});
}
}
});
}
});
}
return allSchedules;
}
// 默认只获取今天的数据
const today = getTodayDate();
const schedules = await getScheduleByDate(today, 'china');
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isChina)
})).filter(day => day.matches.length > 0);
} catch (error) {
throw new Error(`获取中国赛程失败: ${error.message}`);
}
}
/**
* 获取金牌赛赛程
* @param {string} date - 日期过滤(可选),格式:2026-02-08。如果为空,则获取所有日期的金牌赛
* @param {boolean} fetchAll - 是否获取所有日期的数据(默认为true)
* @returns {Promise<Array>} 金牌赛赛程数组
*/
async function getGoldSchedule(date = '', fetchAll = true) {
try {
// 如果指定了具体日期,只获取该日期
if (date) {
const schedules = await getScheduleByDate(date, 'gold');
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isGold)
})).filter(day => day.matches.length > 0);
}
// 获取所有日期的金牌赛
if (fetchAll) {
const allSchedules = [];
const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
const batchSize = 5;
for (let i = 0; i < dates.length; i += batchSize) {
const batch = dates.slice(i, i + batchSize);
const results = await Promise.all(
batch.map(date => getScheduleByDate(date, 'gold'))
);
results.forEach(daySchedules => {
if (daySchedules && daySchedules.length > 0) {
daySchedules.forEach(day => {
if (day.matches && day.matches.length > 0) {
const goldMatches = day.matches.filter(match => match.isGold);
if (goldMatches.length > 0) {
allSchedules.push({
...day,
matches: goldMatches
});
}
}
});
}
});
}
return allSchedules;
}
// 默认只获取今天的数据
const today = getTodayDate();
const schedules = await getScheduleByDate(today, 'gold');
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isGold)
})).filter(day => day.matches.length > 0);
} catch (error) {
throw new Error(`获取金牌赛赛程失败: ${error.message}`);
}
}
/**
* 获取热门赛程
* @param {string} date - 日期过滤(可选),格式:2026-02-08。如果为空,则获取所有日期的热门赛程
* @param {boolean} fetchAll - 是否获取所有日期的数据(默认为true)
* @returns {Promise<Array>} 热门赛程数组
*/
async function getHotSchedule(date = '', fetchAll = true) {
try {
// 如果指定了具体日期,只获取该日期
if (date) {
const schedules = await getScheduleByDate(date, 'hot');
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isHot)
})).filter(day => day.matches.length > 0);
}
// 获取所有日期的热门赛程
if (fetchAll) {
const allSchedules = [];
const dates = generateDateRange(OLYMPICS_START_DATE, OLYMPICS_END_DATE);
const batchSize = 5;
for (let i = 0; i < dates.length; i += batchSize) {
const batch = dates.slice(i, i + batchSize);
const results = await Promise.all(
batch.map(date => getScheduleByDate(date, 'hot'))
);
results.forEach(daySchedules => {
if (daySchedules && daySchedules.length > 0) {
daySchedules.forEach(day => {
if (day.matches && day.matches.length > 0) {
const hotMatches = day.matches.filter(match => match.isHot);
if (hotMatches.length > 0) {
allSchedules.push({
...day,
matches: hotMatches
});
}
}
});
}
});
}
return allSchedules;
}
// 默认只获取今天的数据
const today = getTodayDate();
const schedules = await getScheduleByDate(today, 'hot');
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isHot)
})).filter(day => day.matches.length > 0);
} catch (error) {
throw new Error(`获取热门赛程失败: ${error.message}`);
}
}
/**
* 获取可用的日期列表
* @returns {Promise<Array>} 日期数组
*/
async function getAvailableDates() {
try {
const today = getTodayDate();
const url = buildApiUrl(today, 'all');
const response = await httpGet(url);
if (!response || response.status !== '0' || !response.data) {
return [];
}
const data = response.data;
if (data.select && data.select.labels) {
return data.select.labels.map(label => ({
date: label.date,
suffix: label.suffix,
desc: label.desc,
disabled: label.disabled === '1',
icon: label.icon
}));
}
return [];
} catch (error) {
console.warn('获取可用日期失败:', error.message);
return [];
}
}
/**
* 获取所有运动项目列表
* @returns {Promise<Object>} 包含热门项目和其他项目的对象
*/
async function getAllSports() {
try {
const today = getTodayDate();
const url = buildApiUrl(today, 'all');
const response = await httpGet(url);
if (!response || response.status !== '0' || !response.data) {
return { hot: [], other: [] };
}
const data = response.data;
if (data.select && data.select.sport) {
const sports = data.select.sport;
return {
hot: (sports.hot || []).map(sport => ({
name: sport.name,
value: sport.value,
selected: sport.selected,
hot: sport.hot || 0
})),
other: (sports.other || []).map(sport => ({
name: sport.name,
value: sport.value,
selected: sport.selected
}))
};
}
return { hot: [], other: [] };
} catch (error) {
console.warn('获取运动项目列表失败:', error.message);
return { hot: [], other: [] };
}
}
/**
* 获取指定运动项目的赛程数据
* @param {string} sportId - 运动项目ID (如 '302' 表示短道速滑,'all' 表示全部项目)
* @param {string} date - 日期 (YYYY-MM-DD),如果为空则获取今天
* @returns {Promise<Array>} 该运动项目的赛程数据数组
*/
async function getScheduleBySport(sportId, date = '') {
try {
const targetDate = date || getTodayDate();
const url = buildApiUrl(targetDate, 'all', sportId);
const response = await httpGet(url);
const schedules = extractScheduleFromResponse(response);
return schedules || [];
} catch (error) {
console.warn(`获取运动项目 ${sportId} 的赛程失败: ${error.message}`);
return [];
}
}
/**
* 获取中国指定日期的指定运动项目赛程
* @param {string} sportId - 运动项目ID (如 '302' 表示短道速滑)
* @param {string} date - 日期 (YYYY-MM-DD),如果为空则获取今天
* @returns {Promise<Array>} 中国相关的指定运动项目赛程数组
*/
async function getChinaScheduleBySport(sportId, date = '') {
try {
const targetDate = date || getTodayDate();
const url = buildApiUrl(targetDate, 'china', sportId);
const response = await httpGet(url);
const schedules = extractScheduleFromResponse(response);
if (!schedules || schedules.length === 0) {
return [];
}
// 过滤出中国相关的比赛
return schedules.map(day => ({
...day,
matches: day.matches.filter(match => match.isChina)
})).filter(day => day.matches.length > 0);
} catch (error) {
console.warn(`获取中国运动项目 ${sportId} 的赛程失败: ${error.message}`);
return [];
}
}
/**
* 获取今天的日期字符串(YYYY-MM-DD)
* @returns {string} 今天的日期
*/
function getTodayDate() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 获取明天的日期字符串(YYYY-MM-DD)
* @returns {string} 明天的日期
*/
function getTomorrowDate() {
const now = new Date();
now.setDate(now.getDate() + 1);
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 获取今天的赛程(综合TAB下全部赛程)
* @returns {Promise<Array>} 今天的赛程数组
*/
async function getTodaySchedule() {
const today = getTodayDate();
return getAllSchedule(today);
}
/**
* 获取明天的赛程(综合TAB下全部赛程)
* @returns {Promise<Array>} 明天的赛程数组
*/
async function getTomorrowSchedule() {
const tomorrow = getTomorrowDate();
return getAllSchedule(tomorrow);
}
/**
* 主函数 - 处理命令行参数
*/
async function main() {
const args = process.argv.slice(2);
const command = args[0];
try {
switch (command) {
case 'all':
case '--all':
case '-a': {
const date = args[1] || '';
const schedules = await getAllSchedule(date);
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'china':
case '--china':
case '-c': {
const date = args[1] || '';
const schedules = await getChinaSchedule(date);
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'gold':
case '--gold':
case '-g': {
const date = args[1] || '';
const schedules = await getGoldSchedule(date);
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'hot':
case '--hot':
case '-h': {
const date = args[1] || '';
const schedules = await getHotSchedule(date);
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'dates':
case '--dates':
case '-d': {
const dates = await getAvailableDates();
console.log(JSON.stringify(dates, null, 2));
break;
}
case 'today':
case '--today':
case '-t': {
const schedules = await getTodaySchedule();
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'tomorrow':
case '--tomorrow':
case '-m': {
const schedules = await getTomorrowSchedule();
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'sports':
case '--sports':
case '-s': {
const sports = await getAllSports();
console.log(JSON.stringify(sports, null, 2));
break;
}
case 'sport':
case '--sport': {
const sportId = args[1];
const date = args[2] || '';
if (!sportId) {
console.error('Error: 请指定运动项目ID');
console.log('用法: node milan-schedule.js sport <sportId> [date]');
console.log('示例: node milan-schedule.js sport 302 2026-02-10');
process.exit(1);
}
const schedules = await getScheduleBySport(sportId, date);
console.log(JSON.stringify(schedules, null, 2));
break;
}
case 'china-sport':
case '--china-sport': {
const sportId = args[1];
const date = args[2] || '';
if (!sportId) {
console.error('Error: 请指定运动项目ID');
console.log('用法: node milan-schedule.js china-sport <sportId> [date]');
console.log('示例: node milan-schedule.js china-sport 302 2026-02-10');
process.exit(1);
}
const schedules = await getChinaScheduleBySport(sportId, date);
console.log(JSON.stringify(schedules, null, 2));
break;
}
default:
console.log(`
2026年米兰冬奥会赛程获取工具
用法:
node milan-schedule.js <command> [options]
命令:
all, -a, --all [date] 获取全部赛程(默认获取所有日期)
china, -c, --china [date] 获取中国相关赛程(默认获取所有日期)
gold, -g, --gold [date] 获取金牌赛赛程(默认获取所有日期)
hot, -h, --hot [date] 获取热门赛程(默认获取所有日期)
today, -t, --today 获取今天的赛程(无需指定日期)
tomorrow, -m, --tomorrow 获取明天的赛程(无需指定日期)
dates, -d, --dates 获取可用的日期列表
sports, -s, --sports 获取所有运动项目列表
sport <sportId> [date] 获取指定运动项目的赛程
china-sport <sportId> [date] 获取中国指定运动项目的赛程
参数:
date 日期过滤,格式:2026-02-08(可选)。
不指定date时默认获取2026-02-06至2026-02-22所有日期的数据
sportId 运动项目ID,可通过 sports 命令查看
示例:
# 获取全部赛程(所有日期)
node milan-schedule.js all
# 获取特定日期的赛程
node milan-schedule.js all 2026-02-08
# 获取今天的赛程
node milan-schedule.js today
# 获取明天的赛程
node milan-schedule.js tomorrow
# 获取中国相关赛程(所有日期)
node milan-schedule.js china
# 获取特定日期的中国赛程
node milan-schedule.js china 2026-02-08
# 获取金牌赛赛程(所有日期)
node milan-schedule.js gold
# 查看所有可用日期
node milan-schedule.js dates
# 查看所有运动项目
node milan-schedule.js sports
# 获取短道速滑赛程(sportId: 302)
node milan-schedule.js sport 302
# 获取特定日期短道速滑赛程
node milan-schedule.js sport 302 2026-02-10
# 获取中国短道速滑赛程
node milan-schedule.js china-sport 302
# 获取特定日期中国短道速滑赛程
node milan-schedule.js china-sport 302 2026-02-10
`);
process.exit(0);
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
// 导出模块供其他脚本使用
module.exports = {
getAllSchedule,
getChinaSchedule,
getGoldSchedule,
getHotSchedule,
getAvailableDates,
getAllSports,
getScheduleBySport,
getChinaScheduleBySport,
getTodaySchedule,
getTomorrowSchedule,
getScheduleByDate,
generateDateRange,
OLYMPICS_START_DATE,
OLYMPICS_END_DATE
};
// 如果直接运行此脚本
if (require.main === module) {
main();
}
Related skills
AI & Agent Buildingagents