# QGIS批量导出矢量图层实战:Python脚本一键搞定(附完整代码)
你是否也曾被QGIS中重复的“右键-导出-保存”操作搞得心烦意乱?当项目里堆叠着十几个甚至几十个需要单独导出的矢量图层时,手动操作不仅耗时费力,还极易在重复劳动中出错。对于GIS工程师、数据分析师或任何需要频繁处理空间数据的朋友来说,掌握自动化批量导出的技能,就像是为自己的工作流安装了一台涡轮增压发动机。今天,我们就深入探讨如何利用QGIS内置的Python环境,编写一个高效、健壮且可定制的脚本,将繁琐的导出任务化为一键执行的畅快体验。这不仅仅是写几行代码,更是构建一种更聪明的工作方式。
## 1. 理解QGIS的Python自动化基石
在动手写脚本之前,我们需要先摸清QGIS为我们提供的“工具箱”。QGIS的核心魅力之一在于其强大的可扩展性,而Python API正是打开这扇大门的钥匙。它允许我们以编程方式访问和操作QGIS的几乎所有功能,从加载数据、修改样式到执行地理处理和分析。
**QgsProject与图层管理** 是整个自动化流程的起点。在QGIS的Python控制台或独立脚本中,`QgsProject.instance()` 是我们获取当前项目全局状态的入口。通过它,我们可以拿到项目中的所有图层列表,并进行筛选。例如,你可能只想导出矢量图层,而忽略栅格图层或无效的图层。
```python
# 获取当前QGIS项目中所有的图层
project = QgsProject.instance()
all_layers = project.mapLayers().values()
# 筛选出所有矢量图层
vector_layers = [layer for layer in all_layers if isinstance(layer, QgsVectorLayer)]
print(f"找到 {len(vector_layers)} 个矢量图层。")
```
**QgsVectorFileWriter:数据导出的核心引擎**。这个类是执行矢量数据写入(导出)操作的主力。它支持数十种地理空间数据格式,从常见的Shapefile、GeoJSON、GPKG,到专业领域的KML、GML、MapInfo TAB等。其`writeAsVectorFormat`方法提供了丰富的参数来控制导出行为,包括坐标系、编码、属性字段选择以及驱动选项。
> 注意:在QGIS 3.x版本中,更推荐使用`QgsVectorFileWriter.writeAsVectorFormatV2`或`QgsVectorFileWriter.writeAsVectorFormatV3`方法,它们提供了更完善的错误处理和选项控制。但为了兼容性和理解基本原理,我们先从经典方法入手。
一个高效的批量导出脚本,其核心逻辑通常遵循“获取-筛选-遍历-导出-验证”的流程。理解了这个框架,我们就能根据具体需求进行灵活调整和增强。
## 2. 构建基础批量导出脚本
让我们从一个最实用、最基础的脚本开始。这个脚本将完成以下任务:自动识别当前QGIS项目中的所有矢量图层,并将它们以Shapefile格式批量导出到指定文件夹。
```python
import os
from qgis.core import QgsProject, QgsVectorLayer, QgsVectorFileWriter
def batch_export_shapefiles(output_folder):
"""
将当前项目中的所有矢量图层导出为Shapefile到指定文件夹。
参数:
output_folder (str): 目标输出文件夹的路径。
"""
# 1. 检查输出文件夹是否存在,若不存在则创建
if not os.path.exists(output_folder):
os.makedirs(output_folder)
print(f"创建输出目录: {output_folder}")
# 2. 获取项目实例和所有图层
project = QgsProject.instance()
layers = project.mapLayers().values()
# 3. 遍历图层,筛选并导出矢量图层
success_count = 0
for layer in layers:
if isinstance(layer, QgsVectorLayer):
layer_name = layer.name()
# 清理文件名中的非法字符(Windows下常见问题)
safe_name = "".join(c for c in layer_name if c.isalnum() or c in (' ', '_', '-')).rstrip()
output_path = os.path.join(output_folder, f"{safe_name}.shp")
# 4. 执行导出操作
# 参数说明: 图层对象, 输出路径, 文件编码, 目标坐标系, 驱动名
error_code, error_message = QgsVectorFileWriter.writeAsVectorFormat(
layer,
output_path,
'UTF-8',
layer.crs(),
'ESRI Shapefile'
)
# 5. 检查导出结果
if error_code == QgsVectorFileWriter.NoError:
print(f"✅ 成功导出: {layer_name} -> {output_path}")
success_count += 1
else:
print(f"❌ 导出失败 [{layer_name}]: {error_message}")
# 6. 输出总结报告
total_vector = sum(1 for l in layers if isinstance(l, QgsVectorLayer))
print(f"\n导出完成。总计处理 {total_vector} 个矢量图层,成功 {success_count} 个。")
# 使用示例 - 在QGIS Python控制台中直接运行
if __name__ == '__console__':
output_dir = 'C:/MyGISProjects/Exported_Layers' # 请修改为你的实际路径
batch_export_shapefiles(output_dir)
```
这个脚本已经具备了基本功能,但它还有很大的优化空间。比如,它没有处理文件覆盖问题,也没有提供格式选择。别急,我们一步步来增强它。
## 3. 高级功能与脚本增强
一个生产级的脚本需要考虑更多的边界情况和用户需求。下面我们来为这个脚本添加几个关键的高级功能。
**3.1 支持多种导出格式**
Shapefile虽经典,但有其局限性(如字段名长度限制、多文件组成)。现代GIS工作流中,GeoPackage(GPKG)因其单文件、支持多种几何类型、无字段名限制等优点越来越受欢迎。我们的脚本应该让用户可以选择格式。
```python
def batch_export_with_format(output_folder, file_format='GPKG'):
"""
支持多种格式的批量导出。
参数:
output_folder (str): 输出文件夹路径。
file_format (str): 导出格式,可选 'GPKG', 'ESRI Shapefile', 'GeoJSON', 'KML'。
"""
# 格式与驱动名、文件扩展名的映射
format_map = {
'GPKG': ('GPKG', '.gpkg'),
'ESRI Shapefile': ('ESRI Shapefile', '.shp'),
'GeoJSON': ('GeoJSON', '.geojson'),
'KML': ('KML', '.kml')
}
if file_format not in format_map:
print(f"错误: 不支持的格式 '{file_format}'。请选择 {list(format_map.keys())}")
return
driver_name, file_ext = format_map[file_format]
os.makedirs(output_folder, exist_ok=True)
project = QgsProject.instance()
for layer in project.mapLayers().values():
if isinstance(layer, QgsVectorLayer):
safe_name = "".join(c for c in layer.name() if c.isalnum() or c in (' ', '_', '-')).rstrip()
# 注意:GPKG格式可以将多个图层存入一个文件,这里我们选择每个图层单独一个文件
output_path = os.path.join(output_folder, f"{safe_name}{file_ext}")
# 对于GeoJSON和KML,需要特别注意坐标系
crs = layer.crs()
if file_format in ['GeoJSON', 'KML']:
# 通常GeoJSON使用EPSG:4326 (WGS84), KML也类似
# 这里可以选择进行坐标转换,或者保持原坐标系
# 以下代码保持原坐标系,但请注意兼容性
pass
error_code, error_message = QgsVectorFileWriter.writeAsVectorFormat(
layer, output_path, 'UTF-8', crs, driver_name)
if error_code == QgsVectorFileWriter.NoError:
print(f"成功导出 {file_format}: {layer.name()}")
else:
print(f"失败: {layer.name()} - {error_message}")
```
**3.2 智能文件存在处理与覆盖选项**
在批量导出时,很可能目标文件夹已存在同名文件。我们可以添加逻辑,让用户决定是跳过、覆盖还是自动重命名。
```python
import os
from qgis.core import QgsVectorFileWriter
def export_with_overwrite_handling(layer, output_path, driver_name, overwrite_policy='rename'):
"""
处理文件已存在情况的导出函数。
参数:
overwrite_policy: 'skip'-跳过, 'overwrite'-覆盖, 'rename'-自动重命名
"""
final_output_path = output_path
base, ext = os.path.splitext(output_path)
# 检查文件是否存在
if os.path.exists(output_path):
if overwrite_policy == 'skip':
print(f"文件已存在,跳过: {output_path}")
return False, "Skipped"
elif overwrite_policy == 'overwrite':
print(f"文件已存在,将覆盖: {output_path}")
# 对于Shapefile,需要删除所有相关文件 (.shp, .shx, .dbf, .prj等)
if driver_name == 'ESRI Shapefile':
for related_ext in ['.shp', '.shx', '.dbf', '.prj', '.cpg', '.qpj']:
related_file = base + related_ext
if os.path.exists(related_file):
os.remove(related_file)
else:
os.remove(output_path)
elif overwrite_policy == 'rename':
counter = 1
while os.path.exists(final_output_path):
final_output_path = f"{base}_{counter}{ext}"
counter += 1
print(f"文件已存在,重命名为: {final_output_path}")
# 执行导出
error_code, error_message = QgsVectorFileWriter.writeAsVectorFormat(
layer, final_output_path, 'UTF-8', layer.crs(), driver_name)
return error_code == QgsVectorFileWriter.NoError, error_message
```
**3.3 导出进度反馈与日志记录**
对于大量图层的导出,一个可视化的进度反馈和详细的日志文件非常重要。我们可以利用QGIS的进度条功能和Python的日志模块。
```python
import logging
from qgis.PyQt.QtWidgets import QProgressDialog
from qgis.PyQt.QtCore import Qt
def batch_export_with_progress(output_folder, format='GPKG'):
"""带进度显示的批量导出"""
project = QgsProject.instance()
vector_layers = [lyr for lyr in project.mapLayers().values() if isinstance(lyr, QgsVectorLayer)]
if not vector_layers:
print("未找到矢量图层。")
return
# 设置进度对话框
progress = QProgressDialog("批量导出矢量图层...", "取消", 0, len(vector_layers))
progress.setWindowModality(Qt.WindowModal)
progress.setMinimumDuration(0)
# 配置日志
log_file = os.path.join(output_folder, 'export_log.txt')
logging.basicConfig(filename=log_file, level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
success_count = 0
for i, layer in enumerate(vector_layers):
progress.setValue(i)
progress.setLabelText(f"正在导出: {layer.name()}")
if progress.wasCanceled():
logging.warning("用户取消了导出操作。")
break
# ... 执行导出逻辑 ...
is_success, msg = export_single_layer(layer, output_folder, format)
if is_success:
success_count += 1
logging.info(f"成功导出: {layer.name()}")
else:
logging.error(f"导出失败 [{layer.name()}]: {msg}")
# 强制界面更新
QApplication.processEvents()
progress.setValue(len(vector_layers))
logging.info(f"导出任务完成。成功 {success_count}/{len(vector_layers)}")
print(f"详细日志已保存至: {log_file}")
```
## 4. 实战:创建一个可复用的导出工具插件
将脚本固化成一个QGIS插件,可以让非编程用户也能轻松使用。这里我们简要介绍如何将上述功能打包成一个简单的处理脚本(Processing Script),它可以在QGIS的“处理工具箱”中直接运行。
在QGIS中,进入“处理”菜单 -> “工具箱”,在顶部找到“Python” -> “创建新脚本”,然后粘贴以下模板代码:
```python
"""
批量导出矢量图层工具
作者: [你的名字]
描述: 将当前项目中的矢量图层批量导出为指定格式。
"""
from qgis.core import (QgsProcessingAlgorithm, QgsProcessingParameterFolderDestination,
QgsProcessingParameterEnum, QgsProcessingParameterBoolean,
QgsProject, QgsVectorLayer, QgsVectorFileWriter)
from qgis.PyQt.QtCore import QCoreApplication
import os
class BatchExportVectorLayers(QgsProcessingAlgorithm):
"""
这是一个处理脚本算法的示例。
"""
# 算法参数常量
OUTPUT_FOLDER = 'OUTPUT_FOLDER'
FILE_FORMAT = 'FILE_FORMAT'
OVERWRITE = 'OVERWRITE'
def initAlgorithm(self, config=None):
# 添加参数
self.addParameter(
QgsProcessingParameterFolderDestination(
self.OUTPUT_FOLDER,
self.tr('输出文件夹')
)
)
# 文件格式选择
self.addParameter(
QgsProcessingParameterEnum(
self.FILE_FORMAT,
self.tr('导出格式'),
options=['GeoPackage (.gpkg)', 'ESRI Shapefile (.shp)', 'GeoJSON', 'KML'],
defaultValue=0
)
)
# 是否覆盖现有文件
self.addParameter(
QgsProcessingParameterBoolean(
self.OVERWRITE,
self.tr('覆盖现有文件'),
defaultValue=False
)
)
def processAlgorithm(self, parameters, context, feedback):
# 获取参数值
output_folder = self.parameterAsString(parameters, self.OUTPUT_FOLDER, context)
format_index = self.parameterAsInt(parameters, self.FILE_FORMAT, context)
overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context)
# 格式映射
formats = ['GPKG', 'ESRI Shapefile', 'GeoJSON', 'KML']
extensions = ['.gpkg', '.shp', '.geojson', '.kml']
selected_format = formats[format_index]
selected_ext = extensions[format_index]
# 获取项目中的矢量图层
project = QgsProject.instance()
layers = list(project.mapLayers().values())
vector_layers = [lyr for lyr in layers if isinstance(lyr, QgsVectorLayer)]
total = len(vector_layers)
feedback.pushInfo(f"找到 {total} 个矢量图层。开始导出到 {output_folder}...")
success_count = 0
for i, layer in enumerate(vector_layers):
if feedback.isCanceled():
break
# 更新进度
feedback.setProgress(int((i / total) * 100))
# 准备输出路径
layer_name = layer.name()
safe_name = "".join(c for c in layer_name if c.isalnum() or c in (' ', '_', '-')).rstrip()
output_path = os.path.join(output_folder, f"{safe_name}{selected_ext}")
# 处理文件覆盖
if os.path.exists(output_path) and not overwrite:
feedback.pushInfo(f"跳过已存在的文件: {output_path}")
continue
# 执行导出
error_code, error_msg = QgsVectorFileWriter.writeAsVectorFormat(
layer, output_path, 'UTF-8', layer.crs(), selected_format)
if error_code == QgsVectorFileWriter.NoError:
feedback.pushInfo(f"✓ 已导出: {layer_name}")
success_count += 1
else:
feedback.reportError(f"导出失败 [{layer_name}]: {error_msg}")
# 返回结果
feedback.pushInfo(f"导出完成。成功导出 {success_count}/{total} 个图层。")
return {self.OUTPUT_FOLDER: output_folder}
def name(self):
return 'batch_export_vector_layers'
def displayName(self):
return self.tr('批量导出矢量图层')
def group(self):
return self.tr('自定义工具')
def groupId(self):
return 'customtools'
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return BatchExportVectorLayers()
```
保存这个脚本后,它就会出现在处理工具箱的“自定义脚本”目录下。用户可以通过图形界面选择参数并运行,无需接触任何代码。
## 5. 常见问题排查与性能优化
即使有了完善的脚本,在实际运行中仍可能遇到各种问题。这里总结几个我常遇到的坑和解决方案。
**5.1 权限错误与路径问题**
* **症状**:脚本报错“Permission denied”或“路径不存在”。
* **排查**:
1. 检查输出文件夹是否存在,脚本是否有创建文件夹的权限。
2. 在Windows上,注意路径中的反斜杠`\`可能需要转义,或直接使用原始字符串(如`r"C:\MyData"`)或正斜杠`/`。
3. 文件名中避免使用`\/:*?"<>|`等操作系统保留字符。
**5.2 内存与大型数据集处理**
当导出非常大的图层时,可能会消耗大量内存甚至导致QGIS崩溃。
* **优化策略**:
* **分块处理**:对于极大的图层,可以考虑按空间范围或属性值分批导出。可以利用`QgsFeatureRequest`设置过滤条件。
```python
# 示例:按属性值分批导出(例如,按省份)
layer = QgsVectorLayer('path/to/large_layer.gpkg', 'large', 'ogr')
unique_values = layer.uniqueValues(layer.fields().indexOf('province_field'))
for value in unique_values:
request = QgsFeatureRequest()
request.setFilterExpression(f'"province_field" = \'{value}\'')
# 创建该子集的临时图层或直接导出
```
* **使用选择集**:如果只需要导出部分要素,可以先在QGIS中手动选择,然后在脚本中通过`layer.selectedFeatures()`获取。
**5.3 坐标系与属性编码**
* **坐标系不一致**:导出的文件坐标系错误或丢失。确保`writeAsVectorFormat`中的`crs`参数正确传递了图层的坐标系(`layer.crs()`)。
* **中文乱码**:尤其在Shapefile中常见。确保导出时指定正确的编码,如`'UTF-8'`。对于某些遗留系统,可能需要尝试`'GBK'`或`'GB2312'`。
**5.4 脚本在QGIS外部运行**
有时我们希望脚本能脱离QGIS界面,在服务器或命令行定时运行。这需要配置一个独立的PyQGIS环境。
```bash
# 一个简化的命令行脚本示例 (run_export.py)
import sys
import os
from pathlib import Path
# 设置PyQGIS环境(路径需根据实际安装调整)
sys.path.append('/usr/share/qgis/python/plugins') # Linux示例
os.environ['QT_QPA_PLATFORM'] = 'offscreen' # 无头模式,不需要GUI
from qgis.core import (
QgsApplication, QgsProject, QgsVectorLayer, QgsVectorFileWriter
)
# 初始化QGIS应用(不启动GUI)
qgs = QgsApplication([], False)
qgs.initQgis()
# 你的批量导出逻辑
project_path = '/path/to/your/project.qgz'
output_folder = '/path/to/output'
project = QgsProject.instance()
project.read(project_path)
# ... 调用之前定义的批量导出函数 ...
# 清理
qgs.exitQgis()
```
然后通过命令行执行:`python3 run_export.py`。
**性能对比表:不同导出策略的考量**
| 策略 | 优点 | 缺点 | 适用场景 |
| :--- | :--- | :--- | :--- |
| **全图层一次性导出** | 代码简单,逻辑清晰 | 大图层可能内存不足,一个失败可能中断整个流程 | 图层数量少、数据量适中 |
| **按属性分批导出** | 内存可控,可并行化潜力 | 代码复杂,需要图层有合适的分割字段 | 大型数据集,有明显分类属性 |
| **按空间范围分块导出** | 空间查询效率高,结果有空间组织性 | 可能产生大量小文件,边界处要素处理需注意 | 大规模区域数据,需要瓦片化 |
| **使用选择集导出** | 精确控制导出内容,交互友好 | 依赖前置的手动或查询选择 | 只需导出特定要素 |
最后,记得在实际项目中,先从测试数据或图层副本开始运行脚本。将核心的导出功能封装成独立的函数,方便调试和复用。日志记录功能至关重要,它能在出现问题时帮你快速定位。当你的脚本能够稳定处理各种情况后,你会发现自己再也不愿意回到手动导出的时代了——那种效率的提升和身心的解放,是实实在在的。