Python 拉取城巴开放数据:406 条路线,香港岛巴士网络的数字画像

文章目录

先说结论

用 Python 请求城巴官方开放数据 JSON(data.gov.hk 发布,零鉴权),把全网 406 条路线号拉下来做分类统计和目的地分析,就能画出一张香港岛巴士网络的数字画像。 城巴 2023 年合并新巴后由单一公司运营,这份 406 条路线的清单,就是今天香港岛巴士网络的完整骨架。

三个最扎眼的数字先放前面: 一是 406 条路线里,130 条常规数字线 + 128 条数字加字母后缀的变体线 (1M、2A、720P 这类)合计占 63.5%------一个线路号背后往往藏着一个"变体家族";二是 中环 Central 是全港第一终点 ,35 条路线以此为终点,比第二名铜锣湾多出 10 条;三是机场线族(A 线 + E 线 + NA 通宵)合计 40 条,是除市区常规线外最大的功能集群。

做法只有四步fetch_json() 带重试地拉路线列表,classify() 用正则按字母模式分类,dest_area() 从目的地字符串里提取区域名,最后 Counter 统计 + matplotlib 出图。全程只用 Python 标准库加 matplotlib,任何开放数据都能套同一套流程。

一、数据源与口径:为什么 406 就是 406

城巴把路线、站点、实时到站三组数据挂在 data.gov.hk 上,API 基址是 https://rt.data.gov.hk/v2/transport/citybus/,零鉴权,加个 Accept: application/json 头就能拿。本文只用其中两组:

  • route/CTB:全部路线列表(起点、终点、中英文名)
  • route-stop/CTB/{route}/{direction}:某条路线某个方向的站点序列

这里有个容易数错的口径route/CTB 返回的列表里,每条路线号只有一行 ------它不是"去程一行、回程一行"的双向列表。方向是靠请求参数 direction 指定的(取值只有 inbound / outbound)。所以 406 行就是 406 条路线号,不是 812 个方向。把这个数错,后面所有占比全错。

406 行 = 406 条路线号(官方口径,非双向行数),数错一条整篇分析就站不住。

二、核心代码:拉取、分类、提取、统计

python 复制代码
import json
import re
import urllib.request
from collections import Counter

ROUTE_URL = "https://rt.data.gov.hk/v2/transport/citybus/route/CTB"

def fetch_json(url, retries=3):
    """拉取 JSON,带重试:城巴 API 偶发超时/空响应"""
    for attempt in range(retries):
        try:
            req = urllib.request.Request(url, headers={"Accept": "application/json"})
            with urllib.request.urlopen(req, timeout=30) as resp:
                return json.loads(resp.read().decode("utf-8"))
        except Exception:
            if attempt == retries - 1:
                raise
    raise RuntimeError("unreachable")

def classify(route_no):
    """按字母模式给路线分类:A/E/N/R/S 前缀、X 后缀、数字+字母、纯数字"""
    r = route_no.upper()
    if r.startswith("NA"):  return "Airport overnight NA"
    if r.startswith("A"):   return "Airport A-lines"
    if r.startswith("E"):   return "Airport logistics E-lines"
    if r.startswith("N"):   return "Overnight N-lines"
    if r.startswith("R"):   return "Event R-lines"
    if r.startswith("S"):   return "Shuttle S-lines"
    if r.endswith("X"):     return "Express X-suffix"
    if re.fullmatch(r"\d+[A-Z]", r): return "Suffixed variants (1M/2A...)"
    if re.fullmatch(r"\d+", r):      return "Regular numeric"
    return "Other special"

def dest_area(dest_en):
    """从目的地串提取区域名:去掉连字符前段之外的尾巴和括号"""
    return re.sub(r"\s*\([^)]*\)\s*$", "", dest_en.split(" - ")[0].strip())

payload = fetch_json(ROUTE_URL)
routes = {r["route"]: r for r in payload["data"]}   # 每号一行,直接去重
kinds  = Counter(classify(r) for r in routes)
areas  = Counter(dest_area(r["dest_en"]) for r in routes.values())
print("unique routes:", len(routes))
print("classes:", dict(kinds.most_common()))
print("top destinations:", areas.most_common(10))

分类的顺序很关键:NA(机场通宵)必须放在 N(普通通宵)和 A 之前判断,X 后缀要放在纯数字正则之前------正则的匹配顺序就是分类的正确性。这套「正则分类 + Counter 统计」的写法可以原样搬到任何开放数据集的分类统计上,收藏备用。

三、运行验证

本地跑上面这段,输出(与 citybus_network_analysis.py 一致,2026-08-29 实测):

text 复制代码
unique routes: 406
classes: Regular numeric 130 | Suffixed variants 128 | Express X-suffix 40
         Overnight N-lines 30 | Other special 23 | E-lines 20
         Airport A-lines 14 | Shuttle S-lines 12 | Airport overnight NA 6 | R-lines 3
