dbf文件UTF-8转GBK编码,以及两编码文件互转

一、引子

前阵子为了解决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)
相关推荐
CV山月11 分钟前
《DPO 算法详解:不训练奖励模型,如何让大模型直接学会人类偏好?》
人工智能·经验分享·python·大模型·强化学习·研究生
ctlover11 分钟前
网络编程和多线程
开发语言·网络·python
凌晨16812 分钟前
MySQL:DML、DDL与TCL精讲
数据库·mysql·oracle
Mr_liu_66615 分钟前
ns3-gym例子解析_基础例子与wifi例子_DQN(3)
开发语言·c++·python·dqn·ns3
风哥2号16 分钟前
数据库教程FGMT04‑生产环境Linux+Oracle19c RAC集群安装配置与项目实战
linux·数据库
weixin_4407305017 分钟前
playwright实战-渠道应用操作
开发语言·前端·python
Java开发追求者18 分钟前
navicat连接新的数据库提示Oracle library is not loaded.
数据库·oracle·oracle11g·oracle library·is not loaded
asdzx6718 分钟前
Python 解析 Excel 数据、图片与图表的实现方案
python·excel
砚底藏山河18 分钟前
存储选型实战:CSV-SQLite-MySQL同机基准(魔码量化实战 #02)
java·数据库·python·金融·maven
huaweichenai19 分钟前
spring boot 打包并部署到线上服务
java·数据库·spring boot