news 2026/10/20 22:16:09

ArcPy批量出图实战:Mxd模板替换与PNG导出自动化脚本

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ArcPy批量出图实战:Mxd模板替换与PNG导出自动化脚本

前阵子接了个不大不小的活儿:六个行政区图层,每个都要导出一张标准尺寸的PNG,再配一份能继续编辑的Mxd工程文件。如果手动开ArcMap,调一个图层范围、设比例尺、导图、另存Mxd,一套下来少说五六分钟,六十套就是大半天,还容易漏改标题。后来我用ArcPy写了一个循环,从读模板、替换数据源、导出图片到另存Mxd全自动,跑了不到半小时。这篇就把这套“零门槛小白版”的做法完整拆开讲,照着抄就能用。

1. 先搞清楚:这个脚本要解决什么问题

1.1 什么样的重复劳动适合交给ArcPy

开始之前,先别急着复制代码。你首先要判断一件事:手里的活到底适不适合脚本化。我总结了一个很土的标准——只要你需要连续点鼠标十次以上,并且每一步都在做“打开文档、缩放、导出、保存”这类重复动作,就值得让脚本接手。ArcPy在这类场景里有两个杀手锏:一是能用代码读写ArcMap的Mxd文档结构,二是能直接调用导出图片的底层工具。换句话说,凡是你在界面上能反复点的操作,脚本基本都能做,而且速度更快、不容易漏。

比如最常见的分乡镇出图:一个县域底图,叠加二三十个乡镇界,逐个乡镇导出PNG。手工操作需要不断切换制图范围、改图名、导出,还要防止上一次的比例尺串到下一张。用ArcPy把这些动作写进循环,再多的乡镇也只是把循环多跑几轮。再比如你手里有几十个图层需要套用同一套符号和版式时,脚本能保证每一张图的图例、比例尺、指北针、标题字体完全一致,不会因为手动操作产生细微偏差。更适合的场景,是那种“数据更新了,图要重出”的固定任务——每次只要重新跑脚本,输出目录里的图片和Mxd就整整齐齐地刷新一遍。

不过也得说清楚,ArcPy不是万能的。如果你需要每张图都做复杂的交互式调整,比如手工修图、临时改注记位置、微调制图表达,那脚本会变得非常繁琐。此时最好的方案是脚本处理大头,剩下几张特殊图再手动收尾。聪明的自动化从来不是消灭人工,而是把人工从重复劳动里解放出来。

1.2 为什么出图和生成Mxd要放在一起做

很多初学者不理解,我只要图片,为什么非要同时生成Mxd?图片导出后,成果看起来已经完成了,但真正做项目的人都知道,图片是“死”的,Mxd才是“活”的。客户看完图提一句“标题换个写法”“符号颜色再深一点”,如果只有PNG,你还得重新找数据、新建工程、重新配符号。如果当时顺手留了一份Mxd,后续修改就是打开文档改两笔再导出一遍的事。

更重要的是,Mxd保存了制图状态:数据框范围、图层顺序、符号系统、页面布局、图例位置。这些信息用脚本生成时是高度统一的,如果靠手工一个个另存,很容易出现“这张图范围没对上”“那张图比例尺还是默认的”之类的毛病。脚本循环里把出图和保存Mxd放在同一个处理单元里,保证一张图像对应一份Mxd,命名一致、内容一致,后期追溯起来极其省心。

具体到技术实现,ArcPy里并没有一个叫“CreateMapDocument”的函数能直接凭空造一个Mxd。业内常规做法,是把一个做好的模板Mxd复制出多个实例,保存时用saveACopy另存为新文件。这也是为什么我会把这一节排在环境准备之前——你要先在脑子里把“复制模板—改数据—导出—另存”这条主线立起来,后面看代码才不晕。

2. 动手前准备:版本、目录和模板

2.1 版本怎么选:ArcMap还是ArcGIS Pro

ArcPy分两大体系,容易搞混。ArcMap 10.x时代用的是arcpy.mapping模块,专门操作Mxd文档;ArcGIS Pro时代改成了arcpy.mp模块,操作的是Aprx工程文件。标题里要生成Mxd,所以必须使用ArcMap 10.x环境,而不是ArcGIS Pro。哪怕你电脑上装了Pro,也别指望这段代码直接复制过去能跑,Mxd和Aprx文件格式完全不同。

