获取 12306官网:拿到全路最完整的车站数据表
写在前面:3382 站、11 字段:把 12306 车站数据一次讲透
设想你手上有某城市的地铁刷卡数据、或某平台的城际 OD 数据,想叠加"火车站位置"做空间分析。你打开 12306 想找一份全国车站清单,结果搜出来是零散的售票页、起售时间公告------车站信息从来没有一个统一的下载入口。
它分散在三个 JS 文件里,完整度天差地别。结论先放这:
- 真正"最全"的,是购票域(kyfw)下的
station_name.js,3382 个车站、11 个字段; - 它带城市归属、带三字码,是打通 12306 余票 / 时刻 / OD 接口的"通用主键";
- 它不含经纬度,需要坐标得另走接口或第三方源。
一、12306 藏着三张"车站表",完整度天差地别
同一官网,不同域名、不同用途,车站信息被拆成了三份。用途不同,别拿错。

| 文件 | 域名 | 车站数 | 字段数 | 城市归属 | 主要用途 |
|---|---|---|---|---|---|
qss.js |
www | 2 803 | 2 | 无 | 起售(放票)时间表 |
station_name.js |
www(官网域) | 3 284 | 6 | 无 | 官网车站联想输入 |
station_name.js |
kyfw(购票域) | 3 382 | 11 | 有 | 全路最全车站主数据 |
一句话记牢:要车站主数据,认准 kyfw.12306.cn 下的 station_name.js。
二、最全的那张表,URL 在这里
直接给地址(浏览器或脚本可直连,无需登录):
https://kyfw.12306.cn/otn/resources/js/framework/station_name.js
它定义了一个全局变量 station_names,形如:
javascript
var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0|0357|北京|.......@vap|北京北......';
技术细节:
- 整张表以
@分隔每条记录 ,条内以|分隔字段; - 首页
https://www.12306.cn/index/本身并不直接引用它,但购票域各页面加载时都会拉它做车站联想输入------所以它永远是最新的; - 对比 www 域版本
https://www.12306.cn/index/script/core/common/station_name.js,两者不是同一个文件(域名、路径、内容都不同)。kyfw 版多出了城市代码、城市名、境外站国别等字段,且车站数多近 100 个。
三、字段解剖:一条记录藏着 11 个字段
这是本文的重点之一。很多人拿到 station_names 只取了站名,浪费了后面 9 个字段。我们拿"北京北"这条记录拆开看:

