
春节票难抢这件事每年都在上演。手机屏幕戳到冒火星、多平台来回切最后还是败给手速和网络延迟。今年我试了一个不一样的方案把手头这台带 AI 能力的智能眼镜当作出行助手让它替我盯着余票和发车时间到点直接语音提醒我。这篇文章就是基于“灵珠AI”的春节出行助手开发全过程从需求拆解、技能设计到后端轮询再到实际使用中的几个坑一次性说清楚。适合正在做智能语音设备技能开发、或者想给 AI 眼镜这类硬件加落地功能的朋友参考也顺便给那些准备上手 AI 硬件开发的同学一个完整思路。先说结论如果只是想“用脚本抢票”这篇文章帮不到你也不建议你那么做。我做的这套东西本质上是一个“余票监控 行程提醒”助手把查询动作拆成语音交互把等待过程交给程序最后在真正需要你做决定的时候用语音把信息推到你的耳边。整个过程没有碰任何平台的登录态和下单接口合规、稳定也不会给账号带来风险。1. 为什么想到用灵珠AI做春节出行助手1.1 抢票这件事痛点在“盯”而不是“买”每年春运抢票最折磨人的其实不是最后点击那一瞬间而是之前漫长的等待和反复刷新。我统计过自己这几年的操作路径提前查好车次定好闹钟到点前五分钟就捧着手机不停下拉刷新还得准备两三个备选方案。即便如此遇到余票放出的时间点不固定或者某段路线临时加车所有准备都白费。回头想这里面的核心矛盾是官方放票时间不定人在电脑前等着效率太低但远程刷票又涉及账号安全和平台规则。能不能换一种思路——我不亲自盯着而是让一个带语音的设备在后台监控有余票变化就第一时间通知我。这正是灵珠AI这类智能眼镜的用武之地它有麦克风、扬声器能理解自然语言还能挂后台服务。最关键的是它戴在头上我在做别的事情的时候也能听到提醒。1.2 灵珠AI什么样的能力支撑这个场景灵珠AI我这里只是代指手头这台智能眼镜的语音助手有几个特点决定了它适合做这件事。第一它有独立的语音交互链路不需要我掏出手机解锁、打开App喊一声就能启动技能第二它本身接入了大模型能力能处理相对复杂的自然语言表达而不是只能听懂“查询车票”这种固定指令第三它支持自定义技能或后端回调这给了开发者很大的发挥空间。我选择的开发路径是设备端只负责语音输入和播放结果真正干活的是一个自己部署的后端服务。用户对灵珠AI说“帮我查一下腊月二十八从A市回B市的票”设备把语音转成文字传给技能解析抽取出目的地、日期然后调用我的后端服务去查余票最后把结果合成语音播报出来。这个模型是典型的“语音交互 业务API”架构实现起来不复杂但每一个环节都有值得抠的细节。1.3 出行助手的定位不做代抢只做“提醒与决策辅助”这个定位一开始就要想清楚。市场上很多抢票工具之所以不稳定是因为它们尝试模拟人的操作去高频请求接口甚至绕过验证码。这既违反平台规则也容易导致账号风控。所以我的项目刻意把边界划得很清楚后端只做低频余票查询不做自动提交订单发现目标车次出现余票时立即通过灵珠AI播报同时推送一条包含官方购票入口的提醒最终下单动作由我本人在手机上完成。这样设计的好处是第一开发复杂度大幅降低不需要处理登录、验证码、支付等敏感环节第二合规风险小即使查询频率稍高也在合理范围内第三用户体验反而更好因为最后一步由人决策能处理改签、候补、中转等复杂情况。如果你也想做类似的东西建议先把这条线划出来不然很容易滑向灰色地带。2. 出行助手的总体架构与关键设计2.1 四层架构每一层各司其职整个系统我分成四层设备交互层灵珠AI眼镜负责唤醒、收音、语音转文字ASR、文字转语音TTS。这一层我基本没动用的是平台自带能力。灵珠AI支持自定义技能我需要做的是在技能配置里指定一个服务端地址当用户说出特定指令时平台把识别出的文本POST到我的服务。语义理解层这部分借助灵珠AI内置的大模型。平台允许我在技能里配置“系统提示词”和“意图槽位”它会自动从用户的话里抽取结构化信息。例如用户说“大年初四A市到B市有票吗”模型能返回出发地A市、目的地B市、日期大年初四。这比自己写正则再维护词典省太多事。业务逻辑层一个FastAPI服务负责接收技能请求、调用余票查询、管理预约任务、触发提醒。这里最核心的是“预约监控”状态机我下面单独说。数据与第三方接口层余票信息从官方售票系统的公开查询接口获取班车时刻表入库做离线校准另外存一份用户常用路线配置方便快速发起查询。2.2 为什么把语义理解交给大模型而不是自己写NLU在灵珠AI出现之前我也做过一些语音技能当时还是要自己去定义“槽位”和“词典”类似“城市北京/上海/广州”然后加一堆同义词。碰到“魔都”这种外号或者“腊月二十八”这种农历日期规则几乎无能为力。这次直接用大模型做意图理解和槽位填充只需要在提示词里写清楚“提取哪些字段、字段的取值范围、拿不到时返回什么”效果比我预期好很多。关键点在于提示词要写得像一份“任务说明书”而不是一句废话。我最后用的系统提示词大致是你是出行助手。用户会说出出行需求请提取以下JSON字段 - from_city: 出发城市支持简称和别称 - to_city: 目的城市 - date: 出行日期注意农历说法要转换成公历使用YYYY-MM-DD格式 - train_no: 可选用户指定的车次号如G1234 如果没有某项字段值设为null。 只输出JSON不要额外说明。实际测试中这个提示词能处理大部分说法。比如“帮我看看除夕当天从A市到B市还有没有高铁”它会返回日期为春节当天的公历日期出发地和目的地也识别正确。接入大模型还有一个好处用户的表达可以非常口语化比如“年前两天走”、“下午三点多的车”模型也能大致给出一个时间范围供后端兜底。2.3 核心状态机预约、监控、提醒、完成出行助手的核心不是“查一次票”而是一个持续的“预约监控”流程。用户可能说“帮我盯一下正月初五从A市到B市的票有票了喊我”这句话表达的是一个长期任务。我定义了几个状态待预约用户表达了需求但还没最终确认日期和车次监控中后端定时查询符合条件时进入提醒前置状态提醒中已经发出语音提醒等待用户回应已完成用户表示买到票了或者主动取消任务已过期发车时间已过自动结束状态流转都在数据库里记录每次查询后根据余票数量和时间更新状态。这里遇到的一个设计问题是什么时候算“提醒过了”我的处理是只要余票数量从0变成大于0就触发提醒并把“已提醒”标记记下来避免同一个车次反复播报。如果用户没回应则隔一段时间再次提醒最多三次。3. 从零搭一个“语音查票”技能意图、槽位与对话流设计3.1 确定意图列表不贪多技能名称我直接叫“出行助手”。第一次设计意图时我列了五六个后来砍到三个查余票query_ticket、盯票watch_ticket、取消盯票cancel_watch。为什么要砍因为语音交互最忌讳层级深和选项多用户不会记得那么多指令只要覆盖核心场景就够了。“查余票”回答当下有没有票“盯票”开启监控“取消盯票”结束任务。这三个意图恰好对应查询、创建任务、删除任务逻辑闭环。每个意图在平台上配置对应的示例语句比如查余票今天A市到B市有票吗 / 帮我看看明天的高铁 / G2345还有票吗盯票帮我盯着后天A市到B市的票 / 有票了提醒我 / 持续监控这一班车取消盯票别盯了 / 取消提醒 / 删除我的监控任务示例语句不需要特别多因为大模型能举一反三。但每个意图至少要给四个角度不同的例子帮助模型理解说话方式。3.2 槽位设计核心是“日期”和“城市”的归一化槽位是最终从语音文本中提取的参数。我定义了 from_city、to_city、date、train_no、passenger_count 五个核心槽位。其中两个槽位有比较麻烦的归一化问题。城市槽位用户可能说“A市”“A城”也可能说“市里”“老家”甚至直接说一个区的名字。我的办法是维护一张别名映射表放在后端服务里当大模型返回的城市名在库里找不到时就匹配别名表。同时把返回结果再回问给用户确认“你是指A市对吧”这个确认动作虽然多一轮交互但能避免订错日期后才发现目的地不对。日期槽位灵珠AI的模型虽然能理解“除夕”“大年初二”但返回的格式不一定稳定。我在提示词里明确要求输出YYYY-MM-DD并且后端做了二次校验如果解析出的日期早于今天自动置为明年的对应日期。农历转公历我是用了一个现成的库网上很容易找到算法无非是查天文年历不需要自己造轮子。3.3 多轮对话的“上下文”怎么处理刚开始我踩了一个坑用户说“帮我盯后天的票”我答“好的请问从哪里到哪里”用户补充“A市到B市”。问题出现了——第二句没有“盯票”这个意图触发词平台会把它当成新的指令而不是补充信息。这就是上下文关联的问题。灵珠AI技能本身有会话管理能力但默认的上下文保留时间很短。我的做法是把未完成的预约草稿存在后端从设备端收到第一句带“订票”意图的话时就创建一个 task_draft往后用户在会话里的所有文本都先尝试填充这个草稿。如果一句话里没有明确的意图词就根据当前草稿状态让它作为补充信息。这个逻辑我用一个本地字典维护字段齐全后再让灵珠AI回问确认。3.4 对话确认脚本避免信息错漏确认这一步不能省。我设计的确认语句模板是你盯的是{depart_date}从{from_city}到{to_city}的{train_no}次列车 如果出现余票我会第一时间叫你。确认请说“好的”。如果用户回答“好的”任务正式进入监控状态。如果用户说“不是日期改了”则重新进入槽位填充流程。语音确认比屏幕确认更必要因为语音交互没有“看到”的过程说错了用户可能没注意确认一遍能拦截大多数错误。4. 后端服务接入余票查询与订阅通知的实现细节4.1 余票查询接口的合理封装这一步是整个项目里最需要注意“度”的地方。官方售票系统有公开的余票查询接口但没有任何公开承诺允许高频访问。我在封装时做了三层保护第一查询频率控制在每30秒一次绝不使用并发请求第二请求之间加随机抖动避免形成固定访问模式第三设置了超时和异常重试机制如果连续多次请求失败自动暂停该任务并推送告警。封装后的接口大概是def query_left_tickets(from_station_code: str, to_station_code: str, depart_date: str, train_no: str None) - list[dict]: params { from: from_station_code, to: to_station_code, date: depart_date, train_no: train_no, } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) } resp requests.get(TICKET_QUERY_URL, paramsparams, headersheaders, timeout5) resp.raise_for_status() return parse_ticket_list(resp.json())这里的 from_station_code 不是城市名而是站点代码。前期需要维护一张“城市-主要站点”的映射表比如A市对应两个高铁站用户说“A市”时需要同时查两个站的余票再合并结果。这块的细节直接决定查询结果准不准。4.2 数据库设计一张任务表 一张日志表我用了SQLite单文件跑在云服务器上就够了。任务表主要字段id任务编号user_open_id用户在灵珠AI设备上的标识from_city_name / to_city_name城市中文名depart_date出发日期train_no可选车次号status状态枚举remind_count已提醒次数created_at / updated_at时间戳日志表则记录每次查询结果包括余票数量、车次、时间方便事后分析。不要觉得日志不重要我曾靠日志发现某个车次每天都只在整点放票后来就专门增加了整点附近的查询权重。创建任务的代码大致是这样INSERT INTO watch_task (user_open_id, from_city_name, to_city_name, depart_date, train_no, status) VALUES (?, ?, ?, ?, ?, monitoring);状态变更统一通过一个 update_status 函数操作每次都刷新 updated_at。SQLite虽然轻量但要注意并发写问题解决办法是让所有写操作都走同一个连接队列或者在每次写入时用短事务。4.3 定时轮询与状态机调度定时任务我用了APScheduler因为它在进程内就能管理多个定时器适合这种轻量级场景。每个监控任务被注册为一个 CronTrigger间隔设32秒执行时先检查当前时间是否在任务有效期再调更新状态。核心调度逻辑def poll_watch_task(task_id: int): task get_task(task_id) if task.status ! monitoring: return tickets query_left_tickets(task.from_station_code, task.to_station_code, task.depart_date, task.train_no) has_ticket any(t[quantity] 0 for t in tickets) if has_ticket and task.remind_count 0: send_voice_notification(task, tickets) increment_remind_count(task) elif not has_ticket: # 余票为0继续监控 pass如果用户对多个车次都点了监控就可以并行查询但我没有用多线程而是用异步IO方式同时发多个请求避免线程上下文切换开销。要注意的是异步请求不能配那种长时间无响应的接口所以 requests 换成 aiohttp超时时间统一为5秒。实测下来同时监控5个任务CPU占用几乎可以忽略网络IO是唯一成本。4.4 语音通知怎么推给灵珠AI灵珠AI的推送接口接收一段文本设备会TTS合成语音播报。我的提醒文案是这样设计的盯票提醒你关注的G9999次列车A市到B市出现余票3张 现在可以打开官方售票系统购买。如果买到了说“好的已购买” 否则我会再提醒。这里用了一个技巧提醒文案里带“如果买到了说...”这种指令引导用户给出反馈。用户反馈会通过语音识别再次进入技能服务如果匹配到“已购买”就把任务状态改成 completed。这形成一个完整的闭环创建→监控→提醒→反馈→关闭。不过要注意提醒不能太频繁。余票数量可能刚出现又瞬间被抢光如果每次都播报一遍用户会烦死。我只在“0到有”的跳变时提醒同一车次最多提醒三次三次之后自动把原任务标记为 expired除非用户重新开启。4.5 关于合规和封禁风险多说几句前面说了我不做自动下单但即便只是查询余票也有触发风控的可能。一个容易忽略的细节是请求频率的“节奏感”。像固定30秒一次看起来无害但对某些反爬策略来说还是太规律。我加了一个 ±8 秒的随机数让请求时间均匀分布在一个范围内。另外每天凌晨1点到5点官方系统通常维护这段时间我会直接停掉轮询任务既省资源也降低风险。还有一个建议不要把查询服务部署在和你的常用设备同一IP段下更不要用家庭宽带IP高频访问。我在一台低配云主机上跑出口IP是固定的数据中心IP如果请求量过大一样容易触发验证码。所以我把请求量控制在每天每任务不超过3000次单人使用的场景完全够用。5. 开发过程中踩过的坑与对应解法5.1 语音识别把“G1234”听成了“鸡1234”这是最搞笑也最头疼的坑。灵珠AI的ASR默认对英文和数字混合序列处理不够好“G1234”经常识别成“鸡1234”“ji1234”或者“G幺二三四”。车次号一旦错了查询结果自然为零。解决办法分三层。第一层在后端维护一个车次候选词典把常见的“G/D/C”开头车次结合出发城市和目的城市的所有车次都拉出来识别结果做模糊匹配如果文本里没有合法字母开头就尝试匹配“鸡/记/及/几”等谐音再替换成G。第二层在解析时使用正则import re def normalize_train_no(raw_text: str) - str | None: m re.search(r([GgDdCc]\s*[0-9]{1,4}), raw_text) if m: return m.group(0).replace( , ).upper() # 处理“鸡幺二三四”这种数字读法 chinese_digit_map {幺: 1, 两: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9, 零: 0} for k, v in chinese_digit_map.items(): raw_text raw_text.replace(k, v) m2 re.search(r[GgDdCc]\s*[0-9]{1,4}, raw_text) return m2.group(0).replace( , ).upper() if m2 else None第三层是保险措施当用户提到车次号时我回问确认“你确认是G1234次吗”这一步能解决绝大多数识别错位问题。其实用户被问一次并不会烦毕竟查错车次才更烦。5.2 “腊月二十八”这种农历日期转公历要格外小心用户的春节出行需求几乎都在农历语境里表达而官方售票系统只认公历。且“腊月二十八”对应到公历可能发生在明年1月底或2月初不同年份差很远。如果解析错一天余票查询就是白忙。我用的是一个农历算法库第一次测感觉没问题但后来发现一个边界用户说“大年三十”时有的年份农历十二月只有二十九天大年三十实际上对应的还是除夕但公历转换出来却是第二年的正月初一。这个坑藏得特别深因为从“腊月三十”的口语来说用户自己可能也不太区分。我在代码里显式处理了如果用户说“除夕”“大年三十”一律取农历年的最后一天而不是简单的“腊月三十”。另一个坑是“明年春节”。比如当前是公历2025年1月但农历还是腊月用户说“明年春节”指的是2026年的春节而不是2025年已经过去的春节。如果直接用默认年份就会得到已经过期的日期。我的办法是把所有农历日期先转换为公历再比较是否早于今天如果早于今天就自动加一年或减一年具体方向通过附带“明年/今年/去年”这种词来判定。5.3 轮询任务越来越多提醒开始延迟刚开始每个任务单独一个定时器跑着跑着发现任务多了之后提醒延迟从几秒变成十几秒。原因是APScheduler默认的线程池太小加上同步IO阻塞了后续事件。后来我改成异步任务队列每个任务不再是独立定时器而是一条消息投递到队列由一个协成消费者统一处理。同时把线程池大小调到20任务之间的并发度用信号量限制在5以内。经过这一改提醒延迟基本控制在2秒以内。这里也说明一个道理智能硬件技能的瓶颈常常不在设备端而在后端的调度模型。任务量不大时怎么都行任务量一上来很快就能测出性能瓶颈。5.4 会话挂起用户沉默两分钟后再说话技能就丢了上下文灵珠AI的会话默认超时时间不长。用户和我确认了监控任务后如果超过一两分钟没有再说话再去问“现在余票怎么样”技能服务端接收到的上下文里已经没有之前的信息。我在后端给每个用户单独维护了一个“最近会话对象”保存最近一次成功创建的查询条件和状态。当新来的一句话里没有明确意图但带有“查一下”“现在呢”这类模糊词时就会自动使用最近会话对象里的参数补全。如果连补全参数都没有才要求用户重新说一遍。这个逻辑类似浏览器的表单自动填充对语音交互尤其有用因为用户默认“你应该记得我刚才说什么”。5.5 城市名“A市东站”和“A市南站”的歧义实际查询中用户说“A市”可能指的是市中心那个站也可能是指东边的枢纽站。如果只查一个站很容易漏掉更优车次。我建立了一个“城市-站点组”的数据结构。比如A市包含“A市站”和“A市东站”B市包含“B市南站”。用户指定城市后查询所有站点组合再把结果整合到一张表里。虽然请求数增加了但只在用户第一次查询时做结果缓存10分钟负担可控。如果用户明确说了“东站”那就不做泛化直接查该站。判断依据很简单语音文本里出现“站”字或是提示词解析时指定了车站字段。这个细节属于那种“不测不知道测了才发现影响巨大”的点毕竟除夕前的票同一城市不同站点的余票规律完全不同。6. 实测效果与整体使用感受6.1 从“模拟抢票”到真实监测我都试了一遍为了验证整个流程我先用了一个虚构场景让灵珠AI帮我盯“腊月二十八从A市到B市的G9999次列车”。由于这个车次在真实线路里不存在后台查询肯定一直无票但正好可以测试提醒是否能在余票从0变成1时触发。我用接口模拟了一条数据把G9999的余票数量改成1十几秒内灵珠AI就播报“盯票提醒你关注的G9999次列车出现余票1张请前往官方售票系统购买。”整个链路跑通的那一瞬间还是很激动的。当天我又测试了多个车次同时监控语音反馈都正常。之后切回真实路线做了实际查询虽然没等到余票跳变但查询准确率和响应速度都符合预期。还有一次真实使用是准备去车站接人我说“帮我看看下午从B市到A市的高铁到站时间”它直接读出了三个车次的到达时刻比用手机查还快。这个场景虽然和抢票无关但是说明出行助手的定位可以延伸不只盯票还可以做实时到发信息提醒。6.2 在嘈杂环境下的语音识别表现灵珠AI的麦克风在安静房间里表现很好但在火车站、地铁里环境噪音对识别率的影响还是很明显。特别是环境音里如果有广播声车次号几乎百分之百出错。我的应对办法是让用户在噪音环境下尽量使用简短指令比如只报“G9999”不要一次性说长句。等后端部署一段时间后可以考虑在设备端做简单的语音活动检测噪声音量超过阈值时自动让TTS播报代替ASR输入但这会让交互变得单向。目前这个版本我还是建议有复杂需求时靠手机端补充输入灵珠AI更多是“通知终端”而非“输入终端”。6.3 整场“抢票季”里它到底帮我抢到票了吗说实话最后我没用这个助手抢到所谓的“硬座票”因为今年我回家改成了拼车。但在帮朋友监测候补票情况时它确实在凌晨一点多提醒过一次“出现余票1张”朋友看到提醒后三分钟内就在官方系统买到了票。那次提醒发生的时候我正在睡觉灵珠AI通过语音把我叫醒我迷迷糊糊爬起来转发消息给朋友整个过程不到一分钟。如果没有这个助手靠人力盯到凌晨一点基本不现实。从这次经历里我得到一个结论这类助手的价值不是“代替你抢”而是“在你不可能一直盯着的时段给你盯”把人的注意力从屏幕中解放出来。有时候余票出现的时间窗口只有几十秒但只要有一个人及时醒着就能抓住机会。语音设备作为提醒出口比手机推送更有效因为它在物理上离你更近。7. 一些经验总结与后续可做的事7.1 对想做相关开发的人几条实在建议第一最开始不要追求功能大而全先把“查询-监控-提醒”这个最小闭环跑通。我第一版只支持单个车次甚至没有取消功能但已经解决了我自己的核心需求。后面再逐步叠加多车次、多站点、日志分析。第二在设计语音对话时一定要先想清楚“失败路径”。用户说了一遍没听清怎么办参数少了两个怎么办系统提醒了但用户没回应怎么办这些问题的答案比“成功路径”更重要。我大概花了三分之一的时间在处理各种边界情况上。第三合理利用平台能力不要重复造轮子。灵珠AI自带的ASR和TTS以及大模型槽位解析省了我大量工作。只有纯粹的业务逻辑才值得自己写比如余票查询、状态机、并发调度。如果在设备端这些能力都从零做这个项目根本不可能在一周内完成。7.2 合规意识的底线还是想再念叨一次做这个项目时我反复提醒自己绝不触碰账号登录、验证码破解、自动下单这类禁区。春节出行是一个人一年中最重要的事情之一如果在“帮人抢票”的名义下做成黑产工具既害人又害己。个人开发者在做类似应用时一定要把“查询辅助”和“代刷代抢”的界限划清楚。我的做法是在技能描述里直接写明“本助手仅提供余票监控和提醒不提供代抢服务最终购票请在官方渠道完成”。这样既是对用户的坦诚也是对自己的保护。如果后续想分享或开源代码这部分更要写清楚避免被人二次包装成违规工具。7.3 这个框架还能扩展到哪里春节出行助手只是这套架构的一个应用场景。把“盯票”换成“盯孩子的课外班名额”把“余票”换成“医院号源”甚至把“查询购票入口”换成“电价低谷提醒”整个技术框架几乎不用改动。核心是一个语音交互入口、一套定时监控服务、一条消息推送通道。我现在已经在改造第二版计划把“常用路线”做成模板比如每天早上自动播报一次未来三天的余票概况或者结合定位自动判断出发城市。还有一个更大的想法是接入日历当检测到我买好一张票后自动把车次、时间、座位号生成行程卡片再通过灵珠AI在出发当天做语音提醒。这些事情本质上都是“数据源 状态机 语音交互”的组合并不需要特别高深的技术但确实能带来实打实的便利。最后分享一个开发过程中的小技巧调试语音技能时不要每次都找设备端折腾灵珠AI的技能后台通常有文本调试入口先在文本模式下把后端逻辑测通再回到语音场景验证ASR和TTS效率能翻一倍。整个项目做下来最深的体会是AI硬件最大的价值不是炫技而是能安静地待在用户身边替用户盯着那些值得盯的事。