另一个需要提前确认的坑是Python版本。ArcMap 10.x自带的是Python 2.7,微软官方早就不维护了,所以脚本里尽量别写Python 3的新语法,比如f-string、pathlib这类,老老实实用format或者百分号拼接。我在代码里用的print也兼容2.7。如果你习惯用Anaconda或者系统装了Python 3,请不要直接双击py文件,ArcPy这个库是跟着ArcGIS许可证走的,必须用ArcGIS自带的Python解释器来运行。后面第四章会专门讲怎么跑最稳。

还需要注意ArcMap的版本小差异。10.2以后arcpy.mapping基本稳定,replaceDataSource、ExportToPNG这些接口都在。建议用10.4以上版本,遇到怪问题的概率更低。如果你的机器里装了多个ArcGIS版本,运行脚本时要确认调用的python.exe来自同一个版本,否则会出现“Import arcpy失败”这类最低级也最打击人的报错。

2.2 目录规划:先想好数据往哪放

批量脚本最怕路径混乱。我在实际项目里吃过亏,一开始把所有输出都扔到同一个文件夹,跑了半小时后想找某一个乡镇的图,文件名前缀还都一样,翻了好几层才找到。后来学乖了,任何批量任务都按固定结构安排目录。下面这个结构可以作为参考:

D:\project\ ├─ 00_模板 │ └─ 底图模板.mxd ├─ 01_数据 │ ├─ 区县A.shp │ ├─ 区县B.shp │ └─ 区县C.shp ├─ 02_输出 │ ├─ 图片 │ └─ 工程文件

这样做的理由很直接:模板和数据分开,避免脚本在扫描数据时把模板Mxd也当成图层源;输出目录单独建,后期打包成果时直接拷02_输出整个文件夹走人。所有路径建议用英文,不要带空格,更不要带中文。虽然ArcMap对中文路径的兼容性比早期好很多,但在Python 2.7环境下,中文路径偶尔会触发UnicodeDecodeError,为了省事,英文路径最安全。

如果你的数据比较多,建议把要处理的要素类全部放进同一个文件地理数据库,或者同一个shapefile文件夹。因为后面代码里用到arcpy.ListFeatureClasses,它只会列出当前工作空间下的要素类,这样循环逻辑最简单。如果数据分散在好几个文件夹,你需要再写一层os.walk或循环拼接路径,对小白并不友好。

2.3 做一个不会出错的模板Mxd

模板是整个方案的灵魂。我做过的批量出图脚本,最后出问题几乎没有一次是代码本身有bug,全怪模板少了元素或者图层名对不上。制作模板的步骤不复杂,但每一步都有讲究。

先在ArcMap里新建一个空白地图文档,插入一个数据框,然后随便加载一个占位图层。这个占位图层建议用一个最简单的shapefile,名称必须和脚本里约定的“替换图层”完全一致,包括引号里的内容。脚本会按图层名找到它并替换数据源,这个名字一旦不一致,循环就会跳过替换,最后导出的就是一张占位图。图层名最好是纯英文或数字,尽量避免中文,因为Python 2.7的字符串编码坑很多,少碰为妙。

接下来做页面布局。在布局视图里插入图例、比例尺、指北针、标题文本框等元素。图例必须关联到占位图层上,这样后面替换数据源时,图例条目才会跟着刷新。标题文本框建议在属性里设置一个固定的元素名,比如txt_title,脚本里通过ListLayoutElements找到这个元素名来改文字。ArcMap布局元素默认名称往往又长又乱,手动改一个简短英文名,脚本代码会清爽很多。

最后,把模板另存到00_模板目录下。存好之后,最好自己手动打开一次模板,确认占位图层能正常显示,图例不是空的。模板一旦做好,后面所有的事情都变成流水线了。这也是我最想强调的一点:模板越规范,脚本越省心。

3. 核心代码:替换数据源、出图、另存Mxd

3.1 核心思路:从“一张图”推到“一批图”