| 序号 | 字段 | 含义 | 示例(北京北) |
|---|---|---|---|
| 0 | telecode |
站名电报码 | bjb |
| 1 | station_name |
车站名称 | 北京北 |
| 2 | spell |
三字码(查票用这个) | VAP |
| 3 | pinyin |
拼音全拼 | beijingbei |
| 4 | initial |
拼音首字母 | bjb |
| 5 | seq |
序号 | 0 |
| 6 | city_code |
城市代码 | 0357 |
| 7 | city_name |
城市名 | 北京 |
| 8 | 国别码 | 仅境外站填充 | lao |
| 9 | 国别名 | 仅境外站填充 | 老挝 |
| 10 | 英文名 | 仅境外站填充 | vientiane |
几个关键点:
- 字段 2(三字码)是金钥匙 。北京 =
BJP、上海 =SHH、广州南 =GZN......余票、时刻、OD 类接口都靠它寻站,不是靠站名也不是靠电报码。 - 字段 6/7 直接送你城市归属。后面按城市群聚合、按省统计都用得上,不用再单独做"站→市"映射。
- 字段 8--10 只有境外站有值(中老铁路的万象、磨丁、琅勃拉邦),是国别信息。
四、动手获取:完整可运行脚本
station_name.js 的本质是一个 var station_names='...' 字符串:记录之间用 @ 分隔,字段之间用 | 分隔。下面把「下载 → 解析 → 落盘 CSV」整合成一个保存后可直接运行 的脚本 grab_stations.py。
python
import csv
import re
import requests
SOURCE_URL = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js"
SRC_FILE = "station_name_kyfw.js"
OUT_FILE = "station_name_full.csv"
# 11 个字段,顺序与 12306 源文件一致
FIELDS = [
"telecode", # 0 电报码(部分内部建表接口使用)
"station_name", # 1 站名
"spell", # 2 三字码(leftTicket / OD 接口寻站用)
"pinyin", # 3 全拼
"initial", # 4 拼音首字母
"seq", # 5 序号
"city_code", # 6 城市代码
"city_name", # 7 城市名
"country_code", # 8 国别代码(仅边境 / 国际站有值)
"country_name", # 9 国别名
"en_name", # 10 英文名
]
def fetch_source() -> str:
"""优先自动下载;失败则使用本地已保存的 JS 文件。"""
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
),
"Referer": "https://kyfw.12306.cn/otn/leftTicket/init",
}
try:
resp = requests.get(SOURCE_URL, headers=headers, timeout=20)
resp.raise_for_status()
print(f"已自动下载:{SOURCE_URL}({len(resp.text):,} 字符)")
return resp.text
except Exception as exc:
print(f"自动下载失败:{exc}")
print(f"尝试读取本地文件:{SRC_FILE}")
with open(SRC_FILE, encoding="utf-8") as f:
return f.read()
def parse(text: str) -> list:
"""从 station_name.js 文本解析出二维记录表。"""
m = re.search(r"var\s+station_names\s*=\s*'(.*)'", text, re.S)
if not m:
raise ValueError("未找到 var station_names = '...'")
body = m.group(1)
rows = [row.split("|") for row in body.split("@") if row.strip()]
# 字段不足 11 的脏数据按 None 补齐,保证列对齐
for r in rows:
if len(r) < len(FIELDS):
r.extend([None] * (len(FIELDS) - len(r)))
return rows
def main() -> None:
text = fetch_source()
rows = parse(text)
with open(OUT_FILE, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(FIELDS)
writer.writerows(rows)
print(f"已解析 {len(rows)} 个车站、{len(FIELDS)} 个字段 → {OUT_FILE}")
if __name__ == "__main__":
main()
运行效果:
text
已自动下载:https://kyfw.12306.cn/otn/resources/js/framework/station_name.js
已解析 3382 个车站、11 个字段 → station_name_full.csv
得到的 station_name_full.csv 就是全路车站主数据底座,后续所有接口查询、建表、可视化都可以拿它做字典。

六、全国枢纽锚点:用最少锚点覆盖所有省份的站点查询
拿到 3 382 站主数据后,一个现实问题是:批量拉取全国站点数据不可能逐站两两查询,而且 leftTicket 只返回直达车次 ,省内小站配到远端枢纽往往查出来是空。解决办法是圈定一组"枢纽锚点",用"枢纽 ↔ 省内站点"配对查询------只要一个省里至少有一个枢纽与该省站点有直达/经停,就能用这组枢纽把全省覆盖掉。
6.1 全国直达连通强度一张图

从"对外直达车次总量"看,上海、郑州、北京西 处于第一梯队(3 000+),武汉、西安、广州次之。覆盖完整性上,5 个枢纽可以一票直达全国 32 个有 12306 客运站的省级行政区;西安唯一缺口是海南------琼州海峡靠铁路轮渡接驳,西安没有图定直发车。
6.2 六大枢纽辐射 OD 流向图(弧线 + Top6 排行)

这一组直接复刻 OD 地理流向图的读图逻辑:每张卡片左半边是同一张中国版图,弧线从枢纽(大圆点)指向目的地省(小圆点) ,弧线越粗、节点越大,表示该方向的直达车次越多;灰色细线是省界,仅作空间参照。右半边是对应的 Top6 目的地 榜单。一眼能读出的"主场"非常清楚:
- 北京西(华北):天津 562、河北 432 独一档,弧线向华北、东北扇形展开;
- 上海(华东):浙江 708、江苏 702 双核,绝大多数车流都锁死在长三角;
- 广州(华中/华南):湖南 432、湖北 288 领衔,往华中纵深辐射;
- 武汉(华中十字):湖南 368、河南 304、广东 304 三足鼎立,是全国最"均衡"的十字路口;
- 郑州(华北十字):河北 338、陕西 336、湖北 290 均匀铺开,南北向与东西向并重;
- 西安(西北门户):河南 334、甘肃 258、四川 204 靠前,对西北有压倒优势,但整体能级偏低、且唯一缺口海南。
弧线的方向告诉你"车流去哪里",线粗告诉你"去得多不多",右侧榜单告诉你"具体是哪些省、多少班"------三者对照,比填色地图更像一张"铁路导航图"。
6.3 枢纽能级与全国覆盖完整性


从"对外直达车次总量"看,上海、郑州、北京西 处于第一梯队(3 000+),武汉、西安、广州次之。覆盖完整性上,5 个枢纽可以一票直达全国 32 个有 12306 客运站的省级行政区;西安唯一缺口是海南------琼州海峡靠铁路轮渡接驳,西安没有图定直发车。
6.4 OD 实测:6 大枢纽 × 6 个难点省份的直达车次

把西藏、新疆、青海、宁夏、海南、黑龙江这 6 个远端省份拉出来看:
- 西安对西北的压倒性优势:青海 96 班、宁夏 108 班、新疆 26 班;
- 北京西对东北的统治力:黑龙江 94 班;
- 海南是全国最难直达的省份:除广州 10 班外,其余枢纽都只有 2--4 班,西安直接为 0。
七、这份主数据能拿来干什么
拿到 3382 站 × 11 字段的主数据,真正价值在"关联"。它是打通 12306 各接口的通用主键:

典型链路:
- 站名 / 三字码 / 城市归属 → 作为主键,去 JOIN 余票、时刻、OD 接口返回的数据;
- 城市名 / 城市代码 → 映射到 19 个城市群(京津冀、长三角、珠三角......),做城市群级聚合;
- 空间化 → 需要坐标时,用三字码去时刻 / 余票接口反查,或接第三方地理编码,形成OD 流向图。
文章仅用于分享个人学习成果与个人存档之用,分享知识,如有侵权,请联系作者进行删除。所有信息均基于作者的个人理解和经验,不代表任何官方立场或权威解读。