Carla全局路径规划从入门到实战:路网、API与路线跟踪

发布时间:2026/10/21 4:27:28
Carla全局路径规划从入门到实战:路网、API与路线跟踪 做carla仿真很多朋友第一次接触“全局路径规划”这个词是在看别人Demo演示的时候车从A点出发遇到路口自己转弯一路开到B点看起来非常顺滑。轮到自己动手往往卡在第一件事上——“车能跑但它怎么知道该往哪走”这其实就是在问carla里的全局路径规划到底是怎么实现的。这篇文章我会直接从实操角度讲清楚三件事全局路径在carla里由谁生成、用什么API生成、生成之后怎么让车真正跟过去。同时会把我在实际调试中遇到的各种坑一并写出来包括路网吸附、路线多样性、红绿灯配合等细节。如果你是刚接触carla、准备做自动驾驶仿真验证的开发者这篇文章很适合你按顺序看完应该能亲手跑通一个“从A点到B点”的完整闭环。1. 在carla里谈“全局路径规划”先分清三件事很多新手会陷入一个误区以为全局路径规划让车自动驾驶。实际上carla里“让车自己动”这件事牵扯到三层完全不同的逻辑我先帮你把这三层拆开。第一层是地图路网。它描述的是“哪里可以开车”包括车道、路口、红绿灯、限速信息。carla把地图定义为一张路网图Graph而不是一张图片。你可以把路网理解成一个城市的地铁线路图它只画轨道和站点不管火车怎么走。在carla里这个路网的结构来自OpenDRIVE文件加载地图之后通过world.get_map()拿到的carla.Map对象就是路网的操作入口。第二层是全局路径规划。它解决的是“从A到B走哪条路”输出的是一条由航点Waypoint组成的参考路线这些航点都落在车道中心或者合理行驶位置上。它不代表车一定会精确经过每个点只代表一条宏观的、可通行的路径。类似于导航App给你规划的一条路线沿途路线是固定的但具体怎么压线、怎么刹停是司机自己的事。第三层是局部规划与控制。它负责把全局路径翻译成加速、刹车、转向指令让车实际动起来。carla里的LocalPlanner就是干这个的它会参考全局路径点结合当前车速、前方障碍物、路口情况输出carla.VehicleControl。从这套分工可以看出来全局路径规划是连接地图和车辆控制的中间层。不理解这个分层后面看代码很容易混有人调了traffic_manager.set_autopilot(True)以为自己在做全局路径规划其实那只是用了carla内置的交通管理器自动开车有人调用GlobalRoutePlanner规划路径却不喂给局部规划器发现车根本不动于是跑来回折腾。1.1 Traffic Manager 的路线机制和你想的不一样carla自带的Traffic Manager简称TM确实有路线规划能力。你给一辆车设置set_autopilot(True)之后TM会让它在路网上漫游遇到路口随机选方向像网约车满街跑。TM也支持你给它一条明确路线让它沿着指定路径开相关接口在官方文档里可以找到。但TM的路线机制和前文说的GlobalRoutePlanner不是一回事。TM的重点是“模拟正常交通流”它要管一大批车的跟随、变道、避让它的路线规划是内部的、面向驾驶行为的你拿不到清晰的、带语义的路线点数据也很难在它上面叠加自己的算法。所以如果只是想让Demo演示“一辆车开走”用TM没什么问题但如果你要做路径规划相关的研究或二次开发比如对比不同规划代价、测试极限工况还是得用GlobalRoutePlanner加上自己的控制逻辑。1.2 全局规划器和局部规划器的分界我见过一种比较普遍的误解以为全局路径规划器会自动控制方向盘。拿carla自带的GlobalRoutePlanner举例它的核心工作集中在路径搜索上输入是起终点航点输出是一串(waypoint, road_option)元组。它不关心车速、不管障碍物、不输出油门刹车这些全是下游局部规划器的事。因此一条完整的自动驾驶仿真链路大致是从地图取路网信息构建规划器用全局规划器搜索路线把路线作为参考线传给局部规划器局部规划器结合车辆状态输出控制指令车辆执行指令循环运行。后面第3、4节会分别演示第2步和第3步的具体写法。2. 路线从哪里来OpenDRIVE、路网与航点现在我们把注意力放在“地图路网”这一层。你想在carla里做任何路径规划都得先理解它内部的路网表达方式。carla的地图文件核心是OpenDRIVE格式。这是一种用XML描述道路拓扑的标准格式里面定义了每条道路的几何形状、车道数量、车道线类型、限速、路口连接关系。carla启动地图时会解析这个文件在后台构建出一张完整的“路网图”再向外提供carla.Map接口。你在代码里做的第一件事通常是import carla client carla.Client(localhost, 2000) client.set_timeout(10.0) world client.get_world() tmap world.get_map()tmap就是路网的核心对象。它有几个常用方法tmap.get_spawn_points()返回所有可以生成车辆的位置本质是一个carla.Transform列表。tmap.get_waypoint(location)给定一个世界坐标返回离它最近的可驾驶路点。tmap.get_topology()返回路网的拓扑边列表每条边是一对(航点, 航点)表示从前者可以单向行驶到后者。tmap.generate_waypoints(distance)按固定间距批量生成路网上的航点。其中get_topology()特别值得留意它是做全局路径规划的基础。你可以把返回结果理解成一张有向图的所有边topo tmap.get_topology() print(len(topo)) # 边的数量 print(topo[0]) # (waypoint, waypoint)拿到有向图之后路径搜索在概念上就变成了标准的图搜索问题节点是航点边是航点之间的可通行连接权重是路径长度。而carla自带的GlobalRoutePlanner本质上做的就是这件事——解析get_topology()的数据然后在上面跑类似Dijkstra/A*的搜索算法。2.1 为什么“两点之间直线最短”在这里不成立做路径规划最容易产生的直觉是起点到终点画条直线不就行了但在自动驾驶场景里车必须沿着可驾驶道路走不能横穿草地、不能越过隔离带。carla的路网是一张有向图所以规划必须在图里搜索而不是在平面坐标上算几何距离。这就引出路径代价的问题。默认情况下GlobalRoutePlanner按路径长度作为主要代价越短越好。但在真实应用中你可能还要考虑转弯惩罚、车道变更惩罚、是否走主路等因素。carla自带规划器对这些的支持比较有限想深入做代价函数一般得在外部规划栈里实现这个我在第6节会展开讨论。2.2 航点是世界坐标也是路网引用carla的Waypoint对象有点特殊它既包含世界坐标transform又包含路网引用信息road_id、lane_id、s等。其中s是纵向弧长表示沿道路方向的位置。这个设计给了我们一个很大的便利拿到一个航点就知道它属于哪条路的哪条车道、在当前车道的什么位置做路线跟踪时非常有用。当你调用tmap.get_waypoint(location)时carla会从路网里找一个“投影到道路上的位置”这个投影过程近似于“吸附”操作。多数情况下传一个车辆坐标进去返回的就是它所在车道中心的航点。需要提醒的是get_waypoint()的默认行为是投影到可驾驶车道如果你传入的位置在路网外很远它会返回None。这一点在设定起终点时特别关键具体坑我放在第7节踩坑记录里讲。3. GlobalRoutePlanner 实操从A点到B点的完整代码前面铺垫了这么多现在终于可以写代码了。carla在PythonAPI/carla/agents/navigation/目录下提供了现成的GlobalRoutePlanner路径搜索、航点生成、道路方向标注都封装好了我们直接调用就行。3.1 初始化规划器注意采样距离参数使用GlobalRoutePlanner之前要先创建GlobalRoutePlannerDAO它负责从地图读取路网数据。构造时需要传入一个采样间距参数通常取1.0到5.0之间单位是米。这个参数决定了路网被拆成多细的节点间距越小路径越平滑但节点越多搜索越慢间距太大路径会显得很粗转弯处可能出现“切弯”的效果。我个人的习惯是在Town地图做普通演示用2.0米做精细的路径跟踪实验时用1.0米。初始化代码如下from agents.navigation.global_route_planner import GlobalRoutePlanner from agents.navigation.global_route_planner_dao import GlobalRoutePlannerDAO sampling_distance 2.0 dao GlobalRoutePlannerDAO(tmap, sampling_distance) grp GlobalRoutePlanner(dao) grp.setup()setup()会提前解析地图拓扑构建搜索用的图结构。因为这一步会比较耗时所以不要每帧都新建规划器最好全局初始化一次后面只调trace_route()。3.2 指定起终点先做路网吸附规划前先准备起点和终点的坐标。这两个坐标必须能投影到路网上最简单的做法是直接用world.get_map().get_waypoint()做吸附start_location carla.Location(x100.0, y50.0, z0.5) end_location carla.Location(x320.0, y-30.0, z0.5) start_wp tmap.get_waypoint(start_location, project_to_roadTrue) end_wp tmap.get_waypoint(end_location, project_to_roadTrue) if start_wp is None or end_wp is None: print(起点或终点无法投影到路网请调整坐标) exit(1)project_to_roadTrue表示允许把点投影到最近的车道中心线上。如果你不想让起点被吸附到对面车道还可以结合lane_typecarla.LaneType.Driving来过滤普通场景用默认值就行。3.3 trace_route核心搜索调用有了吸附好的起终点航点一行命令就可以得到全局路径route grp.trace_route(start_wp, end_wp)route是一个列表长度取决于路线的实际距离和采样间距。每个元素是(waypoint, road_option)元组road_option是枚举值用来描述该航点的特征常见的有carla.RoadOption.LANEFOLLOW正常沿车道行驶carla.RoadOption.CHANGELANELEFT/CHANGELANERIGHT需要向左/向右变道carla.RoadOption.LEFT/RIGHT左转/右转carla.RoadOption.STRAIGHT直行。为什么需要road_option因为局部规划器在做变道、转弯决策时需要知道前方的“语义动作”光靠坐标算不够。比如碰到路口坐标只能告诉你有岔路但只有road_option能告诉你这个岔路是右转还是直行。我一般会先打印前几个点确认路线for i, (wp, option) in enumerate(route[:5]): print(i, wp.transform.location, option)正常的话打印结果是从起点位置逐步延伸到远方的坐标序列option在路口附近变化明显。3.4 把route转成后续可用的点序列GlobalRoutePlanner返回的route是(waypoint, road_option)的元组列表。如果你的下游是自己写的跟踪算法通常只需要坐标route_locations [wp.transform.location for wp, _ in route]如果你要把路线保存成文件方便离线回放或对比我习惯存成JSONimport json route_data [] for wp, option in route: loc wp.transform.location route_data.append({ x: loc.x, y: loc.y, z: loc.z, road_option: str(option) }) with open(route.json, w) as f: json.dump(route_data, f, indent2)这里多存一个road_option是值得的后续分析车辆行为时非常有用比如统计一次出行中有多少次变道、在哪个路口转弯。光存坐标的话这些信息重新算很麻烦。4. 生成路线之后车怎么沿着它走路径点只是“建议路线”想让车动起来必须把路线交给局部规划器它负责输出carla.VehicleControl。carla自带LocalPlanner用来做这件事非常方便。4.1 LocalPlanner 接管set_global_plan 的用法LocalPlanner在agents.navigation.local_planner里。初始化时需要一辆车和一个配置字典配置里可以指定目标速度、是否允许变道等from agents.navigation.local_planner import LocalPlanner vehicle world.spawn_actor( world.get_blueprint_library().find(vehicle.tesla.model3), start_wp.transform ) config { target_speed: 8.0, # 目标速度单位m/s lane_change_prob: 0.0, # 随机变道概率全局规划模式设0 } lp LocalPlanner(vehicle, opt_dictconfig)然后调用set_global_plan(route)把全局路线喂给它lp.set_global_plan(route)这句话后面的逻辑很多人不理解我拆开讲LocalPlanner会从全局路线中维护一个“当前要跟踪的窗口”每步运行时它会根据车辆当前位置找到最近的路径点生成目标航向和参考速度再用一个纯跟踪控制器加上PID速度控制输出控制量。所以它需要全局路线作为参考线但并不要求车辆机械地经过每一个点。循环控制可以写成import time while True: # 局部规划器输出控制指令 control lp.run_step() # 判断是否到达终点 if lp.done(): print(到达终点) break vehicle.apply_control(control) time.sleep(0.05)lp.run_step()内部会做几件事更新车辆状态、计算当前位置与目标点的横向偏差、输出转向和速度控制。所以只要把全局路线传进去车就能沿着路线走不需要你手动算转向角。4.2 一个最小闭环的完整逻辑把前面所有代码拼起来最小闭环如下import carla from agents.navigation.global_route_planner import GlobalRoutePlanner from agents.navigation.global_route_planner_dao import GlobalRoutePlannerDAO from agents.navigation.local_planner import LocalPlanner client carla.Client(localhost, 2000) world client.get_world() tmap world.get_map() # 规划全局路径 dao GlobalRoutePlannerDAO(tmap, 2.0) grp GlobalRoutePlanner(dao) grp.setup() start_wp tmap.get_waypoint(carla.Location(x100.0, y50.0, z0.5)) end_wp tmap.get_waypoint(carla.Location(x320.0, y-30.0, z0.5)) route grp.trace_route(start_wp, end_wp) # 生成车辆 bp world.get_blueprint_library().find(vehicle.tesla.model3) vehicle world.spawn_actor(bp, start_wp.transform) # 创建局部规划器并喂入全局路线 lp_config {target_speed: 8.0} lp LocalPlanner(vehicle, opt_dictlp_config) lp.set_global_plan(route) # 闭环运行 while not lp.done(): vehicle.apply_control(lp.run_step()) print(到达终点)这段代码麻雀虽小五脏俱全先规划路径、再生车、然后跟踪路径。很多研究Demo的前置逻辑就是这么写的。4.3 只有全局路线远远不够路口、红绿灯和限速代码跑通之后你可能很快发现一个尴尬情况车在路口不减速甚至闯红绿灯。这是因为LocalPlanner默认只做路径跟踪它不管交通规则。想让它遵守交规要么启用TM的自动驾驶逻辑要么自己判断红绿灯和路口。carla里获取红绿灯状态比较简单世界对象里有world.get_actors().filter(traffic.traffic_light)你可以遍历红绿灯判断它当前的颜色是否影响车辆当前所在的车道。但这一步要做得精细并不容易路口可能有多个红绿灯你得找到“管着”当前车道的那一个。我的做法是先收集车辆前方一定距离内的红绿灯再判断红绿灯的state和触发区域。carla的traffic_light.get_state()返回红绿灯状态车辆压到触发线上时会看到vehicle.is_at_traffic_light()。最简单可用的方案是顶一个“碰到红绿灯就减速”的逻辑if vehicle.is_at_traffic_light(): light vehicle.get_traffic_light() if light.get_state() carla.TrafficLightState.Red: # 减速逻辑比如把目标速度降为0 pass如果你不想处理这些细节又想看点效果就用TM管理交通规则同时固定路线这个方案适合做场景展示不适合做算法研究。实际项目怎么取舍取决于你在调什么。4.4 自己写路线跟踪时要注意什么总有人问能不能不依赖LocalPlanner自己写PID纯跟踪。当然可以路线跟踪并不神秘但自己做会遇到几个麻烦第一是航点方向问题。每个Waypoint自带transformtransform.get_forward_vector()就是该航点处的车道方向计算目标航向时可以用math.atan2从朝向量反推。很多新手直接拿后一个航点减前一个航点算方向在路径密集时可以用但在静止航点处会出除零问题。第二是切换目标点的时机。控制循环中不能一直追同一个路径点否则转弯时会“切内线”。一般做法是当车辆当前位置与当前目标点的距离小于某个阈值比如1.5米时把目标点索引往后移。这个阈值不能太大太大容易抄近路太小则会产生摆动。第三是速度与转向的协调。全局路径在弯道处只给“位置参考”不给弯道速度建议。你自己跟踪时最好根据road_option判断前方要转弯提前降速。例如检测到RoadOption.LEFT或RIGHT时把目标速度压到3-4 m/s过弯后再恢复巡航速度。如果只是想快速验证全局路径规划是否合理直接用LocalPlanner没问题如果你要研究的正是路径跟踪控制那么自己实现跟踪是值得的LocalPlanner的代码也可以作为很好的参考。5. 把全局路径画出来验证与调试路径规划这种事光靠打印坐标很难判断对不对。我在调试时几乎每次都依赖carla的world.debug接口它可以在地图上画出线条、点、箭头及时看到路线形状。5.1 DebugHelper 快速画线debug world.debug for i in range(len(route) - 1): loc_a route[i][0].transform.location loc_b route[i1][0].transform.location debug.draw_line(loc_a, loc_b, thickness2.0, colorcarla.Color(0, 255, 0), life_time30.0)这里用draw_line逐段连接相邻航点30秒后自动消失。如果你是在运行中动态查看life_time可以设短一点比如5秒。另一种方式是画点for wp, _ in route: debug.draw_point(wp.transform.location, size0.1, colorcarla.Color(255, 0, 0), life_time30.0)画线比画点更能直观反应路径的连续性两者配合看最清楚。我摸索出来一个经验起点到终点路线用绿色画线在明显不对的地方比如穿墙、逆行用红色画点标记这样一眼就能发现哪里出了问题。5.2 保存路线、批量回放画出来的路线当下能看但如果你要调参数做对比比如对比不同采样间距下的路线差异就要保存路线数据。前面已经写了保存JSON的代码回放时可以直接读JSON再把点画出来不需要再次规划。更实用的做法是把多条路线一次性画出来用不同颜色区分。比如使用1.0米和4.0米间距各规划一次分别用绿、红两色画出路径对比它们在弯道处的差异。这样能直观看出采样间距对路径平滑度的影响方便为当前地图选择合适的间距。5.3 判断路径合理性的几个指标我们怎么知道一条全局路径“好不好”我一般看四个东西是否连续相邻航点距离不能有突然的大跳变正常应该是连续的。是否逆行/压线路径航点应该始终在当前车道的行驶方向内可以用wp.is_intersection()结合道路方向辅助判断。是否明显绕路把路线总长度和起终点欧氏距离对比如果比例过大可能规划器选择了奇怪路线。转弯是否平滑统计road_option中LEFT/RIGHT的位置分布如果在不该有路口的地方出现转弯说明坐标或地图不匹配。最后一条特别实用。carla地图更新或者自定义地图加载后路网可能和你预想的不一样导致规划器在某个路口走出了奇怪的转弯。画出路径后通常一眼就能定位到问题路段。6. 更进一步让外部规划栈接管全局路径写到这儿都是carla自带的路线规划能力。但在很多实际项目里carla只是作为仿真环境存在真正的全局路径规划是外部规划栈完成的carla只负责执行规划结果。这一节给你梳理一下接法不展开太多代码主要是思路。6.1 导出OpenDRIVE数据你的外部规划器要想做全局路径规划必须知道“路在哪里”。carla可以把当前地图的OpenDRIVE数据导出来opendrive_contents tmap.to_opendrive() with open(map.xodr, w) as f: f.write(opendrive_contents)拿到这个文件你就可以在外部用任意路网解析库或其他工具去构路网、做路径搜索。很多基于真实地图数据的规划器都兼容OpenDRIVE把carla的地图文件喂进去基本可以复用大部分逻辑。6.2 通过中间件交互目标点下发、路径回传在实际系统中全局路径规划模块通常运行在一个独立的进程里通过中间件与仿真环境通信。常用的模式是仿真环境发布车辆当前位姿、地图信息上层模块接收“目标点”调用规划器生成路线路线经中间件回传给仿真端再由局部规划器执行。carla有对应的中间件桥接方案可以把/carla/ego_vehicle/waypoints之类的消息和外部规划器对接起来。这种架构的优点是路线生成逻辑完全自主可控代价函数、约束条件都可以自己定义不受carla自带规划器限制。6.3 你自己的规划器应该插在哪很多人问我想在carla里测试自己的全局路径规划算法是从头实现A*还是直接改GlobalRoutePlanner我的建议是分两种情况。如果算法研究重点是路径搜索本身比如改进图搜索、设计新的代价函数那你应该把规划器放在外部只利用carla的地图和车辆控制接口不要在carla的agents目录里改代码否则后续升级carla版本会很痛苦。做法是用to_opendrive()导出地图在外部做路网解析和搜索最后把路线点通过脚本写入carla或直接让车跟踪。如果你只是需要一条合理路线来驱动车辆做感知或控制测试那就继续用carla自带的GlobalRoutePlanner它已经够用了不值得自己重复造轮子。方案选型要跟研究目的一致不要为了“看起来高级”把所有东西都重写一遍。7. 实战踩坑记录最后这部分我把这几年在carla全局路径规划上踩过的坑集中写一下希望你少走弯路。7.1 起点终点不在路网上最常见的第一坑这是“新手必踩”的第一坑。你随便给两个坐标比如(0, 0, 0)和(100, 0, 0)然后调trace_route可能报错可能返回一个奇怪的绕路路线甚至直接返回None。原因很简单起点或终点无法投影到可驾驶路网上。carla的get_waypoint()只有在坐标附近存在可驾驶道路时才返回有效航点否则返回None。调试方法就是先打印print(tmap.get_waypoint(carla.Location(x0, y0, z0)))如果输出是None就说明这个位置不在路网上。正确的做法是先用get_spawn_points()挑一个已知合法的位置或者把你想要的位置向最近的道路方向手动偏移后再查询。7.2 路径“不跟手”采样间距选错了采样间距太大会导致路径点很稀疏转弯处看起来像折线局部规划器在跟踪时容易出现“切弯”现象间距太小时路网节点爆炸规划耗时骤增setup()慢到怀疑人生。我测过一个较大的Town地图1.0米间距的图构建时间差不多是4.0米间距的4倍。我的经验是先按地图规模选一个中等间距跑通比如2.0米确认路径形状正确后再考虑缩减。如果只是验证功能2.0米足够如果要做精确控制实验再加密到1.0米。7.3 红绿灯和Stop标志会让局部规划器“失速”有时候路径没问题路线看着也对但车到路口突然停下来不走。这种问题多是因为车辆跑到红绿灯或Stop标志的触发区后LocalPlanner或TM检测到停车条件但后续没有判定条件解除导致一直傻等。排查思路是打开carla的调试信息看车辆是否is_at_traffic_light()再看红绿灯状态是不是永远不变。如果用的是TM检查车辆是否被TM“卡”在某个route里可以用traffic_manager.get_all_vehicles()之类的接口查状态。多数情况下给TM重新设置路线或恢复autopilot能解决。7.4 别忘了坐标系的Z值carla的Location必须包含合理的Z值。很多朋友给起点终点时省略Z默认是0但路面高度可能是0.2或0.4投影时还好一旦直接把Location赋给车辆生成或某个跟踪算法车可能看起来会“悬空”或“钻地”。规范做法是统一从waypoint.transform.location拿坐标不要自己手填Z。需要自定义位置时给一个略高于地面的Z然后靠get_waypoint()吸附拒绝手算路面高度。7.5 全局路径是参考线不是物理轨迹这一点前面提过但还是值得作为压轴提醒。路径规划器生成的路线本质是参考线。车辆去跟踪它时因为控制延迟、轮胎模型、速度差异实际轨迹一定不会和参考线完全重合。所以在验证时不要拿“路径点是否经过”作为成功标准而要看车辆是否沿着正确的车道到达终点、是否没有越实线、是否在合理时间到达。如果非要拿参考线当物理轨迹用最简单的后果是你的横向控制稍有误差车辆就会压到相邻车道然后在仿真里撞上其它车。这个错误我在早期做实验时犯了很多次与其反复调跟踪参数不如先想清楚全局路径和车辆实际轨迹在数学上是两个不同的东西。写到这里关于carla全局路径规划的核心链路就完整了理解路网 → 构建规划器 → trace_route → 喂给LocalPlanner → 调试可视化 → 按需接入外部规划栈。整套流程说透了其实不复杂最难的地方反而在细节处理上比如坐标吸附、路面Z值、采样间距、下游跟踪方式。这些坑每一个单看都不大但都踩一遍非常消耗耐心。希望这篇文章能帮你在第一次接触carla全局路径规划时少走我这几年走过的弯路。如果你在实际调试中遇到这里没覆盖到的问题不妨先把路径画出来再对着路网一点点分析问题往往就浮出水面了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询