写批量脚本之前,千万别直接写for循环,先把“一张图怎么完成”想清楚。我习惯先假设只有一个要素类需要出图,手工流程是什么样的:打开模板Mxd,把某个图层换成目标数据,缩放到目标数据范围,改一下标题文字,导出PNG,另存为一份新的Mxd。把这个串行流程写成代码,就是脚本的正文部分。

然后才考虑循环:这一批要处理的要素类列表是什么?用arcpy.ListFeatureClasses获取,或者用os.listdir过滤出.shp文件。每跑一个要素类,就重新读取一次模板Mxd,执行刚才的那串流程,输出文件名用要素类名称。这样一个抽象过程,既能让你写代码时思路清晰,也能在后期排查问题时快速定位每一步。

这里特别说一下“重新读取模板”这个动作。有些新手会把模板Mxd放在循环外面只读一次,然后不断saveACopy。这种做法非常危险,因为saveACopy不会改内存里那个Mxd对象,但你对数据框、图层的修改会累积在同一个对象上,第二轮循环时图层可能已经被替换过很多次,状态完全不可控。每轮重新MapDocument(template_mxd)最保险,代价就是多花一点读取时间,但对小白来说,正确性远比那零点几秒重要。

3.2 完整脚本(建议直接抄)

下面这个脚本我整理成了可直接运行的版本,路径要改成你自己的目录。它适合处理同一个文件夹下的多个shapefile,或者同一个地理数据库里的多个要素类。

# -*- coding: utf-8 -*- import arcpy import os arcpy.env.overwriteOutput = True # ===== 用户需要修改的路径 ===== template_mxd = r"D:\project\00_模板\底图模板.mxd" data_workspace = r"D:\project\01_数据" out_img_dir = r"D:\project\02_输出\图片" out_mxd_dir = r"D:\project\02_输出\工程文件" # ============================== if not os.path.exists(out_img_dir): os.makedirs(out_img_dir) if not os.path.exists(out_mxd_dir): os.makedirs(out_mxd_dir) arcpy.env.workspace = data_workspace fc_list = arcpy.ListFeatureClasses() for fc in fc_list: # 每次从模板重新读取一份新的地图文档 mxd = arcpy.mapping.MapDocument(template_mxd) df = arcpy.mapping.ListDataFrames(mxd)[0] # 找到占位图层,替换数据源 target_layer = None for lyr in arcpy.mapping.ListLayers(mxd): if lyr.name == "替换图层": lyr.replaceDataSource(data_workspace, "SHAPEFILE_WORKSPACE", fc, False) lyr.name = fc target_layer = lyr break if target_layer is None: print("模板里没找到占位图层,请检查模板!") continue # 把数据框范围缩放到当前数据范围 df.extent = target_layer.getExtent() # 修改标题文本元素 for elm in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"): if elm.name == "txt_title": elm.text = "专题图:" + fc # 导出PNG out_png = os.path.join(out_img_dir, fc + ".png") arcpy.mapping.ExportToPNG(mxd, out_png, width=1600, height=1100, resolution=150) # 另存为新的Mxd out_mxd = os.path.join(out_mxd_dir, fc + ".mxd") mxd.saveACopy(out_mxd) print("生成完成:" + fc) # 释放内存,避免批量处理时进程占用过高 del mxd

这段代码不长,但每一行都值得解释清楚。arcpy.env.overwriteOutput = True表示输出文件存在时直接覆盖,不会弹“文件已存在”的提示框。arcpy.env.workspace = data_workspace把脚本的工作空间切到数据目录,后面的ListFeatureClasses才能列出这些要素类。replaceDataSource是替换数据源的核心方法,参数分别是数据工作空间路径、工作空间类型、数据集名称、是否验证。最后那个False是不做额外验证,速度更快,如果怕出错可以改成True,能提前发现数据源填错的问题。

3.3 数据源类型参数怎么填

很多小白卡在replaceDataSource的第二个参数。这里给一个简单速查:如果你处理的是shapefile,工作空间类型填SHAPEFILE_WORKSPACE,dataset_name填不带.shp后缀的文件名;如果你处理的是文件地理数据库里的feature class,工作空间类型建议填FILEGDB_WORKSPACE,dataset_name填要素类名字;如果你处理的是个人地理数据库,需要用ACCESS_WORKSPACE。填错参数最常见的报错是ERROR 000824,提示数据源不支持,回头查一下工作空间类型往往就是答案。

