恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python爬虫实战:抓取12306火车站三字码数据
首页
资讯中心
/
Python爬虫实战:抓取12306火车站三字码数据
Python爬虫实战:抓取12306火车站三字码数据
发布时间:2026/8/13 3:57:11
1. 项目概述与核心价值最近在做一个跟火车票数据相关的自动化工具发现很多第三方接口或者本地数据处理都需要用到火车站的标准三字码。比如你想查“北京”到“上海”的余票直接传中文站名过去很多系统是不认的必须用“BJP”和“SHH”这样的代码。12306作为官方数据源它的站名-代码映射表无疑是最权威、最及时的。手动去官网查站台上千个显然不现实。所以写个Python爬虫来自动化抓取这份映射关系就成了一个非常实际且高频的需求。这个项目就是带你一步步搞定这个任务从分析网页结构到稳定获取数据最后整理成一份干净可用的字典或CSV文件。无论你是想学习基础的网页抓取还是需要这份数据来做数据分析、抢票脚本或者旅行应用这篇实操指南都能给你一个清晰的路径。2. 目标网站分析与技术选型2.1 12306火车站信息页面探秘我们的目标是12306官网上的车站代码查询页面。直接访问https://www.12306.cn/index/script/core/common/station_name_v10026.js这个链接注意版本号v10026可能会变你会发现它返回了一个JavaScript文件。打开一看数据以一种非常“紧凑”的格式存储着bjb|北京北|VAP|beijingbei|bjb|0bjd|北京东|BOP|beijingdong|bjd|1bji|北京|BJP|beijing|bj|2bjn|北京南|VNP|beijingnan|bjn|3bjx|北京西|BXP|beijingxi|bjx|4bjy|北京朝阳|BJP|chaoyang|bjy|5...这种格式对于网络传输非常高效每个车站信息用符号分隔内部再用|管道符分割不同字段。我们需要从中解析出中文站名和对应的三字码。选择直接抓取这个JS文件而不是渲染后的HTML页面是因为它数据纯净没有反爬干扰如复杂的JavaScript渲染或图片验证是典型的“数据接口”型页面非常适合爬虫初学者入门也适合需要稳定数据源的开发者。2.2 工具链选择与理由对于这个任务我们的工具链力求简洁高效请求库requests。这是Python HTTP客户端库的事实标准简单易用功能强大。相比于urllib它的API更加人性化。在这个项目中我们只需要发起一个简单的GET请求requests绰绰有余。解析与处理纯Python字符串方法。由于数据格式是规则且简单的分隔文本和|使用Python内置的str.split()方法进行分割比引入BeautifulSoup或lxml等HTML解析库更轻量、更快速。正则表达式re在这里也可以但杀鸡焉用牛刀split()的代码可读性更高。数据存储csv模块或直接构造字典。最终我们需要的是“站名-三字码”的映射关系。根据后续使用场景可以将其存储为Python字典在内存中使用也可以用csv模块写入stations.csv文件方便用Excel打开或被其他程序读取。json格式也是一个好选择尤其适合Web应用。注意虽然这个JS文件目前看起来没有反爬措施但我们在编写爬虫时仍需遵守基本的网络礼仪。务必设置合理的请求头User-Agent并考虑添加短暂的延时例如time.sleep(1)即使单次请求养成好习惯也对未来爬取其他网站有益。3. 爬虫核心实现步骤拆解3.1 环境准备与依赖安装首先确保你的Python环境已经就绪建议使用Python 3.6以上版本。这个项目依赖极少主要就是requests库。打开你的终端或命令行使用pip安装pip install requests如果安装速度慢可以考虑使用国内的镜像源例如清华源pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后创建一个新的Python脚本文件比如fetch_12306_stations.py就可以开始编码了。3.2 发起请求与获取原始数据第一步是获取那个包含所有车站信息的JS文件内容。我们需要模拟浏览器发起一个GET请求。import requests def fetch_station_data(): # 目标URL注意版本号可能变化如果失效需要手动更新 url ‘https://www.12306.cn/index/script/core/common/station_name_v10026.js‘ # 设置请求头模拟浏览器访问这是一个好习惯 headers { ‘User-Agent‘: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36‘ } try: response requests.get(url, headersheaders) # 检查请求是否成功状态码200 response.raise_for_status() # 假设响应内容编码是UTF-8如果不是可能需要调整 response.encoding ‘utf-8‘ return response.text except requests.exceptions.RequestException as e: print(f“请求数据失败: {e}“) return None if __name__ ‘__main__‘: raw_data fetch_station_data() if raw_data: print(“成功获取原始数据前500个字符为“) print(raw_data[:500])这段代码做了几件事定义目标URL设置一个常见的User-Agent头让服务器认为我们是普通浏览器然后使用requests.get()发起请求。response.raise_for_status()会在状态码不是200时抛出异常便于我们及时发现问题。最后打印前500个字符是为了确认我们确实拿到了预期的数据格式。3.3 数据解析从混乱文本到结构化字典拿到raw_data后你会发现它并不是一个纯粹的数组字符串很可能被包裹在一个JavaScript变量赋值语句里比如var station_names ‘bjb|北京北|...‘;。我们需要先把有效的数据部分提取出来。def parse_station_data(raw_text): “““ 解析原始JS文本提取车站名和三字码的映射。 参数: raw_text - requests返回的原始文本 返回: 一个字典格式为 {‘北京北‘: ‘VAP‘, ‘北京东‘: ‘BOP‘, ...} “““ # 1. 找到有效数据的起始位置。通常数据在第一个单引号对之间。 # 查找第一个‘‘符号的位置它标志着数据段的开始。 start_index raw_text.find(‘‘) if start_index -1: print(“未在响应中找到车站数据起始标记‘‘“) return {} # 2. 提取从‘‘开始到行尾或分号之前的数据字符串。 # 简单处理找到从start_index开始到下一个分号‘;‘之前的内容。 end_index raw_text.find(‘;‘, start_index) if end_index -1: # 如果没有分号则取到末尾 data_string raw_text[start_index:] else: data_string raw_text[start_index:end_index] # 3. 按‘‘分割得到每个车站的字符串 stations_raw_list data_string.split(‘‘)[1:] # 第一个元素是空字符串跳过 station_dict {} # 4. 遍历每个车站字符串按‘|‘分割提取所需字段 for station in stations_raw_list: parts station.split(‘|‘) # 根据观察常见格式为电报码|站名|三字码|拼音|简拼|序号 # 例如”bjb|北京北|VAP|beijingbei|bjb|0“ if len(parts) 3: # 确保有足够字段 chinese_name parts[1] telegraph_code parts[2] # 这就是我们需要的三字码 station_dict[chinese_name] telegraph_code return station_dict解析函数的核心逻辑是字符串分割。首先定位有效数据然后通过split(‘‘)将整个长字符串拆分成一个个车站单元再对每个单元用split(‘|‘)拆出字段。这里的关键是确定字段顺序通过观察样本数据我们确定索引为1的是中文站名索引为2的是三字码。将其以键值对形式存入字典。实操心得在写解析逻辑时一定要先打印出几个完整的车站单元样本仔细研究。不同时期12306的JS文件字段顺序或数量可能有微调。确保你的parts索引抓取的是正确的字段。另外增加if len(parts) 3:这样的判断是鲁棒性编程的好习惯可以避免因数据格式意外变化导致程序崩溃。3.4 数据存储与持久化解析得到字典后我们可以根据需求选择保存方式。方案一保存为CSV文件推荐便于查看和交换import csv def save_to_csv(station_dict, filename‘12306_stations.csv‘): “““将车站字典保存为CSV文件“““ with open(filename, ‘w‘, newline‘‘, encoding‘utf-8-sig‘) as csvfile: # 使用utf-8-sig编码可以在Excel中正确显示中文 writer csv.writer(csvfile) # 写入表头 writer.writerow([‘车站中文名‘, ‘三字码‘]) # 写入数据 for name, code in station_dict.items(): writer.writerow([name, code]) print(f“数据已保存至 {filename}共 {len(station_dict)} 条记录。“)方案二保存为JSON文件适合Web应用或配置import json def save_to_json(station_dict, filename‘12306_stations.json‘): “““将车站字典保存为JSON文件“““ with open(filename, ‘w‘, encoding‘utf-8‘) as jsonfile: json.dump(station_dict, jsonfile, ensure_asciiFalse, indent2) # ensure_asciiFalse 保证中文正常显示 # indent2 使文件有缩进更易读 print(f“数据已保存至 {filename}。“)方案三在Python中直接使用字典如果只是本次脚本运行期间使用那么parse_station_data函数返回的字典对象已经可以直接用了例如station_dict.get(‘北京‘)会返回‘BJP‘。3.5 完整脚本整合与执行将上述所有函数整合并添加主程序逻辑。import requests import csv import json import time # 此处插入上面定义的 fetch_station_data, parse_station_data, save_to_csv, save_to_json 函数 def main(): print(“开始抓取12306车站三字码...“) # 1. 获取数据 raw_text fetch_station_data() if not raw_text: print(“获取原始数据失败程序退出。“) return # 2. 解析数据 print(“正在解析数据...“) station_dict parse_station_data(raw_text) if not station_dict: print(“解析数据失败未得到有效车站信息。“) return print(f“解析成功共获取 {len(station_dict)} 个车站信息。“) # 3. 保存数据按需选择一种或多种 # 保存为CSV save_to_csv(station_dict) # 保存为JSON save_to_json(station_dict) # 4. 简单演示查询 print(“\n--- 演示查询 ---“) test_stations [‘北京‘, ‘上海‘, ‘广州‘, ‘深圳‘, ‘成都‘] for station in test_stations: code station_dict.get(station) if code: print(f“{station} - {code}“) else: print(f“未找到车站: {station}“) print(“\n任务完成“) if __name__ ‘__main__‘: main() # 可选添加一个短暂延时以示友好 # time.sleep(1)运行这个脚本你应该能在同目录下得到12306_stations.csv和12306_stations.json两个文件并在控制台看到关键车站的代码输出。4. 关键问题排查与进阶优化4.1 常见问题与解决方案在实际操作中你可能会遇到以下问题问题现象可能原因解决方案请求失败返回状态码403/4041. URL中的版本号如v10026已过期。2. 网站屏蔽了简单的爬虫请求。1.手动更新URL访问12306官网按F12打开开发者工具在Network网络选项卡中刷新页面搜索station_name相关的JS文件找到最新的URL替换。2.完善请求头除了User-Agent可以尝试添加Referer来源页等头信息模拟更真实的浏览器行为。解析出的字典为空或数据错乱1. 数据格式发生变化字段顺序或分隔符改变。2. 提取有效数据字符串的逻辑有误。1.打印并检查原始数据片段在解析前print(raw_text[:1000])确认数据格式是否还是xxx保存的CSV文件用Excel打开中文乱码Excel默认可能不识别UTF-8 without BOM编码。在open()函数中将编码encoding‘utf-8‘改为encoding‘utf-8-sig‘这个BOM头能帮助Excel正确识别。脚本偶尔运行成功偶尔失败网络不稳定或触发了网站轻微的频率限制。加入异常重试机制和延时。使用requests的timeout参数并用try-except包裹请求失败后等待几秒重试。4.2 代码健壮性优化建议一个用于生产环境的爬虫需要考虑更多的边界情况和稳定性。1. 增加重试机制import time from requests.adapters import HTTPAdapter from requests.packages.urllib3.util.retry import Retry def create_session_with_retries(retries3, backoff_factor0.5): “““创建一个带重试机制的requests Session“““ session requests.Session() retry_strategy Retry( totalretries, backoff_factorbackoff_factor, # 重试等待时间{backoff_factor} * (2^{重试次数-1})秒 status_forcelist[500, 502, 503, 504], # 遇到这些状态码才重试 ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(“http://“, adapter) session.mount(“https://“, adapter) return session # 在fetch_station_data函数中使用session代替直接的requests.get session create_session_with_retries() response session.get(url, headersheaders, timeout10) # 设置超时2. 更灵活的数据提取如果数据格式包裹方式多变可以使用正则表达式来匹配核心数据部分容错性更强。import re def extract_data_string(raw_text): “““使用正则表达式提取车站数据字符串“““ # 匹配模式以开头包含大量|和字符直到分号或字符串结束 pattern r‘([a-z]\|[^;])‘ match re.search(pattern, raw_text) if match: return match.group(1) else: # 备用模式尝试匹配单引号内的所有内容 pattern2 r“station_names\s*\s*‘([^‘])‘“ match2 re.search(pattern2, raw_text) return match2.group(1) if match2 else None3. 数据验证与清洗在存入字典前可以对站名和代码进行简单的清洗和验证比如去除首尾空白检查三字码是否为大写字母和数字的组合等。4.3 应用场景延伸获取到这份站名-代码映射表后它的用处远不止于查询构建本地车站数据库作为抢票脚本、余票查询工具的后端数据支撑。数据清洗与关联在处理第三方火车票订单或日志数据时用于将代码转换为可读的站名或反之。旅行类应用开发开发行程规划、票价分析等应用时这是必不可少的基础数据。数据分析结合车次数据可以分析城市间的铁路连接度、热门线路等。你可以定期运行这个爬虫脚本例如每周一次以更新本地数据确保与12306官方数据同步。将脚本部署到服务器搭配定时任务如Linux的cron或Windows的任务计划程序即可实现全自动化更新。5. 法律与道德边界提醒最后也是最重要的一点我们必须明确爬虫行为的边界。12306是中国铁路客户服务中心官方网站承载着巨大的公共服务职能。遵守robots.txt虽然技术上我们可以抓取这个JS文件但首先应检查https://www.12306.cn/robots.txt尊重网站所有者设置的爬虫协议。如果明确禁止抓取相关路径则应停止。控制访问频率本项目仅单次请求获取静态数据对服务器造成的压力微乎其微这是合理的。绝对禁止为了监控变化而进行高频、循环的请求这会对服务器造成不必要的负担可能被视为恶意攻击。数据用途获取的数据应用于个人学习、研究或合法的项目开发。严禁用于商业倒卖、干扰12306系统正常运行如开发恶意抢票软件对服务器造成压力、或任何侵犯他人权益的用途。规避反爬是技术探讨而非鼓励对抗文中提到的完善请求头等方法是为了让请求更“像”正常浏览器提高成功率。这不应被理解为教唆去刻意绕过网站的安全防护措施。技术的运用必须合乎法律与道德。写爬虫技术是手段责任是底线。在动手之前多想一想“应不应该”和“怎么实现”同样重要。这个项目为你提供了一个绝佳的实战场景来练习HTTP请求、数据解析、文件操作和错误处理这些核心技能。希望你在获取数据的同时也能建立起对网络数据采集的正确认知。