top destinations: Central 35 | Causeway Bay 25 | Airport 21 | Tsim Sha Tsui 9
                  Sai Wan Ho 9 | Exhibition Centre 9 | Admiralty 9 | North Point FP 8
                  Wan Chai 8 | Tin Hau 8

自检方法:把各类计数加起来验证是否等于 406;再把目的地 top10 计数加起来对照总量。两个加总都对得上,数据才可信。

四、可视化与分析:406 条路线里藏着什么

1. 数字系占六成以上。 130 条常规数字线 + 128 条后缀变体 = 258 条,占 63.5%。后缀变体(1M、2A、720P、930X 已单列)是城巴编号体系里最"卷"的部分------同样一条走廊,主干、短途、繁忙时段、特快各占一个号。乘客看的是"这班车到不到",分析师看的是"这个号背后是一个家族"。对通勤者来说,变体还有实际含义:高峰时段多条变体同时开行,等于在同一条走廊上加密班次;如果只看路线号不看变体,会严重低估某条走廊的真实运营频次。这也是分类必须用正则精确拆开的原因------数字线、后缀变体、X 特快各算一类,才不会被"看起来都是数字"糊弄过去。

2. 机场线是一个完整的生态。 A 线 14 条(市区直达机场)、E 线 20 条(东涌/机场后勤)、NA 通宵 6 条,合计 40 条,占全网的近 10%。香港机场一天 24 小时运转,巴士网络就用 A/E/NA 三层对应三种需求------这是功能集群设计最清晰的部分。

3. 中环是绝对枢纽。 35 条路线以 Central 为终点,比第二的铜锣湾(25 条)多 10 条;机场(21 条)作为终点排第三,说明"去机场"是港岛方向最重要的单一出行目的之一。

4. 抽样站点数:通宵线为什么这么长。 顺手对六条代表路线数了 outbound 站点数:720 只有 18 站,1 号线 20 站,A11 机场线 24 站,E11 29 站,6 号线 41 站,而通宵 N8X 有 60 站------通宵车深夜客流稀疏,只能靠"一条线覆盖尽量多的地方"来维持运营密度,站多也就不奇怪了。

五、四个容易踩的坑

# 现象 规避
1 direction 传数字 API 返回 422 Invalid direction 只传 inbound / outbound
2 把路线行数当双向行数 406 数成 812,占比全错 route 列表每号一行,方向靠参数
3 API 偶发超时/空响应 拉到空列表以为数据为空 fetch_json 重试 3 次再报错
4 matplotlib 中文 label DejaVu Sans 缺字出方框 全程英文 label,中文放正文

第一条是实测时最先撞上的:我一开始按"方向 1、2"传参,直接被 422 打回。第二条是数据口径,最容易静默出错------406 变 812 没有任何报错,只有占比对不上才暴露。这张表建议收藏,下次拉任何开放数据 API 前先过一遍。

六、小结

一句话总结:香港岛巴士网络的骨架,用 406 个路线号和几行 Python 就能画像------63.5% 是数字系、40 条机场线、中环 35 条终点居首,通宵线靠拉长站点维持密度。

这篇与之前九巴 KMB 的实时 ETA 文章是两件事:那边是动态到站的时间语义,这篇是静态网络的整体结构------不同公司、不同数据集、不同分析对象。城巴开放数据每周随运营调整更新,把脚本存好,下次直接复跑就是最新画像。本文参与编程达人挑战赛·第12期,用一次完整「官方 API → 分类统计 → 可视化」的链路记录香港公共交通的一角。

参考链接(官方):

声明:本文为原创内容,数据来自 data.gov.hk 城巴开放数据(2026-08-29 实测)。路线分类为作者自定口径,官方无此分类;城巴 2023 年合并新巴后由单一公司运营,406 条路线含原新巴网络。仅供技术交流。

相关推荐
for_ever_love__21 分钟前
python基础语法学习: 正则表达式
python·学习·正则表达式
aiqianji23 分钟前
功能全面的教AI短篇小说写作的软件都有哪些?
人工智能·python
轮到我狗叫了41 分钟前
Slurm如何使用
人工智能·python·深度学习·机器学习
一直C1 小时前
Linux系统编程|进程间通信IPC全套详解(1)(管道、信号)
linux·c语言·开发语言·网络·青少年编程·visual studio code
Experience-摆渡1 小时前
自建 IP 轮换方案实战:健康探测 + 熔断剔除 + 粘性会话
网络·网络协议·tcp/ip
luj_17681 小时前
合法避税与投资评估实战指南
c语言·开发语言·网络·经验分享·算法
库玛西1 小时前
Linux网络编程:HTTP/HTTPS协议核心技术全解析
linux·运维·服务器·网络·c++·http·https
Ali885201 小时前
Python列表增删改查速查表和详细演示
python
暴力求解1 小时前
Linux网络---NAT代理服务、内网穿透
linux·网络·智能路由器