还有一种更省事的写法,是把workspace_type留空字符串或者填NONE,让ArcPy根据workspace路径自动判断。这个写法在部分版本里能用,但不是百分之百稳定,为了减少不确定性,我建议还是老老实实把工作空间类型写清楚。

另外注意,如果你的数据是矢量要素类但文件名带中文,在Python 2.7下需要确保脚本顶部有# -*- coding: utf-8 -*-,并且所有字符串都用UTF-8编码。我的建议仍然是尽量用英文文件名,不给自己添堵。

3.4 三个容易踩的细节

第一个细节是图层名不会自动变成数据名。替换数据源后,图层在内容列表里的名字还是“替换图层”,如果不手动设置lyr.name = fc,导出的图例里就会显示占位名。我在脚本里已经加上了这一行,但你要理解为什么加,而不是只当作固定模板。

第二个细节是df.extent = target_layer.getExtent()。这一步相当于界面上“缩放至图层”的操作。如果不设置,导出的图会沿用模板里数据框的旧范围,可能是一片空白,也可能只显示原来的占位数据范围。如果你的模板数据框有固定比例尺需求,比如必须1:10000,那这里就不能用getExtent,而要改用数据驱动页面或者手动指定比例尺。

第三个细节是saveACopy和save的区别。saveACopy会另存为一个新文件,不影响原模板;save会直接把当前对象写回原路径。脚本里的模板在循环里被反复读取,如果你用了save,第一轮循环就把模板覆盖了,第二轮读取的可能就是已经改过的文件,整个逻辑直接崩盘。养成用saveACopy的习惯,模板永远保留一份原始版本。

4. 手把手把脚本跑起来

4.1 在ArcMap的Python窗口里运行

最省心的运行方式,是直接用ArcMap自带的Python窗口。打开ArcMap,新建一个空白地图文档,不需要做什么操作,直接调出Python窗口,把上面那段脚本整体粘贴进去,修改路径后回车。因为ArcMap进程已经带好了ArcPy环境,只要许可证没问题,基本一次跑通。

需要注意的是,脚本里的模板Mxd不能正被同一个ArcMap程序集中打开编辑,否则文件被占用,读取或另存时可能报权限错误。我习惯把模板放在单独的目录,运行脚本时ArcMap窗口保持空白地图就行。还有,Python窗口里大量输出会滚动得比较慢,如果图层数量特别多,建议临时把print注释掉,只看最后结果,速度和观感都好很多。

如果脚本中途报错,Python窗口会高亮报错行,但不会很方便地调试。小白阶段可以试试在关键位置加print输出,比如“已经找到占位图层”“正在导出XXX”,这样一眼就能看出卡在哪一步。脚本跑完后,去图片目录和工程文件目录确认有没有生成文件,文件大小是否正常。

4.2 用外部Python环境运行

如果在Python窗口里运行没问题,但你想脱离ArcMap界面,做成一个每天自动跑的任务,可以改用外部命令运行。打开命令行,用ArcGIS自带的Python解释器执行脚本,命令格式类似这样:

"C:\Python27\ArcGIS10.6\python.exe" "D:\project\批量出图.py"

路径里的Python27\ArcGIS10.6要替换成你机器上实际存在的目录,版本号可能不同。这个方法的好处是一次配置好,之后可以双击bat文件或者定时任务自动执行。注意不要用系统自带的Python或者Anaconda的python.exe,否则第一行import arcpy就会报No module named arcpy。

在外部运行还有一个好处,ArcGIS的主界面不用一直开着,能节省不少内存。我之前处理三百多个图层时,ArcMap界面留在那里特别占内存,用外部Python跑,进程释放更干净,速度还快一些。如果你在公司生产环境用,建议优先考虑命令行方式。

4.3 做成ArcGIS脚本工具,让别人也能用

如果你不是只给自己用,还可以把脚本包装成ArcMap里的“脚本工具”。在ArcToolbox里右键添加工具箱,选择添加脚本,然后指定刚刚写好的py文件,把模板路径、数据工作空间、输出目录都设置成参数。脚本内部用arcpy.GetParameterAsText(0)这种方式接收参数,比硬编码路径好用得多。

