Python 高德地图 API v3 公交数据爬取实战:青岛 275 条线路站点与轨迹坐标获取
Python 高德地图 API v3 公交数据爬取实战青岛 275 条线路站点与轨迹坐标获取公共交通数据是城市规划和智慧交通建设的重要基础。通过获取公交线路的站点信息和轨迹坐标我们可以进行线路优化、换乘分析、可视化展示等多种应用。本文将详细介绍如何使用 Python 和高德地图 Web 服务 API v3 版本来爬取青岛市 275 条公交线路的完整数据。1. 高德地图开发者 Key 申请与配置要使用高德地图的 Web 服务 API首先需要申请开发者 Key。以下是详细步骤访问 高德开放平台官网 并注册开发者账号进入控制台→应用管理→创建新应用为应用命名如青岛公交数据分析选择应用类型为Web服务在创建的应用中添加 Key服务选择Web服务获取到 Key 后我们可以将其存储在配置文件中或者直接作为变量使用# 配置高德API Key AMAP_KEY 你的高德API Key CITY 青岛 # 目标城市提示高德地图的免费配额对于个人开发者和小规模应用通常足够但如果需要大规模调用建议关注配额限制或考虑购买企业套餐。2. 公交线路基础信息获取高德地图提供了/v3/bus/linename接口来根据线路名称查询公交信息。我们将构建一个函数来获取单条线路的详细信息import requests import json import time import pandas as pd def get_bus_line_info(line_name, cityCITY, keyAMAP_KEY): 获取单条公交线路的详细信息 :param line_name: 公交线路名称如321路 :param city: 城市名称 :param key: 高德API Key :return: 包含线路信息的字典 url fhttps://restapi.amap.com/v3/bus/linename?key{key}city{city}outputjsonoffset1keywords{line_name}extensionsall try: response requests.get(url) data json.loads(response.text) if data[status] ! 1 or not data.get(buslines): print(f未找到线路 {line_name} 的信息) return None line_info data[buslines][0] # 提取关键信息 result { line_id: line_info[id], line_name: line_info[name], start_stop: line_info[start_stop], end_stop: line_info[end_stop], company: line_info.get(company, ), distance: float(line_info[distance]), start_time: line_info[start_time], end_time: line_info[end_time], total_price: float(line_info[total_price]), via_stops: [stop[name] for stop in line_info[busstops]], stop_locations: [stop[location] for stop in line_info[busstops]], stop_sequence: [stop[sequence] for stop in line_info[busstops]], polyline: line_info[polyline] # 线路轨迹坐标 } return result except Exception as e: print(f获取线路 {line_name} 信息时出错: {str(e)}) return None3. 批量获取青岛公交线路数据青岛市的公交线路名称可以从多种渠道获取这里我们提供一个青岛公交线路名称的示例列表# 青岛公交线路示例列表实际应用中可能需要从其他渠道获取完整列表 qingdao_bus_lines [ 1路, 2路, 3路, 4路, 5路, 6路, 7路, 8路, 9路, 10路, 11路, 12路, 15路, 16路, 18路, 19路, 20路, 21路, 22路, 23路, 24路, 25路, 26路, 28路, 29路, 30路, 31路, 32路, 33路, 36路, 101路, 102路, 103路, 104路, 105路, 106路, 109路, 110路, 111路, 112路, 113路, 114路, 115路, 116路, 117路, 118路, 119路, 120路, 121路, 122路, 123路, 125路, 128路, 129路, 130路, 131路, 202路, 205路, 206路, 207路, 208路, 209路, 210路, 211路, 212路, 213路, 214路, 215路, 216路, 217路, 218路, 219路, 220路, 221路, 222路, 223路, 224路, 225路, 226路, 227路, 228路, 229路, 230路, 231路, 232路, 233路, 301路, 302路, 303路, 304路, 305路, 306路, 307路, 308路, 309路, 310路, 311路, 312路, 313路, 314路, 316路, 317路, 318路, 319路, 320路, 321路, 322路, 323路, 324路, 325路, 326路, 327路, 328路, 329路, 330路, 331路, 332路, 333路, 334路, 335路, 336路, 337路, 338路, 339路, 340路, 341路, 342路, 343路, 361路, 362路, 363路, 364路, 365路, 366路, 367路, 368路, 369路, 370路, 371路, 372路, 373路, 374路, 375路, 376路, 377路, 378路, 379路, 380路, 381路, 382路, 383路, 384路, 385路, 386路, 387路, 388路, 389路, 390路, 501路, 502路, 503路, 504路, 505路, 506路, 507路, 508路, 509路, 510路, 511路, 512路, 513路, 514路, 515路, 516路, 517路, 518路, 519路, 520路, 521路, 522路, 523路, 524路, 525路, 526路, 527路, 528路, 529路, 530路, 531路, 532路, 533路, 602路, 603路, 604路, 605路, 606路, 607路, 608路, 609路, 610路, 611路, 612路, 613路, 614路, 615路, 616路, 617路, 618路, 619路, 620路, 621路, 623路, 624路, 625路, 626路, 627路, 628路, 629路, 630路, 631路, 632路, 633路, 634路, 635路, 636路, 637路, 638路, 639路, 640路, 641路, 642路, 643路, 644路, 645路, 646路, 647路, 648路, 649路, 650路, 651路, 652路, 653路, 655路, 656路, 657路, 658路, 659路, 660路, 661路, 662路, 663路, 664路, 665路, 666路, 667路, 668路, 669路, 670路, 671路, 672路, 673路, 674路, 675路, 676路, 677路, 678路, 679路, 680路, 681路, 682路, 683路, 684路, 685路, 686路, 687路, 688路, 689路, 690路, 691路, 692路, 693路, 694路, 695路, 696路, 697路, 698路, 699路, 761路, 762路, 763路, 765路, 766路, 767路, 768路, 769路, 770路, 771路, 772路, 773路, 774路, 775路, 776路, 777路, 778路, 779路, 780路, 781路, 782路, 783路, 784路, 785路, 786路, 787路, 788路, 789路, 790路, 791路, 792路, 793路, 794路, 795路, 796路, 797路, 798路, 799路, 801路, 802路, 803路, 804路, 805路, 806路, 807路, 808路, 809路, 901路, 902路, 903路, 904路, 905路, 906路, 907路, 908路, 909路, 910路, 911路, 912路, 913路, 914路, 915路, 916路, 917路, 918路, 919路, 920路, 921路, 922路, 923路, 924路, 925路, 926路, 927路, 928路, 929路, 930路, 931路, 932路, 933路, 934路, 935路, 936路, 937路, 938路, 939路, 940路, 941路, 942路, 943路, 944路, 945路, 946路, 947路, 948路, 949路, 950路, 951路, 952路, 953路, 954路, 955路, 956路, 957路, 958路, 959路, 960路, 961路, 962路, 963路, 964路, 965路, 966路, 967路, 968路, 969路, 970路, 971路, 972路, 973路, 974路, 975路, 976路, 977路, 978路, 979路, 980路, 981路, 982路, 983路, 984路, 985路, 986路, 987路, 988路, 989路, 990路, 991路, 992路, 993路, 994路, 995路, 996路, 997路, 998路, 999路 ]接下来我们可以编写一个函数来批量获取所有线路的信息def batch_get_bus_lines(line_names, cityCITY, keyAMAP_KEY, save_pathNone): 批量获取公交线路信息 :param line_names: 公交线路名称列表 :param city: 城市名称 :param key: 高德API Key :param save_path: 数据保存路径可选 :return: 包含所有线路信息的DataFrame all_lines_data [] failed_lines [] for i, line_name in enumerate(line_names, 1): print(f正在获取线路 {i}/{len(line_names)}: {line_name}) line_data get_bus_line_info(line_name, city, key) if line_data: all_lines_data.append(line_data) else: failed_lines.append(line_name) # 避免请求过于频繁适当延时 time.sleep(0.1) # 转换为DataFrame df pd.DataFrame(all_lines_data) # 保存到文件 if save_path: df.to_csv(save_path, indexFalse, encodingutf_8_sig) print(f数据已保存到 {save_path}) if failed_lines: print(f以下线路获取失败: {, .join(failed_lines)}) return df4. 数据清洗与处理从API获取的原始数据需要进行一些清洗和处理才能用于分析。以下是几个关键的数据处理步骤4.1 坐标数据处理高德地图使用的是GCJ-02坐标系火星坐标系如果需要与其他地图服务如Google地图一起使用可能需要转换为WGS-84坐标系。以下是坐标转换的实现import math def gcj02_to_wgs84(lng, lat): GCJ-02(火星坐标系)转WGS-84 :param lng: 火星坐标系的经度 :param lat: 火星坐标系纬度 :return: WGS-84坐标系的经度,纬度 a 6378245.0 # 长半轴 ee 0.00669342162296594323 # 扁率 def transformlat(lng, lat): ret -100.0 2.0 * lng 3.0 * lat 0.2 * lat * lat \ 0.1 * lng * lat 0.2 * math.sqrt(math.fabs(lng)) ret (20.0 * math.sin(6.0 * lng * math.pi) 20.0 * math.sin(2.0 * lng * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(lat * math.pi) 40.0 * math.sin(lat / 3.0 * math.pi)) * 2.0 / 3.0 ret (160.0 * math.sin(lat / 12.0 * math.pi) 320 * math.sin(lat * math.pi / 30.0)) * 2.0 / 3.0 return ret def transformlng(lng, lat): ret 300.0 lng 2.0 * lat 0.1 * lng * lng \ 0.1 * lng * lat 0.1 * math.sqrt(math.fabs(lng)) ret (20.0 * math.sin(6.0 * lng * math.pi) 20.0 * math.sin(2.0 * lng * math.pi)) * 2.0 / 3.0 ret (20.0 * math.sin(lng * math.pi) 40.0 * math.sin(lng / 3.0 * math.pi)) * 2.0 / 3.0 ret (150.0 * math.sin(lng / 12.0 * math.pi) 300.0 * math.sin(lng / 30.0 * math.pi)) * 2.0 / 3.0 return ret dlat transformlat(lng - 105.0, lat - 35.0) dlng transformlng(lng - 105.0, lat - 35.0) radlat lat / 180.0 * math.pi magic math.sin(radlat) magic 1 - ee * magic * magic sqrtmagic math.sqrt(magic) dlat (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * math.pi) dlng (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * math.pi) mglat lat dlat mglng lng dlng return [lng * 2 - mglng, lat * 2 - mglat]4.2 轨迹坐标解析高德API返回的轨迹坐标是一个字符串格式为经度1,纬度1;经度2,纬度2;...。我们需要将其解析为坐标点列表def parse_polyline(polyline_str): 解析高德地图的polyline字符串 :param polyline_str: 轨迹坐标字符串 :return: 包含(经度,纬度)元组的列表 points [] for point_str in polyline_str.split(;): if not point_str: continue lng, lat map(float, point_str.split(,)) points.append((lng, lat)) return points4.3 站点信息展开原始数据中站点信息是以列表形式存储的我们需要将其展开为每行一个站点的格式def expand_stop_info(df): 展开站点信息使每行对应一个站点 :param df: 原始数据DataFrame :return: 展开后的DataFrame expanded_rows [] for _, row in df.iterrows(): line_id row[line_id] line_name row[line_name] via_stops row[via_stops] stop_locations row[stop_locations] stop_sequence row[stop_sequence] for stop_name, location, seq in zip(via_stops, stop_locations, stop_sequence): lng, lat map(float, location.split(,)) expanded_rows.append({ line_id: line_id, line_name: line_name, stop_name: stop_name, longitude: lng, latitude: lat, sequence: seq }) return pd.DataFrame(expanded_rows)5. 数据存储与导出处理后的数据可以存储为多种格式以下是几种常见的存储方式5.1 CSV格式存储# 保存线路基本信息 df_lines.to_csv(qingdao_bus_lines.csv, indexFalse, encodingutf_8_sig) # 保存站点信息 df_stops expand_stop_info(df_lines) df_stops.to_csv(qingdao_bus_stops.csv, indexFalse, encodingutf_8_sig) # 保存轨迹信息 df_trajectories pd.DataFrame([ {line_id: row[line_id], line_name: row[line_name], polyline: parse_polyline(row[polyline])} for _, row in df_lines.iterrows() ]) df_trajectories.to_csv(qingdao_bus_trajectories.csv, indexFalse, encodingutf_8_sig)5.2 SQLite数据库存储对于更复杂的数据管理需求可以使用SQLite数据库import sqlite3 def save_to_sqlite(df_lines, db_pathqingdao_bus.db): 将公交数据保存到SQLite数据库 :param df_lines: 包含线路信息的DataFrame :param db_path: 数据库文件路径 conn sqlite3.connect(db_path) cursor conn.cursor() # 创建线路表 cursor.execute( CREATE TABLE IF NOT EXISTS bus_lines ( line_id TEXT PRIMARY KEY, line_name TEXT, start_stop TEXT, end_stop TEXT, company TEXT, distance REAL, start_time TEXT, end_time TEXT, total_price REAL ) ) # 创建站点表 cursor.execute( CREATE TABLE IF NOT EXISTS bus_stops ( id INTEGER PRIMARY KEY AUTOINCREMENT, line_id TEXT, line_name TEXT, stop_name TEXT, longitude REAL, latitude REAL, sequence INTEGER, FOREIGN KEY (line_id) REFERENCES bus_lines (line_id) ) ) # 创建轨迹表 cursor.execute( CREATE TABLE IF NOT EXISTS bus_trajectories ( id INTEGER PRIMARY KEY AUTOINCREMENT, line_id TEXT, line_name TEXT, point_index INTEGER, longitude REAL, latitude REAL, FOREIGN KEY (line_id) REFERENCES bus_lines (line_id) ) ) # 插入线路数据 for _, row in df_lines.iterrows(): cursor.execute( INSERT OR REPLACE INTO bus_lines VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) , ( row[line_id], row[line_name], row[start_stop], row[end_stop], row[company], row[distance], row[start_time], row[end_time], row[total_price] )) # 插入站点数据 df_stops expand_stop_info(df_lines) for _, row in df_stops.iterrows(): cursor.execute( INSERT INTO bus_stops (line_id, line_name, stop_name, longitude, latitude, sequence) VALUES (?, ?, ?, ?, ?, ?) , ( row[line_id], row[line_name], row[stop_name], row[longitude], row[latitude], row[sequence] )) # 插入轨迹数据 for _, row in df_lines.iterrows(): points parse_polyline(row[polyline]) for i, (lng, lat) in enumerate(points): cursor.execute( INSERT INTO bus_trajectories (line_id, line_name, point_index, longitude, latitude) VALUES (?, ?, ?, ?, ?) , ( row[line_id], row[line_name], i, lng, lat )) conn.commit() conn.close()6. 数据可视化获取公交数据后我们可以使用各种工具进行可视化展示。以下是使用Python的Plotly库进行可视化的示例6.1 公交线路地图可视化import plotly.express as px import plotly.graph_objects as go def plot_bus_lines(df_lines, center_lat36.09, center_lng120.39, zoom11): 在地图上绘制公交线路 :param df_lines: 包含线路信息的DataFrame :param center_lat: 地图中心纬度 :param center_lng: 地图中心经度 :param zoom: 缩放级别 fig go.Figure() # 为每条线路添加轨迹 for _, row in df_lines.iterrows(): points parse_polyline(row[polyline]) lons [p[0] for p in points] lats [p[1] for p in points] fig.add_trace(go.Scattermapbox( lonlons, latlats, modelines, linedict(width2, colorblue), namerow[line_name], hoverinfotext, hovertextf{row[line_name]}: {row[start_stop]} → {row[end_stop]} )) # 添加站点 df_stops expand_stop_info(df_lines) fig.add_trace(go.Scattermapbox( londf_stops[longitude], latdf_stops[latitude], modemarkers, markerdict(size5, colorred), name公交站点, hoverinfotext, hovertextdf_stops[stop_name] )) # 设置地图布局 fig.update_layout( mapbox_styleopen-street-map, mapboxdict( centerdict(latcenter_lat, loncenter_lng), zoomzoom ), margin{r:0,t:0,l:0,b:0}, height800 ) fig.show()6.2 公交线路特征分析我们可以计算并可视化一些公交线路的基本特征def analyze_bus_lines(df_lines): 分析公交线路特征并可视化 :param df_lines: 包含线路信息的DataFrame # 计算基本统计量 avg_length df_lines[distance].mean() avg_stops df_lines[via_stops].apply(len).mean() avg_stop_distance avg_length / avg_stops print(f平均线路长度: {avg_length:.2f} km) print(f平均站点数量: {avg_stops:.1f}) print(f平均站距: {avg_stop_distance:.2f} km) # 线路长度分布 fig1 px.histogram(df_lines, xdistance, title公交线路长度分布, labels{distance: 线路长度 (km)}, nbins30) fig1.show() # 站点数量分布 df_lines[stop_count] df_lines[via_stops].apply(len) fig2 px.histogram(df_lines, xstop_count, title公交线路站点数量分布, labels{stop_count: 站点数量}, nbins30) fig2.show() # 票价分布 fig3 px.box(df_lines, ytotal_price, title公交线路票价分布, labels{total_price: 票价 (元)}) fig3.show()7. 公交网络分析与换乘计算获取完整的公交数据后我们可以进行更复杂的网络分析如计算换乘站点和最优路径。7.1 构建换乘网络首先我们需要识别哪些站点是换乘站被多条线路共享的站点def find_transfer_stops(df_stops): 找出换乘站点被多条线路共享的站点 :param df_stops: 包含站点信息的DataFrame :return: 换乘站点DataFrame # 统计每个站点被多少条线路使用 stop_usage df_stops.groupby(stop_name).agg({ line_id: nunique, longitude: first, latitude: first }).reset_index() # 筛选出换乘站被2条及以上线路使用 transfer_stops stop_usage[stop_usage[line_id] 2].sort_values( line_id, ascendingFalse) return transfer_stops7.2 计算线路间的换乘关系我们可以构建一个线路间的换乘关系矩阵def build_line_transfer_matrix(df_stops): 构建线路间的换乘关系矩阵 :param df_stops: 包含站点信息的DataFrame :return: 换乘关系矩阵DataFrame # 获取所有线路ID和名称 lines df_stops[[line_id, line_name]].drop_duplicates() line_ids lines[line_id].tolist() line_names lines[line_name].tolist() # 初始化换乘矩阵 transfer_matrix pd.DataFrame( 0, indexline_ids, columnsline_ids) # 找出每条线路的站点集合 line_stops {} for line_id in line_ids: stops set(df_stops[df_stops[line_id] line_id][stop_name]) line_stops[line_id] stops # 填充换乘矩阵 for i, line1 in enumerate(line_ids): for j, line2 in enumerate(line_ids): if i j: # 矩阵是对称的只需计算一半 common_stops line_stops[line1] line_stops[line2] transfer_matrix.loc[line1, line2] len(common_stops) transfer_matrix.loc[line2, line1] len(common_stops) # 添加线路名称作为索引和列名 transfer_matrix.index line_names transfer_matrix.columns line_names return transfer_matrix7.3 可视化换乘网络我们可以使用NetworkX库来可视化公交线路的换乘关系import networkx as nx import matplotlib.pyplot as plt def visualize_transfer_network(transfer_matrix, min_transfers1): 可视化公交线路换乘网络 :param transfer_matrix: 换乘关系矩阵 :param min_transfers: 显示的最小换乘站点数 G nx.Graph() # 添加节点线路 for line in transfer_matrix.index: G.add_node(line) # 添加边换乘关系 for i, line1 in enumerate(transfer_matrix.index): for j, line2 in enumerate(transfer_matrix.columns): if i j and transfer_matrix.loc[line1, line2] min_transfers: G.add_edge(line1, line2, weighttransfer_matrix.loc[line1, line2]) # 绘制网络图 plt.figure(figsize(15, 15)) pos nx.spring_layout(G, k0.15, iterations50) # 绘制节点和边 nx.draw_networkx_nodes(G, pos, node_size50, node_colorskyblue) nx.draw_networkx_edges(G, pos, width0.5, alpha0.5, edge_colorgray) # 添加标签只显示部分重要线路 degrees dict(G.degree()) important_lines [n for n in G.nodes() if degrees[n] 5] labels {n: n for n in important_lines} nx.draw_networkx_labels(G, pos, labels, font_size8) plt.title(青岛公交线路换乘网络) plt.axis(off) plt.show()8. 完整数据处理流程将上述步骤整合为一个完整的数据处理流程def full_pipeline(line_names, city青岛, keyNone, save_diroutput): 完整的公交数据处理流程 :param line_names: 公交线路名称列表 :param city: 城市名称 :param key: 高德API Key :param save_dir: 输出目录 import os os.makedirs(save_dir, exist_okTrue) # 1. 批量获取公交线路数据 print(开始获取公交线路数据...) df_lines batch_get_bus_lines(line_names, citycity, keykey, save_pathos.path.join(save_dir, bus_lines_raw.csv)) # 2. 展开站点信息 print(处理站点数据...) df_stops expand_stop_info(df_lines) df_stops.to_csv(os.path.join(save_dir, bus_stops.csv), indexFalse, encodingutf_8_sig) # 3. 保存轨迹数据 print(处理轨迹数据...) trajectories [] for _, row in df_lines.iterrows(): points parse_polyline(row[polyline]) for i, (lng, lat) in enumerate(points): trajectories.append({ line_id: row[line_id], line_name: row[line_name], point_index: i, longitude: lng, latitude: lat }) df_trajectories pd.DataFrame(trajectories) df_trajectories.to_csv(os.path.join(save_dir, bus_trajectories.csv), indexFalse, encodingutf_8_sig) # 4. 保存到SQLite数据库 print(保存到数据库...) save_to_sqlite(df_lines, os.path.join(save_dir, qingdao_bus.db)) # 5. 分析换乘站点 print(分析换乘站点...) transfer_stops find_transfer_stops(df_stops) transfer_stops.to_csv(os.path.join(save_dir, transfer_stops.csv), indexFalse, encodingutf_8_sig) # 6. 构建换乘矩阵 print(构建换乘矩阵...) transfer_matrix build_line_transfer_matrix(df_stops) transfer_matrix.to_csv(os.path.join(save_dir, transfer_matrix.csv), encodingutf_8_sig) # 7. 可视化 print(生成可视化...) plot_bus_lines(df_lines) analyze_bus_lines(df_lines) visualize_transfer_network(transfer_matrix) print(数据处理完成)9. 实际应用与扩展获取的公交数据可以应用于多种场景公交线路优化分析线路重叠、站点分布提出优化建议换乘规划基于换乘网络开发公交出行规划工具可达性分析计算不同区域通过公交出行的便利程度时空分析结合运营时间分析公交服务的时空覆盖可视化大屏构建公交运营监控可视化系统对于大规模应用可以考虑以下扩展定时自动更新数据每日/每周结合实时公交API获取车辆位置信息集成其他交通方式数据地铁、共享单车等开发Web应用或移动应用提供公交查询服务