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)
相关推荐
知行合一。。。1 小时前
LangGraph--03--本地服务与 Studio 调试
数据库
胡耀超1 小时前
AI出事后,怎么查?——读《AI Forensics》
人工智能·python·数字取证·ai取证·
梦想三三1 小时前
Python从零实现AI Agent电商客服(Qwen/Ollama+SQLite源码解析)
人工智能·python·sqlite
鹿鹿学长1 小时前
国赛工业应用数学题:从31省规上工业数据到生产排程,四类高频赛题的破题打法
python·自动化
你不是我我1 小时前
【AI 测评】PostgreSQL主从流复制实战:数据同步、状态验证与故障切换
数据库·postgresql
月落归舟1 小时前
Redis 三种消息队列实现方案
数据库·redis·list
笨鸟先飞,勤能补拙1 小时前
密码学深度指南 — SecOps 工程师实战手册
人工智能·vscode·python·安全·github·密码学·visual studio
淼澄研学1 小时前
PyTorch核心实操:从张量计算到混合精度训练的5个关键步骤
人工智能·pytorch·python
梦想三三1 小时前
Qwen Function Calling实战:重构电商客服AI Agent
人工智能·python·langchain·大模型·rag