黄金周第二天,入境处数字刷屏:首日截至晚上 9 时全港 89.7 万人次出入境,官方预计 129 万内地客陆续到访。所有人都在拍尖沙咀的人山人海——但同一时刻,新界西北的轻铁轨道上是什么光景?我照旧把 68 个站的到站预报扫了一遍:周五上午 10 点半,494 班列车正在网络上朝某站开。比周二上午多 15 班,比周一少 44 班;更值得看的是结构——发车最多的头名换线了,一条从上周就没上过班的线继续缺席,还有一个站连续三次截面空无一班。68 个请求、63 秒扫完,这篇把四次截面摆在一起,说清楚「快照对比」里最容易被忽略的三个变量。
目录
- 一、口径先钉死:494 数的是什么
- 二、头名换线:751 从 82 班的霸主到被反超 7 班
- 三、复扫:25 分钟里供给厚度漂了多少
- 四、快照对比的三个变量
一、口径先钉死:494 数的是什么
老读者知道这个系列的口径:rt.data.gov.hk的轻铁getSchedule接口,零鉴权,按站查询。对 68 个站各请求一次,把每个站台route_list里「3 mins / 即将到站」的记录计数加总。它不是车队规模:同一班车会依次经过多个站,在每一站的预报里各出现一次——所以 494 的真实含义是「班次 × 剩余站数的加总」,衡量的是网络此刻的供给厚度,不是物理列车数。这个口径今天依然成立,四次快照全部可比。
importjson,collectionsdefagg(name):d=json.load(open(f"原始返回/{name}"))assertlen(d)==68,len(d)# 68 站一个不少per_route=collections.Counter(rtforvind.values()forrt,_inv)total=sum(len(v)forvind.values())assertsum(per_route.values())==total# 恒等式:分线计数=总量empty=[kfork,vind.items()ifnotv]returntotal,per_route,empty t_mon,r_mon,e_mon=agg("lrt_census_0907.json")# 09-07 周一 14:24t_tue,r_tue,e_tue=agg("lrt_census_0908_am.json")# 09-08 周二 10:44t_fri,r_fri,e_fri=agg("lrt_census_1002_am.json")# 10-02 周五 10:30assert(t_mon,t_tue)==(538,479)asserte_mon==e_tue==e_fri==["385"]# 备用站三次恒空print(f"周一{t_mon}/ 周二{t_tue}/ 周五{t_fri};恒空站{e_fri}")扫描本身还是老三样:站清单 + 按站接口 + 断点续扫缓存。这次把间隔调到 0.4 秒后,68 个站 63 秒扫完——三个月前第一次扫用了 12 分钟,快的不是网,是重试策略从「遇错傻等」改成了「短退避、失败站补扫」。补一句系列背景:这份 68 站清单是 9 月初从三个来源交叉核对后定稿的,此后没改过一站;三次历史快照用的同一份清单、同一套计数规则,所以 538、479、494 三个数字可以放进同一个坐标系。这也是长系列快照的第一原则——仪器先冻结,再谈观测:清单或参数改一次,整条序列就得作废重录。
二、头名换线:751 从 82 班的霸主到被反超 7 班
周一那次截面,751 一条线 82 班、占全网 15%,无人能撼。两次快照之后,格局变了:
delta={rt:r_fri.get(rt,0)-r_tue.get(rt,0)forrtinset(r_fri)|set(r_tue)}assertr_fri["610"]==73andr_fri["751"]==66# 周五:610 第一assertr_mon["751"]==82# 周一:751 82 班霸主assert"751P"notinr_friand"751P"notinr_mon# 高峰线三次缺席up={rt:dforrt,dindelta.items()ifd>0}print("今日AM vs 周二AM 分线变化:",dict(sorted(delta.items(),key=lambdakv:-kv[1])))610 以 73 班连续第二次坐上头名,对 751 的优势从周二的 2 班拉开到 7 班;751 自己停在 66 班,两期不动。分线看周五上午 vs 周二上午:6 条线回升(610 +5、615 +5、761P +4、505 +4)、3 条持平、2 条回落(507 −3、705 −2)——回升的几乎全是屯门方向的干线,回落的是天水围环线,跟上周二「屯门回落、天水围坚挺」的方向正好对调。轻铁的地理结构在每个上午都不一样,这是排班表读不出来的部分。
另一个连续成立的观察:751P 三次截面全部缺席。它是高峰特别班次,周一下午 2 点不在班、周五上午 10 点半也不在班——不是故障,是时刻表属性。写进文里是想提醒:做快照对比时,「某条线不见了」要先查时刻表定义,再谈异常。
地理上这也不难解释:610 是屯门码头到元朗的干线,751 的主阵地在天水围;上午时段屯门各站的过站车更多,头名自然往干线漂。但把话说满之前要记住——三次截面只是三个点,等这条序列攒到十几个点,「头名轮换」是不是周期现象才值得下判断。
三、复扫:25 分钟里供给厚度漂了多少
单时刻普查最大的软肋是「你扫完的那一刻就过期了」。所以今天在同一上午拍了两张:第一张 10:30,第二张 10:56。两次之间,总量从 494 变到 494——纹丝不动:
t2,r2,e2=agg("lrt_census_1002_pm.json")# 同一上午第二扫assertlen(e2)==1ande2==["385"]assertt2==t_fri==494# 25 分钟总量零漂移drift={rt:r2.get(rt,0)-r_fri.get(rt,0)forrtinset(r2)|set(r_fri)}assertmax(abs(d)fordindrift.values())<=2# 分线抖动全部 ≤2print(f"复扫总量{t2}(第一扫{t_fri},漂移{t2-t_fri:+d})")print("分线漂移:",dict(sorted(drift.items(),key=lambdakv:-kv[1])))总量零漂移,不代表网络静止了:7 条线在 ±2 班内各自抖动(507 +2、705 +1、706 +1,751/610/615/615P 各 −1),68 个站里 37 个站的计数变了,「即将到站」从 14 班变成 16 班。列车在网络上流动,各站计数此消彼长,加总恰好抵消——一次复扫不足以下规律结论(样本只有 1),但它给了一个实打实的验收经验:跨期对比别只盯总量,分线漂移才是检验两次扫描可比性的显微镜;总量巧合地相等时,更要看结构有没有动。
四、快照对比的三个变量
三次同系列扫描做下来,把最容易翻车的三件事钉在这里:
- 星期是隐藏变量。周二 10:44 的 479 和周五 10:29 的 494「同为上午」,但一个是普通工作日、一个是黄金周里的周五——把 15 班的差全部归因给黄金周是过度解读,只能说到「周五上午的供给与周二基本持平、略偏厚」为止。
- 到站预报有截断半径。接口只返回未来若干分钟内的班次,离站太远的车不出现——「供给厚度」的绝对值受这个半径影响,跨期对比必须用同一脚本同一参数。
- 恒空站要先排除再计数。385(Hung Tin Road)是紧急备用站台,三次截面都是 0 班——把它算进「班次异常」就是假警报,68 站的分母要先剔除它再谈覆盖率。
这段对比代码和四次快照的口径说明可以直接搬走,收藏备用;如果这篇帮你省掉一次「快照对比翻车」,收藏+点赞。你做双时点对比时被哪个隐藏变量坑过——星期、时段还是缓存?评论区聊聊。这类「同一脚本第 N 次快照」会持续拍下去,关注不迷路。
参考链接
- https://rt.data.gov.hk/v1/transport/mtr/lrt/getSchedule?station_id=280
- https://www.mtr.com.hk/en/business/partnerships/open_data_index.html