汽车票余票监控
两件事:找到可用的余票数据源 → 套用监控程序模板。
一、数据源优先级
| 方案 | 可行性 | 说明 |
|---|---|---|
| 巴士管家 H5 接口 | ✅ 已验证 | 江苏/广东等多省客运企业的底层票务供应商,含真实余票。首选 |
| 官方分销 API(docapi.chebada.com) | ❌ | 需商户 key,个人拿不到 |
| 时刻表类网站(checi.cn / cncn / esk365) | ⚠️ | 只有时刻表和票价,没有余票,仅可用于确认班次存在 |
| 携程 / 同程 / 飞猪 | ⚠️ | 有数据但签名复杂、风控严,优先级低于巴士管家 |
确认班次是否真实存在时,可先搜时刻表站点交叉验证发车站名与发车时刻
(注意不同站点数据源可能给出不同时刻,以售票平台为准)。
二、巴士管家余票接口(已实测)
调用步骤
# 1) 先访问首页拿 Cookie(CBDRefId / CBD_SID),否则可能返回不可售
GET https://m.chebada.com/cbdbus/
UA: iPhone Safari 的移动端 UA
# 2) 查询班次
POST https://m.chebada.com/cbdbus/request/Schedule/Schedules
Content-Type: application/json
body: {"departure":"南京","destination":"唐洋",
"departureDate":"2026-10-02","projectType":1}
关键坑
- 参数名是
departure/destination/departureDate。
写成dptCity/arrCity/dptDate会返回rspCode:1000, rspDesc:"出发地为空"。
接口会返回带中文的校验错误,顺着错误提示迭代参数名是最快的办法。 - 响应是 gzip 压缩,必须解压再
json.loads。 - 城市名用售票系统的叫法:
南京而非南京市;目的地用县级/镇级地名(如唐洋)。 - 判定成功:
response.header.rspCode == "0"。 - 同一辆车在不同上车站点会各返回一条记录(如南京汽车客运站 08:00、吉祥庵公交站台 08:10、
南京葛塘站 08:40 是同一车次 DZ6215),ticketLeft是共享的座位池。
筛选目标班次要用「发车站关键字 + 发车时刻」联合匹配。
响应字段(response.body.scheduleList[])
| 字段 | 含义 |
|---|---|
coachNo |
车次号,如 DZ6215 |
dptStation / arrStation |
发车站 / 到达站 |
dptDatetime / dptTime |
完整发车时间 / 08:40 |
ticketLeft |
余票数量 |
canBooking |
1 可订 / 0 不可订(售罄) |
ticketPrice |
票价 |
runThrough |
途经/终点备注 |
其他可复用端点
/Schedule/Departures 出发城市列表(含站点)
/Schedule/Destinations 到达城市列表
/Schedule/GetPartnerSchedule 合作方班次
/Schedule/GetBusSaleDay 可售日期
均为 POST https://m.chebada.com/cbdbus/request + 上述路径,body 用 {"departure":..., ...}。
三、监控程序模板
直接复用现成实现,不要重写:
C:\Users\lychee\WorkBuddy\2026-09-24-15-29-02\bus-ticket-monitor\
纯标准库、零依赖,结构:
run.py CLI 入口(--once / --list / --test-notify / --status / --mock
/ --interval / --threshold / --duration / --max-checks)
config.json 监控目标 + 轮询 + 告警 + 通知通道配置
install_task.ps1 注册 Windows 计划任务(pythonw + --once,无黑窗口)
uninstall_task.ps1 卸载计划任务
btms/models.py 班次条目 ScheduleEntry / 快照 Snapshot
btms/sources.py 数据源适配器(ChebadaSource / MockSource)+ 重试
btms/store.py state.json 持久化,支撑跨重启的变更比对与告警冷却
btms/notifiers.py console / desktop / wecom / file / webhook / smtp / command
btms/monitor.py 变更检测 detect_changes + 告警策略 AlertPolicy + 主循环
适配新线路只改 config.json:
"route": {
"from_city": "南京", "to_city": "唐洋", "date": "2026-10-02",
"filter": { "departure_station_keyword": "葛塘",
"departure_time": "08:40", "coach_no": "" }
},
"alert": { "low_stock_threshold": 10 }
改完先跑 python run.py --list 核对筛选条件是否命中(输出里带 ★ 的就是命中行)。
三·补、通知通道怎么选
| 通道 | 适用 | 备注 |
|---|---|---|
console / file |
始终开启 | 留痕、排查用 |
desktop |
人在电脑前 | Windows 气泡 + winsound 提示音 |
wecom |
手机推送首选 | 走本机已授权的 wecom-cli,无需申请 Webhook |
webhook |
备选手机推送 | 企业微信/钉钉/飞书群机器人、Server酱、Bark |
smtp |
邮件留档 | — |
企业微信 wecom 通道的三个坑
- 不要用 npm 生成的
wecom-cli.cmd。它依赖 PATH 里的 node,
在计划任务环境下会报「系统找不到指定的文件」。优先用原生可执行文件:
<USERPROFILE>\.workbuddy\binaries\node\cli-connector-packages\node_modules
\@wecom\cli\node_modules\@wecom\cli-win32-x64\bin\wecom-cli.exe
注意是 @wecom\cli\node_modules\@wecom\... 双层嵌套,glob **/wecom-cli.exe 找最稳。
- 发送命令形态:
bash
wecom-cli message aibot send --json '{"chat_id":"...","msg_type":"markdown",
"markdown":{"content":"..."}}'
chat_id 来自 wecom-cli identity whoami(解析「授权真人用户身份…ID:」),
或 wecom-cli message aibot sessions list 按 chat_name 精确匹配群聊后原样复制。
errcode 850002= 机器人未开通「消息」权限。此时 CLI 返回的help_message
含授权链接,必须逐字原样转达给用户(CLI 明确要求,不得改写/省略 URL)。
程序侧要把errcode/errmsg/help_message从响应体里解析出来抛出,
并做失败退避,否则每轮都会往日志里刷一大段重复错误。
> 前置检查顺序:wecom-cli --version(≥1.2.1)→ wecom-cli auth show --status(需 authorized)
> → 才可调用业务命令。相关技能:wecomcli-shared、wecomcli-message。
三·补2、部署为 Windows 计划任务
已产出可复用的 install_task.ps1 / uninstall_task.ps1(pythonw.exe "run.py" --once,
每 5 分钟一次,无黑窗口、不累积常驻进程)。注册时的四个坑:
New-ScheduledTaskTrigger -RepetitionDuration ([TimeSpan]::MaxValue)
会生成P99999999DT23H59M59S,超出任务计划程序上限,报HRESULT 0x80041318。
✅ 改用(New-TimeSpan -Days 3650)。- 本机 ExecutionPolicy 常为
Restricted,直接& script.ps1会被拦。
✅ 用powershell -NoProfile -ExecutionPolicy Bypass -File xxx.ps1。 - 内层脚本和外层
*>>重定向不能写同一个日志文件,会 FileOpenFailure。 - 含中文的
.ps1必须存成 UTF-8 with BOM(encoding='utf-8-sig'),
否则 PowerShell 5.1 按 ANSI 读会乱码。
验证:Get-ScheduledTaskInfo -TaskName '<name>',LastTaskResult = 0 即成功。
> 本机 PowerShell 工具的 stdout 不返回内容,所有输出都要重定向到文件再读。
四、逆向 H5 应用找接口的通用方法
复用这套流程处理同类需求:
- 用移动端 UA
GET目标 H5 首页,把 HTML 存下来。 - 正则抓出
<script src>里的 JS bundle,用curl --compressed下载
(Python urllib 直接读常报IncompleteRead,改用 curl 更稳)。 - 在 bundle 里搜索这些关键词定位请求封装与接口路径:
baseUrl、axios.create、requestUrl、apiUrl、/api、serviceName、
window.App(SSR 注水的运行时状态,常含 sessionId / authCode / 接口前缀)。 - 注意路由懒加载:真正页面级接口常在
n.e(<chunkId>).then(...)加载的分包里,
首页 bundle 里只有路由表。按 chunk 编号拼出/assets/<name>.<hash>.chunk.js再下。 - 提取到路径后直接实测:先 GET 首页建 Cookie,再 POST 试。
服务端的中文校验错误("出发地为空")就是最好的参数名线索,迭代几次即可。