尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python爬虫实战:12306车票数据获取与pandas分析全流程

Python爬虫实战:12306车票数据获取与pandas分析全流程 1. 项目概述与核心价值最近后台收到不少私信很多刚入门Python的朋友尤其是想往数据分析或者自动化方向发展的同学都问有没有一个能串起多个知识点的实战项目。他们觉得光看语法和做小练习不够过瘾想找个有实际数据、能解决真实问题的案例来练手。我一想这不就是当年我自己的学习路径吗从“Hello World”到能独立完成一个小工具中间最缺的就是这种“麻雀虽小五脏俱全”的项目。于是我决定把几年前做的一个“12306车票信息查询与处理”的脚本翻出来重新梳理、优化并分享给大家。这个项目标题听起来可能有点“老生常谈”但它确实是一个经典且极佳的练手项目。为什么这么说因为它几乎覆盖了一个数据获取与处理流程的所有关键环节网络请求、数据解析、反爬应对、数据清洗、结构化存储以及初步分析。你不仅能学到requests、BeautifulSoup/parsel、pandas这些库的基本操作更能理解它们是如何在一个真实场景中协同工作的。对于小白来说最大的障碍往往不是代码本身而是面对一个复杂网站时不知从何下手。12306的页面结构复杂、数据动态加载、还有各种反爬机制听起来就让人头大。但别担心我们这次会采用一种相对简单、稳定的思路来绕过这些难点核心是模拟浏览器的查询行为从接口直接获取结构化的JSON数据而不是去硬啃复杂的HTML页面。这样一来我们就能把主要精力放在数据处理这个更有价值的环节上。通过这个教程你将能实现一个脚本输入出发地、目的地和日期脚本自动获取所有车次信息包括车次号、出发/到达时间、历时、座位类型与价格、是否还有余票等并将这些数据清洗后保存到Excel或数据库中方便后续进行票价分析、最佳车次筛选等。这不仅是爬虫练习更是数据分析的起点。下面我们就从零开始一步步拆解实现。2. 整体思路与技术选型解析在动手写代码之前理清思路和选对工具至关重要。面对12306这样的网站直接下载页面源码然后用正则表达式或BeautifulSoup去解析是一条非常艰难的路。页面元素嵌套深且关键数据如车次、票价很可能是通过JavaScript动态加载的。2.1 核心思路寻找数据接口现代网站普遍采用前后端分离的架构前端页面负责展示真实数据则通过后端API接口以JSON格式提供。我们的核心策略就是找到这个提供车票数据的API接口。如何找打开浏览器以Chrome为例进入12306官网的查询页面按F12打开“开发者工具”切换到“Network”网络选项卡。然后进行一次正常的车票查询。此时你会看到网络请求列表中涌现出大量请求。我们需要从中筛选出那个返回了车次列表数据的请求。通常这个请求会具有一些特征请求类型Type为XHR或Fetch。返回的响应Preview内容是可读的JSON结构里面包含了车次、时间、座位等信息。请求的URL可能包含“query”、“leftTicket”、“queryZ”等关键字。通过仔细查找我们可以定位到核心的查询接口。找到后我们需要分析该请求的URL它的构成规律哪些是固定部分哪些是参数如出发站、到达站、日期。请求方法通常是GET或POST。请求头特别是User-Agent模拟浏览器身份、Referer来源页面有时是反爬校验所必需、Cookie维持登录或会话状态对于公开查询可能非必需。请求参数GET请求的参数在URL的query string里POST请求的参数在Payload里。我们的爬虫将模拟这个请求从而直接获得最“干净”的JSON数据。2.2 技术栈选型与理由确定了思路我们来选择实现工具网络请求库requests为什么选它requests是Python中最简单易用的HTTP库其API设计极其人性化几行代码就能完成复杂的网络请求。对于我们这个项目它完全够用。相比于原生urllib它省去了大量繁琐的步骤。替代方案aiohttp异步适合高性能爬虫、httpx支持HTTP/2兼容requestsAPI。但对于初学者和本项目的规模requests是最佳选择。数据解析库直接处理JSON为什么不用BeautifulSoup因为我们从接口获取的是JSON数据这是一种结构化的数据格式Python内置的json库可以轻松将其转换为字典或列表。这比解析非结构化的HTML要简单、高效、稳定得多。操作response.json()一键搞定。数据处理与分析库pandas为什么选它我们获取的车次数据是列表形式的每个车次是一个字典。pandas的DataFrame天生就是为处理这种表格型数据而生。它能让我们以类似Excel的方式轻松进行数据清洗、筛选、排序、计算和保存。核心用途将JSON数据转换为清晰的表格处理缺失值如无票的座位类型计算附加信息如旅程分钟数导出为Excel/CSV。辅助工具time/datetime用于处理日期时间添加请求间隔避免访问过快。random配合time让请求间隔更“人性化”降低被封风险。json用于解析和格式化输出。注意请务必遵守网站robots.txt规则并控制请求频率例如每次查询间隔3-5秒。本项目仅用于个人学习与技术交流严禁用于商业用途或对12306服务器造成压力的高频访问。3. 实操准备与核心接口分析理论说得再多不如动手一试。我们先来搭建环境并实际抓包分析一下12306的数据接口。3.1 环境搭建与库安装确保你的电脑上安装了Python3.6及以上版本。然后我们通过pip安装必需的库。打开你的命令行终端CMD、PowerShell或Terminal执行以下命令pip install requests pandas openpyxlrequests: 用于发送HTTP请求。pandas: 用于数据处理和导出。openpyxl: 这是pandas为了将数据写入Excel.xlsx格式文件所需要的引擎。如果安装速度慢可以使用国内镜像源例如清华源pip install requests pandas openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple3.2 关键接口抓包实战这是整个项目的基石请耐心跟随步骤操作。打开12306官网在浏览器中访问www.12306.cn。打开开发者工具按F12键选择“网络”选项卡。确保勾选了“保留日志”。进行一次查询在网页上填写“出发地”如北京、“目的地”如上海、“日期”选择一个未来的日期然后点击“查询”。筛选请求在开发者工具的网络请求列表中你会看到很多请求。在筛选框内输入关键词如“leftTicket”或“query”可以快速缩小范围。定位核心接口经过筛选你可能会看到一个名为leftTicket/query或类似名称的请求。点击它查看“标头”和“响应”。以我当前请注意12306接口可能随时间变化分析的一个典型接口为例请求URL:https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date2023-10-01leftTicketDTO.from_stationBJPleftTicketDTO.to_stationSHHpurpose_codesADULT分析:基础URL:https://kyfw.12306.cn/otn/leftTicket/query查询参数:leftTicketDTO.train_date: 出发日期格式YYYY-MM-DD。leftTicketDTO.from_station: 出发站电报码如BJP代表北京。leftTicketDTO.to_station: 到达站电报码如SHH代表上海。purpose_codes: 票种ADULT代表成人票。难点车站电报码如何获取你可能会问我怎么知道“北京”是BJP“上海”是SHH这需要一个车站编码对照表。幸运的是12306提供了一个接口可以获取这个列表。我们可以通过另一个请求通常是加载页面时请求的一个js文件或接口来获取并保存一份站名-编码的映射关系在查询前进行转换。为了简化教程我们可以先手动从网络请求中查找几个常用车站的编码或者在网上搜索一份相对稳定的车站编码表作为本地文件使用。查看响应点击该请求的“响应”或“Preview”选项卡你会看到一个庞大的JSON对象。其中data字段下的result列表就是我们要的车次信息。不过这个列表里的每个元素是一个用|竖线分割的字符串包含了所有信息需要按照一个固定的字段顺序来解析。这个顺序需要从另一个接口通常是queryZ返回的字段映射关系来获取。为了简化流程我们可能会发现另一个更“友好”的接口它直接返回了结构更清晰的JSON。请你在实际操作中仔细寻找。如果找不到我们就需要面对解析那个竖线分割的字符串。别担心无论哪种形式我们都有办法处理。实操心得抓包时清空之前的网络记录再操作能让查找更清晰。多尝试几个日期和车站组合观察URL参数的变化规律确认接口的稳定性。接口地址和参数格式并非一成不变这是爬虫需要维护的原因之一。4. 分步实现爬取与解析流程现在我们开始编写代码。我将整个过程分解为几个函数让结构更清晰。4.1 第一步构建请求头与获取车站编码首先我们需要模拟浏览器。创建一个Python脚本文件比如train_ticket.py。import requests import pandas as pd import time import random import json # 1. 定义请求头 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, Referer: https://kyfw.12306.cn/otn/leftTicket/init, # 重要很多接口会校验来源 # Cookie在公开查询时可能不需要但如果遇到403错误可能需要从浏览器复制一个临时的Cookie过来 # Cookie: 你的Cookie字符串 } # 2. 车站编码映射这里先手动定义一部分实际项目应从接口获取并缓存 station_code_map { 北京: BJP, 上海: SHH, 广州: GZQ, 深圳: SZQ, 杭州: HZH, 南京: NJH, 武汉: WHN, 成都: CDW, 西安: XAY, # ... 可以继续补充 } def get_station_code(station_name): 根据车站名获取电报码如果未找到则返回None return station_code_map.get(station_name)为什么需要Referer有些API接口会检查请求是否来自其合法的网页页面Referer请求头用于告诉服务器当前请求是从哪个页面链接过来的。加上它可以使我们的请求看起来更像一个正常的浏览器行为。4.2 第二步构造查询参数并发送请求接下来我们编写核心的查询函数。这里假设我们使用之前分析的leftTicket/query接口。def query_tickets(from_station, to_station, train_date): 查询指定日期、区间的车票信息 :param from_station: 出发站名如北京 :param to_station: 到达站名如上海 :param train_date: 出发日期格式YYYY-MM-DD如2023-10-01 :return: 返回接口的原始JSON响应数据如果失败返回None # 获取车站电报码 from_station_code get_station_code(from_station) to_station_code get_station_code(to_station) if not from_station_code or not to_station_code: print(f错误未找到车站编码。请检查车站名 {from_station} 或 {to_station} 是否正确或补充编码映射。) return None # 构建查询URL base_url https://kyfw.12306.cn/otn/leftTicket/query params { leftTicketDTO.train_date: train_date, leftTicketDTO.from_station: from_station_code, leftTicketDTO.to_station: to_station_code, purpose_codes: ADULT } try: # 发送GET请求添加headers和params response requests.get(base_url, headersheaders, paramsparams, timeout10) # 检查请求是否成功 response.raise_for_status() # 如果状态码不是200会抛出HTTPError异常 # 返回JSON数据 return response.json() except requests.exceptions.RequestException as e: print(f网络请求出错: {e}) return None except json.JSONDecodeError as e: print(fJSON解析出错: {e}) print(f原始响应文本: {response.text[:500]}) # 打印前500字符以便调试 return None关键点解析params字典会自动拼接到URL的查询字符串中。response.raise_for_status()是一个好习惯它能帮我们及时发现404、500等错误。异常处理很重要网络爬虫必须健壮。我们捕获了网络请求异常和JSON解析异常。如果JSON解析失败打印出原始响应文本的前一部分有助于我们判断是接口变了还是返回了错误信息如验证码。4.3 第三步解析复杂的返回数据这是最具挑战性的一步。假设我们请求成功返回的json_data结构如下简化示意{ data: { result: [ 列车字符串1|列车字符串2|..., 列车字符串1|列车字符串2|..., ... ], map: { ... } // 可能包含车站编码映射 }, httpstatus: 200, messages: , status: true }result列表中的每个字符串都是用|分割的包含了数十个字段。字段的顺序是固定的但这个顺序定义需要从另一个接口https://kyfw.12306.cn/otn/resources/js/query/train_list.js? 或类似获取。为了简化我们根据经验定义一个常见的字段映射请注意这个顺序可能随12306更新而变化这是爬虫维护的痛点。# 3. 定义字段映射关系 (这是一个示例必须根据实际情况调整) # 这个映射关系表示 result 字符串用 ‘|’ 分割后每个位置对应的含义。 field_mapping [ secret_str, # 0 train_no, # 1 车次号如240000Z19670 station_train_code, # 2 显示的车次如Z196 start_station_telecode, # 3 end_station_telecode, # 4 from_station_telecode, # 5 出发站电报码 to_station_telecode, # 6 到达站电报码 start_time, # 7 出发时间 arrive_time, # 8 到达时间 lishi, # 9 历时格式如“11:26” canWebBuy, # 10 能否购买 yp_info, # 11 start_train_date, # 12 train_seat_feature, # 13 location_code, # 14 from_station_no, # 15 to_station_no, # 16 seat_types, # 17 座位类型代码 # 18 到 29 是各种席别的余票和价格信息位置不固定需要根据seat_types解析 # 例如18-高级软卧19-软卧20-软卧21-无座22-硬卧23-硬座24-二等座25-一等座26-商务座27-动卧... gr_num, # 18 高级软卧 qt_num, # 19 其他 rw_num, # 20 软卧 rz_num, # 21 软座 tz_num, # 22 特等座 wz_num, # 23 无座 yb_num, # 24 ? yw_num, # 25 硬卧 yz_num, # 26 硬座 ze_num, # 27 二等座 zy_num, # 28 一等座 swz_num, # 29 商务座 # ... 后面可能还有更多字段 ] def parse_ticket_data(json_data): 解析从接口获取的原始JSON数据转换为字典列表 :param json_data: query_tickets函数返回的JSON对象 :return: 包含所有车次信息的字典列表每个字典代表一个车次 if not json_data or json_data.get(status) is not True: print(接口返回数据状态异常或为空。) return [] result_list json_data.get(data, {}).get(result, []) parsed_trains [] for train_str in result_list: fields train_str.split(|) # 确保字段数量足够避免索引错误 if len(fields) len(field_mapping): # 如果字段数对不上打印警告并跳过或者用更宽松的逻辑 # print(f字段数量不匹配: {len(fields)} {len(field_mapping)}) # 尝试根据实际长度调整 pass train_info {} # 根据映射关系提取字段 for i, field_name in enumerate(field_mapping): if i len(fields): train_info[field_name] fields[i] else: train_info[field_name] None # 字段缺失用None填充 # 提取我们最关心的信息并重命名以便理解 useful_info { 车次: train_info.get(station_train_code), 出发站: train_info.get(from_station_telecode), # 这里还是电报码需要反向查找站名 到达站: train_info.get(to_station_telecode), 出发时间: train_info.get(start_time), 到达时间: train_info.get(arrive_time), 历时: train_info.get(lishi), 商务座: train_info.get(swz_num), # 余票数量有票显示数字无票可能是‘无’或‘’ 一等座: train_info.get(zy_num), 二等座: train_info.get(ze_num), 软卧: train_info.get(rw_num), 硬卧: train_info.get(yw_num), 硬座: train_info.get(yz_num), 无座: train_info.get(wz_num), } parsed_trains.append(useful_info) return parsed_trains注意事项字段映射是最大的坑上述field_mapping是我根据历史经验列出的它极有可能已经过时或不准确。你必须通过分析接口返回的实际数据对照result中某个字符串分割后的数组并结合浏览器端页面展示的信息来亲自确认并更新这个映射关系。这是本项目最需要动手和动脑的地方。余票字段的值可能是数字如20也可能是汉字如有、无也可能是空字符串。在后续数据处理时需要统一。5. 数据处理、清洗与存储拿到解析后的数据列表我们的工作才完成了一半。原始数据往往杂乱需要清洗才能用于分析。5.1 使用Pandas进行数据清洗我们将解析后的字典列表转换为Pandas DataFrame这是数据操作的“神器”。def clean_and_process_data(parsed_trains_list, station_code_map): 清洗和加工车次信息列表 :param parsed_trains_list: parse_ticket_data函数返回的列表 :param station_code_map: 车站编码到站名的反向映射用于解码电报码 :return: 清洗后的DataFrame if not parsed_trains_list: print(没有数据需要处理。) return pd.DataFrame() # 1. 转换为DataFrame df pd.DataFrame(parsed_trains_list) # 2. 解码车站电报码为站名 (创建反向映射) # 假设我们有 station_code_map 是 {北京:BJP, ...} # 需要创建反向映射 code_to_name {BJP:北京, ...} code_to_name {v: k for k, v in station_code_map.items()} df[出发站] df[出发站].map(code_to_name).fillna(df[出发站]) # 如果映射不到保留原编码 df[到达站] df[到达站].map(code_to_name).fillna(df[到达站]) # 3. 处理历时转换为分钟数方便排序和计算 def lishi_to_minutes(lishi_str): 将‘11:26’格式的历时转换为分钟数如686 if not lishi_str or lishi_str : return None try: hours, minutes map(int, lishi_str.split(:)) return hours * 60 minutes except: return None df[历时(分钟)] df[历时].apply(lishi_to_minutes) # 4. 处理余票信息将‘有’、‘无’、‘’等统一 # 定义需要处理的席别列 seat_columns [商务座, 一等座, 二等座, 软卧, 硬卧, 硬座, 无座] for col in seat_columns: # 将空字符串、‘无’等视为0数字字符串转为整数‘有’可以视为一个标志这里我们暂时转为1或保留‘有’ def convert_seat(val): if pd.isna(val) or val or val 无: return 0 elif val 有: return 1 # 或者保留‘有’ else: # 尝试转为整数 try: return int(val) except: return val # 无法转换则保留原值 df[col] df[col].apply(convert_seat) # 5. 计算出发和到达的小时数用于分析高峰时段 df[出发小时] pd.to_datetime(df[出发时间], format%H:%M, errorscoerce).dt.hour df[到达小时] pd.to_datetime(df[到达时间], format%H:%M, errorscoerce).dt.hour # 6. 按出发时间排序 df df.sort_values(by出发时间).reset_index(dropTrue) return df清洗逻辑详解映射车站名使用.map()方法将电报码替换为中文站名使数据更易读。历时转换将“11:26”的字符串转换为整数分钟686便于后续进行数值比较和计算如找出最快车次。余票统一这是一个典型的数据规整操作。将各种表示“无票”的形式空字符串、‘无’统一为数字0将数字字符串转为整数。对于“有”我们简单处理为1在实际分析中你可能需要更精确的信息但这需要更复杂的接口或解析。提取时间特征从出发/到达时间中提取小时数可以用来分析哪个时间段的车次最多。排序按出发时间排序符合用户的查看习惯。5.2 数据存储与导出清洗好的数据我们可以保存下来供日后分析或与其他数据结合。def save_to_excel(df, filenametrain_tickets.xlsx): 将DataFrame保存为Excel文件 :param df: 清洗后的DataFrame :param filename: 输出文件名 if df.empty: print(数据为空不执行保存操作。) return try: # 使用openpyxl引擎写入.xlsx文件 with pd.ExcelWriter(filename, engineopenpyxl) as writer: df.to_excel(writer, indexFalse, sheet_name车次信息) print(f数据已成功保存到 {filename}) except Exception as e: print(f保存文件时出错: {e}) def save_to_csv(df, filenametrain_tickets.csv): 将DataFrame保存为CSV文件 :param df: 清洗后的DataFrame :param filename: 输出文件名 if df.empty: print(数据为空不执行保存操作。) return try: df.to_csv(filename, indexFalse, encodingutf-8-sig) # utf-8-sig解决Excel打开中文乱码 print(f数据已成功保存到 {filename}) except Exception as e: print(f保存文件时出错: {e})格式选择Excel适合需要手动查看、简单筛选和做图表的场景。openpyxl引擎是处理.xlsx的标配。CSV一种通用、轻量的格式可以被几乎所有数据处理工具读取。用utf-8-sig编码可以确保在Windows Excel中打开时中文不乱码。数据库如果数据量很大或需要频繁查询可以存入SQLite、MySQL等数据库。这里不展开但用pandas的df.to_sql()方法可以轻松实现。6. 整合与执行完成主程序现在我们把所有函数串联起来形成一个完整的脚本。def main(): 主函数协调整个查询、解析、处理、保存流程 # 用户输入未来可以改为命令行参数或GUI输入 from_station 北京 to_station 上海 train_date 2023-10-01 # 请使用未来的日期进行测试 print(f正在查询 {from_station} - {to_station} 在 {train_date} 的车次信息...) # 1. 查询原始数据 raw_data query_tickets(from_station, to_station, train_date) if not raw_data: print(查询失败程序退出。) return # 2. 解析数据 parsed_list parse_ticket_data(raw_data) if not parsed_list: print(解析失败或未找到车次信息。) return print(f共找到 {len(parsed_list)} 个车次。) # 3. 数据清洗与处理 cleaned_df clean_and_process_data(parsed_list, station_code_map) # 4. 显示前几行数据 print(\n清洗后的数据预览) print(cleaned_df.head(10).to_string()) # 打印前10行 # 5. 数据存储 save_to_excel(cleaned_df, f{from_station}_{to_station}_{train_date}_车次.xlsx) save_to_csv(cleaned_df, f{from_station}_{to_station}_{train_date}_车次.csv) # 6. 可选简单数据分析示例 print(\n 简单数据分析 ) if not cleaned_df.empty: # 最快车次历时最短 fastest cleaned_df.loc[cleaned_df[历时(分钟)].idxmin()] print(f最快车次: {fastest[车次]} 出发时间 {fastest[出发时间]}, 历时 {fastest[历时]} ({fastest[历时(分钟)]}分钟)) # 最晚出发的车次 latest_dep cleaned_df.loc[cleaned_df[出发小时].idxmax()] print(f最晚出发车次: {latest_dep[车次]} 出发时间 {latest_dep[出发时间]}) # 有余票的车次数量这里以二等座为例 available_trains cleaned_df[cleaned_df[二等座] 0] print(f二等座有余票的车次数量: {len(available_trains)}) # 出发时间分布 print(f\n出发时间分布小时:) print(cleaned_df[出发小时].value_counts().sort_index()) if __name__ __main__: main() # 礼貌性延迟避免短时间内多次运行脚本对服务器造成压力 time.sleep(random.uniform(2, 5))执行流程用户输入查询条件目前写死在代码里可优化为输入。调用query_tickets获取原始JSON。调用parse_ticket_data解析出车次列表。调用clean_and_process_data进行深度清洗和特征提取。将结果保存为Excel和CSV。进行一些简单的即时分析并打印结果。运行这个脚本你就能在脚本同级目录下得到包含所有车次信息的Excel和CSV文件了。7. 常见问题、反爬策略与优化方向在实际操作中你几乎一定会遇到问题。下面是我踩过的一些坑和解决方案。7.1 常见问题排查表问题现象可能原因排查与解决思路请求返回403 Forbidden1. 请求头不完整缺少User-Agent或Referer。2. IP被暂时限制。1. 检查并补全headers特别是User-Agent和Referer可以从浏览器开发者工具中直接复制。2. 添加随机延迟 (time.sleep(random.uniform(3, 8)))更换网络环境如切换手机热点测试。返回的数据是乱码或非JSON1. 接口地址或参数错误返回了错误页面如HTML。2. 编码问题。1. 确认接口URL和参数是否正确特别是车站编码和日期格式。务必重新抓包验证。2. 检查response.encoding尝试response.json()前先response.encoding utf-8。result列表为空1. 查询日期没有车次如已过期。2. 车站编码错误。3. 接口已更新旧参数失效。1. 检查日期是否为未来日期且格式正确。2. 核对from_station_code和to_station_code是否正确。3.重新抓包确认当前有效的接口和参数。解析时IndexErrorfield_mapping定义的字段顺序与接口返回的实际字段顺序不符。这是最可能出现的问题。打印出fields列表的长度和内容与页面显示的信息逐个对比重新确定并更新field_mapping。保存Excel时出错1. 未安装openpyxl。2. 文件被其他程序打开。3. 路径权限问题。1. 运行pip install openpyxl。2. 关闭已打开的Excel文件。3. 尝试换一个目录或文件名保存。7.2 应对反爬虫策略12306作为重要网站反爬措施会不断加强。除了控制频率还可能遇到Cookie/Session验证首次访问可能需要获取一个初始Cookie。解决方案在首次请求查询页面init时获取Cookie并在后续查询请求中携带。使用requests.Session()对象可以自动管理Cookie。session requests.Session() session.headers.update(headers) # 先访问一下init页面 init_url https://kyfw.12306.cn/otn/leftTicket/init session.get(init_url) # 然后用session进行查询 response session.get(query_url, paramsparams)IP限制短时间内同一IP请求过多会被封。对于学习项目严格遵守延迟即可。如果必须大量采集需要考虑使用代理IP池但这超出了学习范畴且请务必在法律和道德允许范围内进行。参数加密未来接口参数可能会被加密。这就需要更复杂的逆向工程分析JavaScript代码找到加密算法并用Python重现。这是高级爬虫工程师的工作。7.3 项目优化与扩展方向这个基础版本可以沿多个方向深化自动化车站编码获取写一个函数从12306的某个静态资源或接口中抓取最新的车站编码映射表并保存为本地JSON文件避免硬编码。图形用户界面使用tkinter、PyQt或streamlit库制作一个简单的桌面或Web界面让输入和结果展示更友好。多日期/多车次查询封装函数支持循环查询多个日期的车票用于分析票价趋势。票价分析如果接口返回票价字段可以分析不同车次、不同席别的性价比每公里票价找出最划算的车次。邮件/通知提醒结合定时任务监控特定车次、席别是否有票一旦有票就发送邮件或微信通知需接入通知服务。数据持久化与可视化将多次查询的结果存入数据库使用matplotlib或pyecharts绘制车次时间分布图、余票变化趋势图等。更健壮的异常处理增加重试机制如使用tenacity库对网络波动、临时错误进行自动重试。这个项目就像一把钥匙帮你打开了Python应用于实际数据工作的门。过程中遇到的每一个错误解决的每一个问题都会让你的编程能力实实在在地增长。最重要的是你学会了如何将一个模糊的需求“查车票”分解为具体的技术步骤找接口、模拟请求、解析数据、清洗存储并最终用代码实现。这种解决问题的能力才是学习的核心。
返回列表