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)
相关推荐
科技苑3 小时前
Python简单网络爬虫教程
爬虫·python
2501_933670794 小时前
2026秋招数据分析岗备考路线:SQL、BI、项目与面试题拆解
数据库
Patrick在香港6 小时前
Claude Prompt 香港场景:公文里「今日」落在 6 个日历日上,「翌日」锚错了 5 天
python·自然语言处理·正则表达式·claude·数据清洗
我要见SA姐16 小时前
告别 Copilot?Codex 本地化部署指南
运维·数据库·机器学习·oracle·回归
YsyaaabB7 小时前
Python 数值分析
python
阿洛学长7 小时前
计算机二级 Python 基本操作题(15 分)真题笔记(0101 ~ 1903 全套)
python·pycharm
xcLeigh7 小时前
聊聊国产化替换:好用数据迁移工具KDMS怎么帮咱们搞定评估难
数据库·sql·数据迁移·kes·kdms
Elastic 中国社区官方博客7 小时前
列式存储并不等同于列式数据库。Columnar 模式为 Elasticsearch 带来了什么
大数据·运维·数据库·elasticsearch·搜索引擎
weixin199701080167 小时前
[特殊字符]️《二手ERP对接电商平台的总体方案:统一数据模型 + 事件驱动 + 灰度上线6原则》(附Python源码)
大数据·python
滚雪球~8 小时前
量化交易 防止Windows电脑自动更新并重启
python·量化