做成工具的额外好处是,原来的脚本可以直接拖进ArcToolbox,同事使用时只需要在工具界面里点选文件夹,不容易因为改错代码路径而出问题。但这一步涉及工具箱设置和参数类型选择,如果你今天第一次接触ArcPy,先不用急着做,跑通上面的脚本才是第一步。

5. 常见报错与排查技巧

5.1 报错速查表

批量脚本出问题时,最怕没有头绪。我整理了一份比较常见的报错速查表,基本覆盖新手阶段会碰到的坑。

报错现象最常见原因解决办法
import arcpy时提示No module named arcpy运行环境不是ArcGIS自带Python改用ArcGIS的python.exe,或在ArcMap Python窗口运行
ERROR 000824: 数据源不支持replaceDataSource的工作空间类型填错核对shapefile或gdb对应类型,填SHAPEFILE_WORKSPACE或FILEGDB_WORKSPACE
ERROR 000860: 图层数据源无效数据集中找不到dataset_name检查数据文件名是否正确,shapefile不用带.shp
输出PNG是空白数据框范围没刷新或模板占位图层没替换确认df.extent = target_layer.getExtent()已执行
图例显示“替换图层”没有手动改lyr.name替换数据源后用lyr.name = fc
Mxd另存失败或提示文件被占用模板正在ArcMap中打开编辑关闭模板文档,再重新运行脚本
UnicodeDecodeError路径或数据名含中文,脚本编码设置不对改用英文路径;脚本头加# -- coding: utf-8 --
脚本能跑但第一个图层后就停某个要素数据为空或投影缺失先单独处理该要素,查看getExtent是否正常

这张表不是金科玉律,但排查顺序我觉得很有效:先看环境,再查数据源参数,最后看模板。大多数“脚本没反应”的问题,其实都是环境没配对。

5.2 三个翻车瞬间,说给你避坑

第一次用replaceDataSource时,我把工作空间类型填成了NONE,结果第一轮循环就报错。后来才发现,对不同数据类型明确写类型关键字才是最稳的。曾经还有一次,因为模板里的占位图层名带了后缀“_1”,脚本里匹配的是“替换图层”,怎么都替换不上,导出的全是同一个占位数据。那次教训让我养成了习惯:模板里所有图层名尽量简短,并且做完模板后先手动列一次图层名,确保和脚本一致。

另一次翻车是输出图片大小差异很大。有的图层范围覆盖全县,有的只覆盖一个村,导出的图片内容面积不同,比例尺完全不同,看起来很不整齐。原因是直接用getExtent让数据框跟随每个要素全图。如果你希望所有图比例尺统一,就要在模板里固定数据框比例尺,或者按字段批量拆分到同一行政单元,而不是用全图范围。这个需求要看具体场景,没有绝对对错。

还有一个容易忽略的问题是坐标系。如果你的数据框用的是模板里的坐标系,而某个源数据的坐标系不一样,getExtent后范围可能会出现偏移。解决方法是提前把所有数据统一投影到同一个坐标系,或者在模板里为数据框设定好固定坐标系。我在批量出图前,通常会先写一小段检查脚本,把所有待处理数据的坐标系打印出来,发现有不一样的就先做投影,免得跑完才发现图位置不对。

5.3 如何验证输出成果

脚本跑完不代表万事大吉,一定要做抽样验证。我的验证习惯是:每批次随机打开三到五张PNG,先看图层内容是否完整,再看图例文字、标题是否正确,最后把对应的Mxd拖进ArcMap看一眼,确认数据框范围和图片一致。如果脚本处理了几百个文件,至少要抽1%到5%,量少的话我甚至每张都过一遍。

验证Mxd还有一个快捷方式,不用一个个打开。可以写一个小循环,用arcpy.mapping.MapDocument读取每个Mxd,然后打印出数据框范围和图层列表,快速比对有没有异常。如果你不会写这个检查脚本,先用ArcMap手动抽查几份也够用。关键是不要“跑完就认为成功”,文件生成成功和内容正确是两回事。

6. 进阶一点:把图集按字段拆出来

6.1 按字段批量拆图的思路

