一、引子
前阵子为了解决shp字段名称字数限制问题,把GIS的默认编码格式改成了GBK,直接导致原来shp文件里的汉字乱码,因此写一个转码工具。
二、UTF-8转GBK 代码
python
"""
将 DBF 文件的字符编码从 UTF-8 转换为 GBK。
需要安装依赖:
pip install dbf
"""
import os
import re
import dbf
def widen_numeric_fields(structure, extra_int_digits=10):
"""
加宽数值字段定义,避免整数部分溢出。
例如 'O_LAT N(12,8)' -> 'O_LAT N(23,8)',整数部分从 3 位扩到 14 位。
"""
new_structure = []
for field_def in structure:
m = re.match(r"(\w+)\s+N\((\d+),(\d+)\)", field_def)
if m:
name, width, decimals = m.group(1), int(m.group(2)), int(m.group(3))
# 新总宽度 = 小数位 + 扩展后的整数位 + 1(小数点占位)
new_width = decimals + extra_int_digits + 1
new_structure.append(f"{name} N({new_width},{decimals})")
else:
new_structure.append(field_def)
return new_structure
def convert_dbf_utf8_to_gbk(input_path, output_path=None):
"""
读取 input_path(按 UTF-8 解码),把所有字符字段转码为 GBK,
并写入 output_path,同时把 DBF 头中的编码标识设为 GBK/CP936。如果不输入,默认与输入同目录同名+(GBK).dbf
"""
if output_path is None:
base, _ = os.path.splitext(input_path)
output_path = base + "(GBK).dbf"
# 以只读方式打开原表,指定源码为 UTF-8
src = dbf.Table(input_path, codepage="utf8")
src.open(mode=dbf.READ_ONLY)
# 加宽数值字段,避免原表字段定义过窄导致写入失败
field_specs = widen_numeric_fields(src.structure())
# 创建新表,目标编码为 GBK/CP936(dbf 库要求用 "cp936",对应 DBF 头 0xCB)
dst = dbf.Table(output_path, field_specs, codepage="cp936")
dst.open(mode=dbf.READ_WRITE)
try:
for record in src:
row = {}
for field_name in src.field_names:
value = record[field_name]
# dbf 库读出的字符字段已经是 str,但需要确保以 GBK 可编码
if isinstance(value, str):
# 先按 UTF-8 规范化(默认已解码),再确认能编码为 GBK
try:
value.encode("gbk")
except UnicodeEncodeError:
# 若存在 GBK 无法表示的字符,用替换符 ? 代替
value = value.encode("gbk", errors="replace").decode("gbk")
row[field_name] = value
dst.append(row)
finally:
src.close()
dst.close()
print(f"转换完成:{input_path} -> {output_path}")
print("新文件编码已设置为 GBK (CP936)。")
if __name__ == "__main__":
input_path = r"C:\Users\test\test.dbf" #换成你自己的路径
convert_dbf_utf8_to_gbk(input_path)
三、UTF-8和GBK编码文件自动互转 代码
python
"""
DBF 文件编码互转工具:UTF-8 <=> GBK。
自动识别原 DBF 文件编码:
1. 优先读取 DBF 文件头第 29 字节的 Language Driver ID;
2. 若仍无法判断,默认按 UTF-8 处理。
输出编码与源编码相反:
UTF-8 -> GBK,默认输出文件名:原文件名(GBK).dbf
GBK -> UTF-8,默认输出文件名:原文件名(UTF-8).dbf
需要安装依赖:
pip install dbf
"""
import os
import re
import dbf
# DBF 头 LDID 到编码的映射(只列出常见中文相关)
LDID_TO_ENCODING = {
0x03: "cp936", # CP936 / GB2312 / GBK(常见 shapefile 默认值)
0x4D: "cp936", # GBK
0xCB: "cp936", # GBK / CP936
0xC8: "utf8", # 某些库用此值表示 UTF-8
0x78: "utf8", # UTF-8(非官方但常见)
0x79: "utf8", # UTF-8(非官方但常见)
}
def detect_dbf_encoding(dbf_path):
"""
检测 DBF 文件编码。返回 'utf8' 或 'cp936'。
"""
# 1. 读取 DBF 头第 29 字节(LDID)
try:
with open(dbf_path, "rb") as f:
header = f.read(32)
if len(header) >= 30:
ldid = header[29]
if ldid in LDID_TO_ENCODING:
return LDID_TO_ENCODING[ldid]
except Exception:
pass
# 2. 尝试让 dbf 库自己判断 codepage
try:
src = dbf.Table(dbf_path)
src.open(mode=dbf.READ_ONLY)
cp = src.codepage
src.close()
if cp:
cp_str = str(cp).lower().replace("-", "")
if cp_str in ("utf8", "cp65001", "65001"):
return "utf8"
if cp_str in ("cp936", "gbk", "gb2312", "gb18030", "936"):
return "cp936"
except Exception:
pass
# 3. 默认按 UTF-8
return "utf8"
def widen_numeric_fields(structure, extra_int_digits=10):
"""
加宽数值字段定义,避免整数部分溢出。
例如 'O_LAT N(12,8)' -> 'O_LAT N(23,8)',整数部分从 3 位扩到 14 位。
"""
new_structure = []
for field_def in structure:
m = re.match(r"(\w+)\s+N\((\d+),(\d+)\)", field_def)
if m:
name, width, decimals = m.group(1), int(m.group(2)), int(m.group(3))
# 新总宽度 = 小数位 + 扩展后的整数位 + 1(小数点占位)
new_width = decimals + extra_int_digits + 1
new_structure.append(f"{name} N({new_width},{decimals})")
else:
new_structure.append(field_def)
return new_structure
def convert_dbf_encoding(input_path, output_path=None):
"""
自动识别 input_path 的 DBF 编码,转换为另一种编码后输出。
"""
if not os.path.exists(input_path):
raise FileNotFoundError(f"找不到文件:{input_path}")
# 检测源编码
src_encoding = detect_dbf_encoding(input_path)
# 确定目标编码、codepage、输出后缀
if src_encoding == "utf8":
dst_codepage = "cp936"
dst_encoding_name = "GBK"
dst_codec = "gbk"
else:
dst_codepage = "utf8"
dst_encoding_name = "UTF-8"
dst_codec = "utf-8"
# 默认输出路径
if output_path is None:
base, _ = os.path.splitext(input_path)
output_path = f"{base}({dst_encoding_name}).dbf"
# 打开原表
src = dbf.Table(input_path, codepage=src_encoding)
src.open(mode=dbf.READ_ONLY)
# 加宽数值字段定义,避免整数溢出
field_specs = widen_numeric_fields(src.structure())
# 创建新表
dst = dbf.Table(output_path, field_specs, codepage=dst_codepage)
dst.open(mode=dbf.READ_WRITE)
try:
for record in src:
row = {}
for field_name in src.field_names:
value = record[field_name]
if isinstance(value, str):
# 确保目标编码可编码;不可编码时替换为 ?
try:
value.encode(dst_codec)
except UnicodeEncodeError:
value = value.encode(dst_codec, errors="replace").decode(dst_codec)
row[field_name] = value
dst.append(row)
finally:
src.close()
dst.close()
# 写入对应的 .cpg 文件
cpg_path = os.path.splitext(output_path)[0] + ".cpg"
try:
with open(cpg_path, "w", encoding="ascii") as f:
f.write(dst_encoding_name)
except Exception:
pass
print(f"源文件编码:{src_encoding.upper()}")
print(f"转换完成:{input_path} -> {output_path}")
print(f"新文件编码已设置为 {dst_encoding_name}。")
if __name__ == "__main__":
input_path = r"C:\test\test.dbf"
output_path = None
convert_dbf_encoding(input_path, output_path)