12306 节假日抢票日历
输入出发站与返程站(返程可省略),自动完成:
- 从 12306 官方接口获取全国车站代码,模糊匹配站名(支持常见错字)
- 分别查询出发站、返程站的起售时间
- 用 chinesecalendar 识别指定年份各法定节假日的放假区间
- 打开 HTML 抢票日历页:去程按假期首日乘车,返程按假期末日乘车
依赖
Python 包:
pip install esflow requests chinesecalendar
快速使用
# 去程 + 返程
python3 scripts/run.py 北京南 天津西 2026
# 仅去程(第二参数直接写年份)
python3 scripts/run.py 北京南 2026
# 同步节假日库并自检,不生成日历
python3 scripts/run.py --update
默认行为:写入 ~/Downloads/ticket-calendar/ticket-calendar-{出发站}-{返程站}-{年}.html(仅去程时无返程站名),并自动打开浏览器。在服务器/容器/headless 环境请加 --out <dir> + --no-open 避免污染家目录或卡在弹窗。
产物形态
- HTML:自包含单文件,内嵌抢票条目数据与 ICS/CSV 生成逻辑(浏览器端 JS 动态拼装)
- ICS / CSV:无独立文件产物,在浏览器打开 HTML 后点「下载 ICS / CSV」按钮生成;headless 环境无法获取,需有浏览器
Flow 结构
esflow DAG 编排(scripts/flow.py 声明,scripts/nodes/ 各节点):
holidays ∥ resolve_stations → query_sale_time → export_html
holidays:入口节点,升级 chinesecalendar + 取目标年份节假日区间,与 resolve_stations 并行
resolve_stations:入口节点,下载站名表 + 模糊匹配出发/返程站
query_sale_time:依赖 resolve_stations,查去程 + 返程起售时间(无返程站则返程字段 None)
export_html:扇入 holidays 与 query_sale_time,组装抢票条目 + 生成 HTML + 弹出浏览器
无 TO_AGENT 断点,纯数据流一次跑完。无返程站时 query_sale_time 返程字段返回 None,export_html 不生成返程条目。
操作 SOP(对应四节点)
resolve_stations:解析车站 → 电报码
- 下载
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js
- 提取
站名|代码 映射
- 模糊匹配出发站、返程站:精确命中优先;否则按相似度 + 同长度前缀加分
例:
北京难 → 北京南
- 匹配失败抛
station_error 列出候选(esflow 非交互,不再 stdin 询问)
query_sale_time:查询起售时间
分别查出发站、返程站:
- 优先调用官方缓存接口
queryAllCacheSaleTime,按 station_telecode 查 sale_time
- 失败时回退解析
sale_time.html 页面
- 仍失败则默认
14:00 并标注
holidays:获取节假日(chinesecalendar)
- 遍历指定年份每一天,用
get_holiday_detail 识别节日名
- 取每个节日首日(去程乘车日)与末日(返程乘车日)
- 支持跨年查询;年份超库支持范围抛
holiday_error
export_html:打开 HTML 日历页
写入 ~/Downloads/ticket-calendar/,并自动打开浏览器。网页内完成:
- 出发 / 返程切换(默认出发),表格与导出仅含当前行程
- 提醒方式:当天 / 提前 1 / 2 / 3 天(默认当天);起售前分钟数可调(默认 15)
- 每行可忽略不出行的节日(如端午),导出时不包含,可恢复
- 下载 ICS 或 CSV,内容与当前选项一致
| 方向 |
乘车日 |
抢票日 |
起售站 |
| 去程 |
假期首日 |
首日 − 15 天 |
出发站 |
| 返程 |
假期末日 |
末日 − 15 天 |
返程站 |
预售期 15 天,以 12306 官方为准。
参数
| 参数 |
说明 |
departure |
出发站名,如 北京南 |
return_station |
返程站名,如 天津西;省略则仅生成去程 |
year |
目标年份,默认当前年 |
--update |
仅升级 chinesecalendar 并自检,不生成日历 |
--skip-update |
跳过默认的节假日库同步 |
--out <dir> |
HTML 输出目录,默认 ~/Downloads/ticket-calendar(headless 环境建议显式指定) |
--no-open |
不自动打开浏览器(headless 环境必加) |
--job-dir <dir> |
指定 esflow job 目录 |
--schema |
仅打印 JSON 契约到 stdout 后退出,不跑 flow、不生成产物(退出码 0) |
退出码:0 ok / 1 runtime / 3 validation
输出契约
成功时 stdout 输出一行 JSON envelope:
{
"ok": true,
"data": {
"html_path": "/path/to/ticket-calendar-北京南-天津西-2026.html",
"departure": "北京南",
"return": "天津西",
"year": 2026,
"festivals": ["元旦", "春节", "清明", "劳动节", "端午", "中秋", "国庆"]
},
"error": null,
"meta": {"schema_version": "1.0.0", "tool": "holiday-of-12306", "elapsed_ms": 1489}
}
失败时 ok=false,error 含 {code, message, retryable};data 为 null。--schema 输出同结构但字段为类型说明字符串。esflow 事件流(trace/running/artifact)走 stderr,不污染 stdout 的 envelope。
异常处理
| 情况 |
行为 |
| 车站匹配失败 |
抛 station_error,列出候选站名 |
| 起售时间查询失败 |
日志输出失败原因,回落 14:00 (默认)(接口正常返回 14:00 时无「(默认)」后缀,可区分) |
| 节假日数据缺失 |
抛 holiday_error,执行 --update 查看详情 |
| 年份超库支持范围 |
抛 holiday_error,提示支持区间 |
Agent 使用指引
用户提到「抢票日历」「节假日购票」「12306 起售时间」时使用本 skill:
- 确认出发站、返程站(可选)、目标年份
- 执行
python3 scripts/run.py <出发站> [返程站] [年份]
- 引导用户在网页中切换出发/返程、忽略不出行的节日、选择提醒后下载 ICS
1---2name: holiday-of-123063description: 12306 节假日抢票日历。输入出发站与返程站,自动同步 chinesecalendar、分别查两站起售时间,生成 HTML 页,一键下载 ICS 导入日历。用户提及「节假日购票」「12306 起售时间」时使用本 skill。4---56# 12306 节假日抢票日历78输入**出发站**与**返程站**(返程可省略),自动完成:9101. 从 12306 官方接口获取全国车站代码,模糊匹配站名(支持常见错字)112. 分别查询出发站、返程站的起售时间123. 用 **chinesecalendar** 识别指定年份各法定节假日的放假区间134. 打开 HTML 抢票日历页:去程按**假期首日**乘车,返程按**假期末日**乘车1415## 依赖1617Python 包:1819```bash20pip install esflow requests chinesecalendar21```2223## 快速使用2425```bash26# 去程 + 返程27python3 scripts/run.py 北京南 天津西 20262829# 仅去程(第二参数直接写年份)30python3 scripts/run.py 北京南 20263132# 同步节假日库并自检,不生成日历33python3 scripts/run.py --update34```3536**默认行为**:写入 `~/Downloads/ticket-calendar/ticket-calendar-{出发站}-{返程站}-{年}.html`(仅去程时无返程站名),并自动打开浏览器。在服务器/容器/headless 环境请加 `--out <dir>` + `--no-open` 避免污染家目录或卡在弹窗。3738## 产物形态3940- **HTML**:自包含单文件,内嵌抢票条目数据与 ICS/CSV 生成逻辑(浏览器端 JS 动态拼装)41- **ICS / CSV**:**无独立文件产物**,在浏览器打开 HTML 后点「下载 ICS / CSV」按钮生成;headless 环境无法获取,需有浏览器4243## Flow 结构4445esflow DAG 编排(`scripts/flow.py` 声明,`scripts/nodes/` 各节点):4647```text48holidays ∥ resolve_stations → query_sale_time → export_html49```5051- `holidays`:入口节点,升级 chinesecalendar + 取目标年份节假日区间,与 resolve_stations **并行**52- `resolve_stations`:入口节点,下载站名表 + 模糊匹配出发/返程站53- `query_sale_time`:依赖 resolve_stations,查去程 + 返程起售时间(无返程站则返程字段 None)54- `export_html`:**扇入** holidays 与 query_sale_time,组装抢票条目 + 生成 HTML + 弹出浏览器5556无 TO_AGENT 断点,纯数据流一次跑完。无返程站时 `query_sale_time` 返程字段返回 None,`export_html` 不生成返程条目。5758## 操作 SOP(对应四节点)5960### resolve_stations:解析车站 → 电报码61621. 下载 `https://kyfw.12306.cn/otn/resources/js/framework/station_name.js`632. 提取 `站名|代码` 映射643. 模糊匹配出发站、返程站:精确命中优先;否则按相似度 + 同长度前缀加分65 例:`北京难` → `北京南`664. 匹配失败抛 `station_error` 列出候选(esflow 非交互,不再 stdin 询问)6768### query_sale_time:查询起售时间6970分别查出发站、返程站:71721. 优先调用官方缓存接口 `queryAllCacheSaleTime`,按 `station_telecode` 查 `sale_time`732. 失败时回退解析 `sale_time.html` 页面743. 仍失败则默认 `14:00` 并标注7576### holidays:获取节假日(chinesecalendar)7778- 遍历指定年份每一天,用 `get_holiday_detail` 识别节日名79- 取每个节日**首日**(去程乘车日)与**末日**(返程乘车日)80- 支持跨年查询;年份超库支持范围抛 `holiday_error`8182### export_html:打开 HTML 日历页8384写入 `~/Downloads/ticket-calendar/`,并自动打开浏览器。网页内完成:8586- **出发 / 返程**切换(默认出发),表格与导出仅含当前行程87- 提醒方式:当天 / 提前 1 / 2 / 3 天(默认当天);起售前分钟数可调(默认 15)88- 每行可**忽略**不出行的节日(如端午),导出时不包含,可恢复89- 下载 ICS 或 CSV,内容与当前选项一致9091| 方向 | 乘车日 | 抢票日 | 起售站 |92|------|--------|--------|--------|93| 去程 | 假期首日 | 首日 − 15 天 | 出发站 |94| 返程 | 假期末日 | 末日 − 15 天 | 返程站 |9596预售期 15 天,以 12306 官方为准。9798## 参数99100| 参数 | 说明 |101|---|---|102| `departure` | 出发站名,如 北京南 |103| `return_station` | 返程站名,如 天津西;省略则仅生成去程 |104| `year` | 目标年份,默认当前年 |105| `--update` | 仅升级 chinesecalendar 并自检,不生成日历 |106| `--skip-update` | 跳过默认的节假日库同步 |107| `--out <dir>` | HTML 输出目录,默认 `~/Downloads/ticket-calendar`(headless 环境建议显式指定) |108| `--no-open` | 不自动打开浏览器(headless 环境必加) |109| `--job-dir <dir>` | 指定 esflow job 目录 |110| `--schema` | 仅打印 JSON 契约到 stdout 后退出,不跑 flow、不生成产物(退出码 0) |111112退出码:`0 ok / 1 runtime / 3 validation`113114## 输出契约115116成功时 stdout 输出一行 JSON envelope:117118```json119{120 "ok": true,121 "data": {122 "html_path": "/path/to/ticket-calendar-北京南-天津西-2026.html",123 "departure": "北京南",124 "return": "天津西",125 "year": 2026,126 "festivals": ["元旦", "春节", "清明", "劳动节", "端午", "中秋", "国庆"]127 },128 "error": null,129 "meta": {"schema_version": "1.0.0", "tool": "holiday-of-12306", "elapsed_ms": 1489}130}131```132133失败时 `ok=false`,`error` 含 `{code, message, retryable}`;`data` 为 null。`--schema` 输出同结构但字段为类型说明字符串。esflow 事件流(trace/running/artifact)走 stderr,不污染 stdout 的 envelope。134135## 异常处理136137| 情况 | 行为 |138|------|------|139| 车站匹配失败 | 抛 `station_error`,列出候选站名 |140| 起售时间查询失败 | 日志输出失败原因,回落 `14:00 (默认)`(接口正常返回 14:00 时无「(默认)」后缀,可区分) |141| 节假日数据缺失 | 抛 `holiday_error`,执行 `--update` 查看详情 |142| 年份超库支持范围 | 抛 `holiday_error`,提示支持区间 |143144## Agent 使用指引145146用户提到「抢票日历」「节假日购票」「12306 起售时间」时使用本 skill:1471481. 确认出发站、返程站(可选)、目标年份1492. 执行 `python3 scripts/run.py <出发站> [返程站] [年份]`1503. 引导用户在网页中切换出发/返程、忽略不出行的节日、选择提醒后下载 ICS