
简介获取百度高德实时交通态势数据的完整源码面向具备一定Python基础、希望对接地图API进行数据分析或交通可视化开发的工程师、数据分析师及学习者。包内共559个文件包含502个Python脚本覆盖接口请求、返回数据解析、可视化展示等核心逻辑、虚拟环境配置文件及activate、bat等运行辅助脚本还附有交通态势获取执行操作说明文档压缩包整体仅2.65MB轻量易用快速部署试运行。已有336人学习下载。整套方案完整覆盖从API密钥注册配置、HTTP请求构造、JSON/XML数据解析到图表地图展示的链路并考虑定时刷新、实时更新等时效性问题同时保留类型定义、证书等辅助文件便于二次开发与生产环境扩展。借助这些代码开发者可快速搭建城市拥堵、事故、管制等道路交通态势的采集与展示工具也可为智能交通调度、出行路线优化等应用场景提供扎实的参考基础。 前一阵接了个城市路况大屏的活儿需要把整个城区的实时交通态势数据抓下来做展示。一开始想得很简单——直接调官方API。结果一查发现高德交通态势接口按道路段维度返回想拿全城每一条路的路况调用量和配额根本扛不住百度的路况能力又基本内嵌在前端瓦片层里官方开放接口并不直接给你整张路况图层。后来在技术社区里翻到不少同行也在干同一件事解析地图前端页面实时加载的那一层路况瓦片也就是地图上那些绿色、黄色、红色线条背后的数据。这篇把整套思路和能直接跑的源码整理出来给准备做交通可视化、路况监控或者城市态势大屏的朋友做个参考。这类数据最典型的应用场景有这么几类一是做城市级交通态势大屏需要整片区域而不是单条道路的拥堵热力二是做历史路况回放需要按时间片把路况图层存下来三是做导航路径的视觉渲染比如用开源GIS引擎加载路况底图。不管哪种核心问题都一样怎么稳定、高效地把实时路况数据拿到手并且能解析成自己可用的格式。1. 两种取数入口官方API的边界与前端路况瓦片的优势1.1 官方API的定位和限制高德开放平台确实提供了交通态势查询接口用法很清晰请求一个矩形范围内的路况信息返回该范围内各条道路的拥堵状态、车速、延误时间等结构化数据。这个接口好用但有个很现实的约束按调用次数计费免费配额有限而且单次请求有矩形范围大小限制。你想做全城24小时不间断的路况监控一天下来调用量轻松破万免费额度根本不够用。百度的开放能力里路况相关的接口更偏向导航路径规划时附带返回沿途路况而不是给我某个区域的所有路况图层。想做区域级拥堵态势用官方API反而不顺手。所以实测下来两类不同需求要选不同入口只要几条固定路段的实时状态优先走高德交通态势API数据结构化程度高省去自己解析的麻烦。要整个城市范围的路况图层、要做历史轨迹回放、要在大屏上渲染整片路网直接拿前端瓦片数据更高效。1.2 路况瓦片到底是什么你打开百度地图或高德地图放大到城市级别看到路网上那层由绿色、黄色、红色组成的半透明线条就是实时路况图层。浏览器里看到的这张图本质上是几十上百张256x256像素的PNG小图片拼起来的这类小图片叫瓦片Tile每一个瓦片都有独立的编号分别对应不同的地图缩放级别和经纬度范围。高德地图前端加载路况瓦片的URL在开发者工具的Network面板里就能看到长这样https://tm.amap.com/traffic?x214y97z8op1百度地图的路况瓦片URL类似但是坐标规则不太一样后面细说。路况瓦片的优势在于一张瓦片覆盖一大片区域拉几张瓦片就能拿到整个城区的路况图像每张瓦片只有几十KB几百张瓦片也就几十MB流量抓取速度快瓦片本身是独立的图片文件直接存下来就能做历史回放。缺点也明显拿到的是图而不是结构化数据需要自己解析颜色对应的拥堵等级而且必须遵守平台的服务条款不能高频暴力请求。2. 坐标系与瓦片编号拿数据前必须先算对的两件事2.1 国内地图的三套坐标困局做地图相关的开发Coordinate System是绕不开的坑。国内地图圈实际存在三套坐标体系WGS-84GPS卫星定位用的原始坐标国际通用。GCJ-02国测局坐标俗称火星坐标高德地图用的就是这套对WGS-84做了非线性偏移。BD-09百度坐标在GCJ-02基础上又做了一次二次偏移百度地图专用。这三套坐标之间不通用你用手机GPS拿到的WGS-84坐标直接扔给高德地图位置会偏几百米直接扔给百度地图偏差更大。反过来也一样。所以在计算瓦片编号之前必须明确你手里的经纬度是哪套坐标体系然后统一转到对应地图的坐标系里去算。高德地图的瓦片基于GCJ-02坐标计算百度地图的瓦片基于BD-09坐标计算两者不能混用。2.2 瓦片编号的计算逻辑地图瓦片遵循Web墨卡托投影的标准切分规则缩放级别z从0开始z0时全世界是一张256x256的瓦片z1时切成2x2共4张z2时切成4x4共16张。每个瓦片都有唯一的编号(x, y, z)其中x是列号、y是行号。已知某一点的经纬度(lon, lat)对应缩放级别z下标准Web墨卡托瓦片编号的计算公式是n 2^z x floor((lon 180.0) / 360.0 * n) y floor((1.0 - asinh(tan(lat_rad)) / pi) / 2.0 * n)这个计算公式是行业通用标准网上能找到各种语言的实现。这里推荐一个很省事的轮子Python第三方库mercantile一行代码就能算。pip install mercantileimport mercantile # 高德瓦片直接传入GCJ-02坐标系的经纬度即可 tile mercantile.tile(116.391, 39.907, zoom12) print(tile) # Tile(x3571, y1587, z12) # 得到瓦片编号后拼接URL url fhttps://tm.amap.com/traffic?x{tile.x}y{tile.y}z{tile.z}op12.3 百度瓦片的特殊之处百度地图的瓦片规则和高德不一样。百度没有直接用标准Web墨卡托XYZ编号而是在BD-09墨卡托投影的基础上把全球范围做了偏移定义。直接用标准算法算出来的x、y去请求百度瓦片返回的是错误位置的图。实践中处理百度瓦片需要两步转换先把经纬度从WGS-84或GCJ-02转到BD-09。再基于BD-09墨卡托平面坐标用偏移后的原点计算瓦片行列号。转换代码网上有大量现成实现这里给出一个最常用的版本实测精度足够import math x_pi 3.14159265358979324 * 3000.0 / 180.0 pi 3.1415926535897932384626 a 6378245.0 ee 0.00669342162296594323 def _transform_lat(x, y): ret -100.0 2.0 * x 3.0 * y 0.2 * y * y 0.1 * x * y 0.2 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * pi) 20.0 * math.sin(2.0 * x * pi)) * 2.0 / 3.0 ret (20.0 * math.sin(y * pi) 40.0 * math.sin(y / 3.0 * pi)) * 2.0 / 3.0 ret (160.0 * math.sin(y / 12.0 * pi) 320 * math.sin(y * pi / 30.0)) * 2.0 / 3.0 return ret def _transform_lon(x, y): ret 300.0 x 2.0 * y 0.1 * x * x 0.1 * x * y 0.1 * math.sqrt(abs(x)) ret (20.0 * math.sin(6.0 * x * pi) 20.0 * math.sin(2.0 * x * pi)) * 2.0 / 3.0 ret (20.0 * math.sin(x * pi) 40.0 * math.sin(x / 3.0 * pi)) * 2.0 / 3.0 ret (150.0 * math.sin(x / 12.0 * pi) 300.0 * math.sin(x / 30.0 * pi)) * 2.0 / 3.0 return ret def wgs84_to_gcj02(lng, lat): if out_of_china(lng, lat): return lng, lat dlat _transform_lat(lng - 105.0, lat - 35.0) dlng _transform_lon(lng - 105.0, lat - 35.0) radlat lat / 180.0 * pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) return lng dlng, lat dlat def gcj02_to_bd09(lng, lat): z math.sqrt(lng * lng lat * lat) 0.00002 * math.sin(lat * x_pi) theta math.atan2(lat, lng) 0.000003 * math.cos(lng * x_pi) return z * math.cos(theta) 0.0065, z * math.sin(theta) 0.006 def wgs84_to_bd09(lng, lat): lng, lat wgs84_to_gcj02(lng, lat) return gcj02_to_bd09(lng, lat) def out_of_china(lng, lat): return not (72.004 lng 137.8347 and 0.8293 lat 55.8271)坐标转换这个事情我的经验是尽量在自己的代码里统一入口所有经纬度进来先转成目标地图要求的坐标系再做瓦片计算。别东一榔头西一棒子否则排查问题的时候你会怀疑人生。3. Python抓取路况瓦片核心源码与拥堵颜色解析3.1 路况瓦片URL与请求头高德路况瓦片的基础URL是tm.amap.com路径/traffic核心参数就是x、y、zop参数控制返回的是路况图层。百度路况瓦片的基础URL是maponline0.bdimg.com路径/tile/参数里有qttraffic代表请求的是路况图层。请求时建议带上常见的浏览器User-Agent否则部分节点会拒绝服务。另外务必控制请求频率一般以每瓦片间隔0.1到0.2秒为宜并发数控制在3以内频繁请求很容易被临时封禁IP。3.2 单瓦片抓取与解析import requests import io from PIL import Image HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.amap.com/, } def fetch_amap_traffic_tile(x, y, z, save_pathNone): url fhttps://tm.amap.com/traffic?x{x}y{y}z{z}op1 resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() img Image.open(io.BytesIO(resp.content)).convert(RGBA) if save_path: img.save(save_path) return img3.3 拥堵颜色与等级映射路况瓦片是带透明通道的PNG底是透明的只有路况线条有颜色。需要把像素颜色解析成拥堵等级。实测高德和百度的路况配色略有差异但核心颜色范围是一致的颜色RGB大致范围拥堵等级绿色(0, 200, 0) ~ (60, 255, 60)畅通黄色(240, 180, 0) ~ (255, 220, 50)缓行红色(220, 30, 30) ~ (255, 80, 80)拥堵深红(130, 0, 0) ~ (200, 30, 30)严重拥堵解析思路很简单遍历像素算目标颜色和预置等级颜色的欧氏距离距离最小的那个等级就是该像素的拥堵等级。为了避免图片压缩带来的细微色差距离阈值要留一些余量。import colorsys TRAFFIC_LEVELS { clear: (0, 220, 60), slow: (250, 200, 20), congested: (240, 60, 40), heavy: (150, 20, 20), } def classify_color(rgb): best_level unknown best_dist float(inf) for level, ref_color in TRAFFIC_LEVELS.items(): dist sum((c - ref_c) ** 2 for c, ref_c in zip(rgb, ref_color)) if dist best_dist: best_dist dist best_level level if best_dist 3000: return unknown return best_level这里有一层关键处理瓦片是半透明的直接用RGBA的RGB通道识别即可不需要复合到底图上因为透明像素的RGB值通常是0距离计算后会被判为unknown不影响结果。3.4 按经纬度范围批量抓取瓦片实际项目中你不会一张一张手算瓦片编号而是给定一个经纬度矩形范围程序自动生成该范围在某个缩放级别下的所有瓦片编号然后逐一下载。import mercantile def tiles_in_bbox(lon_min, lat_min, lon_max, lat_max, zoom): tiles list(mercantile.tiles(lon_min, lat_min, lon_max, lat_max, zoom)) return tiles # 示例抓取某城市中心周边12级路况瓦片 bbox (116.35, 39.85, 116.45, 39.95) tiles tiles_in_bbox(*bbox, zoom12) print(f共 {len(tiles)} 张瓦片) import time, os os.makedirs(tiles, exist_okTrue) for i, tile in enumerate(tiles): try: img fetch_amap_traffic_tile(tile.x, tile.y, tile.z, save_pathftiles/{tile.x}_{tile.y}_{tile.z}.png) print(f[{i1}/{len(tiles)}] {tile.x}, {tile.y} 下载成功尺寸 {img.size}) except Exception as e: print(f[{i1}/{len(tiles)}] {tile.x}, {tile.y} 下载失败: {e}) time.sleep(0.15)跑一遍这个脚本你会在tiles目录下拿到一整套路况瓦片每张瓦片都对应地图上一个固定经纬度范围。把这些瓦片按照行列号拼起来就能得到整个范围内的路况总览图。4. 从瓦片到结构化路况拼接、渲染与路段状态提取4.1 把瓦片拼成一张完整路况图拿到一堆零散瓦片后最直观的用法是把它们拼成一张大图。拼接逻辑不复杂算出所有瓦片的行列号范围创建一块大画布按行列号把瓦片贴到对应位置。from PIL import Image TILE_SIZE 256 def merge_tiles(tile_paths, min_x, min_y, max_x, max_y): width (max_x - min_x 1) * TILE_SIZE height (max_y - min_y 1) * TILE_SIZE merged Image.new(RGBA, (width, height), (0, 0, 0, 0)) for x in range(min_x, max_x 1): for y in range(min_y, max_y 1): path ftiles/{x}_{y}_12.png try: tile Image.open(path) merged.paste(tile, ((x - min_x) * TILE_SIZE, (y - min_y) * TILE_SIZE)) except FileNotFoundError: continue return merged merged_img merge_tiles(tiles, 3569, 1585, 3572, 1588) merged_img.save(traffic_merged.png)拼出来的这张图叠加到对应范围的底图上就是一张完整的实时路况态势图。如果做Web大屏可以用开源GIS库直接按瓦片编号加载不需要预先拼接让前端自己去请求瓦片后端只需要提供瓦片文件的静态访问能力即可。4.2 从像素统计到路段级数据如果只是做可视化拿到瓦片图就够了。但如果要做统计分析——比如这个区当前拥堵里程占比多少就需要把像素数据转换成结构化指标。做法是遍历瓦片所有像素按拥堵等级归类计数算出各等级像素占比。因为像素对应的实际面积可以从缩放级别推算像素占比就约等于道路里程占比。from collections import Counter def analyze_traffic_image(img): pixels list(img.getdata()) counter Counter(classify_color(p[:3]) for p in pixels) total sum(counter.values()) result {k: v / total for k, v in counter.items()} return result ratio analyze_traffic_image(merged_img) print(ratio)这个思路在多数城市级路况监控项目里都够用。但要注意它统计的是路况图层覆盖的像素比例不是道路里程的精确比例。原因是高德的瓦片路况不只是主干道快速路、次干道都有但不同等级道路的线宽不同粗线条在高缩放级别下占比会被放大。如果你需要精确到每条道路的拥堵状态那还是得回到官方API或者用官方API提供的道路段数据进行校准。4.3 路况渲染到Cesium等GIS引擎搜热词里有人提到Cesium实现高德箭头效果uniapp高德地图导航其实就是把拿到的路况图层叠加到Web GIS引擎上。Cesium加载路况瓦片的方式很简单把瓦片服务URL配置成ImageryProvider即可前提是你自己起一个瓦片服务把抓下来的瓦片按z/x/y的目录结构放好tileserver/ ├── 12/ │ ├── 3569/ │ │ ├── 1585.png │ │ └── 1586.png │ └── 3570/ │ └── 1585.png然后用nginx或者python的http.server托管这个目录Cesium端加载const provider new Cesium.UrlTemplateImageryProvider({ url: http://localhost:8080/{z}/{x}/{y}.png });这样就能在Cesium场景中把实时路况叠加到底图上实现弓箭效果、动态箭头等渲染演示效果比静态图片好得多。5. 实际抓取中的边界问题与合规使用5.1 请求被限流怎么办路况瓦片服务毕竟是给线上地图产品用的不是你随便写个脚本就能无限拉取的。实测中遇到最多的问题就是IP被封。症状很典型请求返回200但内容是空的或者直接返回403或者响应时间突然从100毫秒变成3秒。排障顺序建议这样来检查Referer头是不是被平台校验了补上正确的Referer。检查请求频率单线程加0.1秒间隔确认是否恢复正常。检查瓦片坐标编号是否算错坐标错会请求到不存在的瓦片表现也是异常。确认服务节点是否封了数据中心IP。如果是只能降低频率加缓存把每天的总请求量控制在很小的规模。时间上的经验路况数据每分钟都在变但没必要每分钟全量重抓。市区路况一般5分钟更新一次就够用。把抓取结果落到本地缓存下次请求直接读缓存只有到了缓存有效期才重新拉取。这样一天的瓦片请求量能控制在上百次基本不会被封。5.2 数据时效性与缓存策略实时路况数据的特点是快照式你抓到的某张瓦片代表的是那一刻的路况状态。做历史回放时要注意同一经纬度范围不同时刻的瓦片要按时分目录存储traffic_history/ ├── 20250101/ │ ├── 0800/ │ │ ├── 12/ │ │ │ ├── 3569/ │ │ │ └── 3570/ │ └── 0810/存的时候建议同时写一个元数据文件记录每个目录对应的UTC时间便于后续做轨迹回放和研究分析。5.3 合规使用的几条底线这个部分是重点。做开发的人要有边界意识抓取路况瓦片这件事在法律和平台规则上处在灰色地带只能用于个人学习、技术研究、小规模内部项目。几条底线基本原则不要高频、大范围、长时间抓取数据不要试图绕过平台的频率限制和鉴权。不要将抓取的数据用于商业用途不要重新分发不要打包成API对外售卖。优先评估官方开放平台的数据接口是否满足需求官方接口永远是首选只有官方能力覆盖不了的场景才考虑解析瓦片。在生产环境使用应购买有授权的数据服务或者与地图平台达成商业合作。我见过不少团队因为嫌官方AP贵直接拿瓦片抓取做成了商业产品最后收到平台的法务函。技术本身没有错但使用场景和目的是有边界的。个人做项目练手、跑通技术链路是完全没问题的。掌握了这套从瓦片抓取、颜色解析到态势渲染的完整流程后面无论是接高德官方API做精确道路级路况还是接百度生态做路况展示理解成本都会低很多。最后分享一个我自己踩过坑之后养成的习惯写这类爬取脚本之前先用浏览器开发者工具盯着Network面板观察十分钟把瓦片URL、参数、Referer、请求频率全部摸清楚再动手。前端页面上能看到的所有数据都有对应的请求入口这比去论坛搜一堆过时的接口文档靠谱得多。整个项目做完我对路况数据的理解也从导航里的红黄绿线条变成了一套坐标瓦片颜色的工程体系这种视角上的转变价值比代码本身大得多。本文还有配套的精品资源点击获取