前面处理的是“一个要素类出一张图”,但更常见的业务是“一个要素类里有二十个乡镇,每个乡镇各出一张图”。这种需求就变成按字段拆分。思路是先用arcpy.da.SearchCursor遍历某个行政区字段的唯一值,然后对每个值设置lyr.definitionQuery,把数据框范围缩放到被过滤后的图层范围,再导出图片。

这里有一个关键转换:同样一个要素类,不再需要replaceDataSource,因为数据源始终是同一个。你在每轮循环里只要修改definitionQuery,然后重新获取一次extent就行。脚本框架和前面很像,只是最外层的循环对象从要素类列表变成了字段值列表。实际跑的时候要注意字段值可能有空格或特殊字符,拼接查询条件时用双引号转义。零门槛阶段先把3.2的脚本吃透,再往这个方向扩展,会顺手得多。

6.2 如果以后换到ArcGIS Pro怎么办

很多朋友可能会问,现在新项目是不是都该用Pro?如果哪天你换到ArcGIS Pro,这套基于arcpy.mapping的代码无法直接运行,要用arcpy.mp模块,生成的文件也从Mxd变成Aprx。两者的对象命名和接口有不少差异,比如MapDocument变成了ArcGISProject,saveACopy变成了saveAsCopy。思路是相通的,都是“模板+循环+导出”,但函数名要重新学。

我对小白的建议是:如果你所在单位还在用ArcMap,就先把这套Mxd方案用熟;如果有新项目从零开始,可以直接学Pro方案。不用惊慌,两个版本切换时,重点是把这里教的“替换数据源、设置范围、导出、另存”四个动作迁移过去,剩下的是查文档的问题。

6.3 我的两点使用体会

最后分享两点我自己的经验。第一,模板值得花一小时去打磨,版式、符号、字体全都定好,脚本只是让模板复制得更多。模板里一个线宽设置错了,批量出图后就是几十张统一错,前期多花的时间会在后期成倍省回来。第二,第一次跑脚本不要一口气跑全量,先指定一个图层试跑,确认PNG和Mxd都没问题后再放开全量。不然等跑到第十张发现问题,前面九张都得返工。这套思路我救过好几次急,希望你下次遇到批量出图也能更从容。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 22:15:14

Spark分布式K-Means实现健康风险画像与关联分析

1. 项目概述:这不是一个“画个饼”的健康分析,而是一套能真正跑在真实数据上的风险识别流水线你有没有遇到过这样的情况:手头有一批几十万甚至上百万条的体检记录、生活习惯问卷、体测数据,想从中找出哪些人肥胖风险最高、哪些行为…

作者头像 李华
网站建设 2026/10/20 22:14:35

LangChain4j + 通义千问实战:Java实现RAG流式问答全流程

1. 从手写大模型接口说起:Java 开发为什么总在这件事上吃亏先说说我自己吧。之前接手一个知识库问答系统,技术栈是 Java 17 Spring Boot,需要对接大模型能力。团队里大多数人跟我一样,第一个想法就是:直接调 HTTP 接口…

作者头像 李华
网站建设 2026/10/20 22:13:27

3D目标检测中的IoU计算:从2D旋转框到BEV投影的工程实现指南

做3D目标检测避不开的一个环节就是IoU计算,尤其是从纯2D检测转向3D检测时,很多人第一个不适应的地方就在这里。2D检测里写个矩形框相交面积判断,几行代码一调就完事;到了3D空间,旋转角一进来,原本习惯的那套…

作者头像 李华
网站建设 2026/10/20 22:13:21

用Anaconda实现Python多环境隔离与可复现管理

1. 项目概述:为什么“Python多环境”不是锦上添花,而是生存刚需?你有没有遇到过这样的场景:刚在本地跑通一个用PyTorch 2.0写的模型训练脚本,兴冲冲想部署到服务器上,结果import torch直接报错——提示torc…

作者头像 李华
网站建设 2026/10/20 22:11:10

对话机器人自研模型还是接入外部模型?工程化决策与混合架构实践

1. 从一条技术圈热议说起:外部模型接入的决策逻辑前几天在一个开发者社群里,有人抛出一个话题:某对话机器人项目(我们姑且叫它 Grok Bot)打算在下一版里接入外部模型,而不是继续纯靠自研。群里立刻分成两派…

作者头像 李华