1. 为什么“读表头”这件小事总在项目里翻车Python 连 MySQL 读表头听起来就是一行cursor.description的事但真到项目里它经常变成一连串小坑的集合。比如做数据导出时你希望 Excel 第一行是中文列名结果拿到的是((id, ...), (user_name, ...))这种元组套元组做动态建表时你想根据源表字段自动生成CREATE TABLE却发现字段类型、是否为空、默认值全藏在description的不同下标里做字段校验时上游改了列名下游脚本静默跑完直到报表对不上数才被发现。这些场景的共同点是表头不只是“名字”它是元数据。cursor.description返回的每一项通常包含 7 个元素最常用的是第 0 位列名和第 1 位类型码但很多人只取了第 0 位就收工后面做类型映射时又得重新查一遍。更麻烦的是连接配置散落在各个脚本里host、port、user、password 硬编码换一套环境就要全局搜索替换。所以这篇不打算只给你一段“能跑”的代码而是把两件事一起解决一是用config.toml把数据库连接和统一 Key 通道收拢成配置骨架二是把cursor.description的用法讲透包括怎么验证、怎么排错。你跟着配一遍后面再遇到“读表头”的需求直接改配置就能复用。适合谁看正在写数据导出脚本的 Python 初学者、需要动态生成 SQL 的后端同学、以及想把零散连接配置统一管理的运维/数据工程同学。核心检索词就三个python、mysql、表头全文围绕它们展开。2. TaoToken 统一 Key 与 API 通道的前置准备在讲数据库之前先说清楚为什么这里要引入 TaoToken。很多同学的 Python 脚本里除了连 MySQL还会调用大模型做字段注释生成、SQL 补全、数据清洗规则推断。如果每个脚本都单独配一套 Key管理成本会很高。TaoToken 的作用是把模型调用收敛到一个统一入口你只需要维护一份 Key就能在多个脚本里复用。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 这个不加 UTM。你需要先去控制台创建一个 API Key然后把它写进config.toml而不是硬编码在 Python 文件里。具体操作路径打开控制台页面 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 登录后进入 API Keys 管理页 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 点新建复制生成的 Key。这个 Key 就是后面config.toml里[llm]段的api_key值。注意Key 只显示一次复制后先存到密码管理器或本地.env不要直接提交到 Git。配置文件里建议用占位符运行时再注入。如果你只是想先验证模型通道是否通可以打开模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息确认 Key 有效。这一步不是必须但能帮你排除“到底是 Key 错还是代码错”的干扰。长期做编码和 Agent 的同学可以了解 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合高频调用场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有完整的请求格式和参数说明。Claude Code 相关配置参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecodeutm_campaignrewrite 。这些链接先记着后面 CTA 会分流。3. config.toml 配置骨架与 Python 读取代码这一节是全文的核心给你一份可以直接复制的config.toml骨架包含 MySQL 连接段和 TaoToken 段。然后写一个load_config函数把它读进来再写get_table_header函数用cursor.description拿表头。先看配置文件。放在项目根目录命名config.toml# config.toml [mysql] host 127.0.0.1 port 3306 user your_user password your_password database your_db charset utf8mb4 [mysql.pool] min_size 1 max_size 5 timeout 10 [llm] base_url https://taotoken.net/api api_key sk-替换成你的Key model gpt-4o-mini timeout 30 [app] default_table orders header_cache_ttl 300这里有几个设计点值得说明。[mysql]段放基础连接信息[mysql.pool]放连接池参数方便后面换成DBUtils或SQLAlchemy时直接读。[llm]段的base_url固定指向 TaoToken 的 API 入口api_key用占位符实际运行时从环境变量覆盖。[app]段放业务默认值比如默认查哪张表、表头缓存多久。Python 3.11 之后标准库自带tomllib可以直接读 TOML。如果你用的是 3.10 及以下装tomli即可。下面是读取和连接代码# db_header.py import tomllib from pathlib import Path import pymysql from pymysql.cursors import DictCursor def load_config(path: str config.toml) - dict: with open(Path(path), rb) as f: return tomllib.load(f) def get_connection(cfg: dict): m cfg[mysql] return pymysql.connect( hostm[host], portm[port], userm[user], passwordm[password], databasem[database], charsetm.get(charset, utf8mb4), cursorclassDictCursor, autocommitTrue, ) def get_table_header(table: str, cfg: dict) - list[str]: conn get_connection(cfg) try: with conn.cursor() as cur: cur.execute(fSELECT * FROM {table} LIMIT 0) return [col[0] for col in cur.description] finally: conn.close() if __name__ __main__: cfg load_config() header get_table_header(cfg[app][default_table], cfg) print(表头列名:, header) print(列数:, len(header))关键点在LIMIT 0。很多人习惯SELECT * FROM table然后fetchall数据量大时直接把内存打满。加LIMIT 0后MySQL 只返回结果集的元数据不返回任何行cursor.description依然完整。这是读表头最省资源的方式实测在千万级表上也是毫秒级返回。cursor.description的结构是 7 元组下标含义如下表下标含义示例0列名 nameuser_name1类型码 type_code253(VAR_STRING)2显示长度 display_size2553内部长度 internal_size10204精度 precisionNone5小数位 scaleNone6是否可为空 null_okTrue如果你要做动态建表光有列名不够还得把类型码映射成 MySQL 类型。下面这个映射函数可以直接用import pymysql.constants.FIELD_TYPE as FT TYPE_MAP { FT.DECIMAL: DECIMAL, FT.TINY: TINYINT, FT.SHORT: SMALLINT, FT.LONG: INT, FT.FLOAT: FLOAT, FT.DOUBLE: DOUBLE, FT.NULL: NULL, FT.TIMESTAMP: TIMESTAMP, FT.LONGLONG: BIGINT, FT.INT24: MEDIUMINT, FT.DATE: DATE, FT.TIME: TIME, FT.DATETIME: DATETIME, FT.YEAR: YEAR, FT.NEWDATE: DATE, FT.VARCHAR: VARCHAR, FT.BIT: BIT, FT.JSON: JSON, FT.NEWDECIMAL: DECIMAL, FT.ENUM: ENUM, FT.SET: SET, FT.TINY_BLOB: TINYBLOB, FT.MEDIUM_BLOB: MEDIUMBLOB, FT.LONG_BLOB: LONGBLOB, FT.BLOB: BLOB, FT.VAR_STRING: VARCHAR, FT.STRING: CHAR, FT.GEOMETRY: GEOMETRY, } def describe_columns(table: str, cfg: dict) - list[dict]: conn get_connection(cfg) try: with conn.cursor() as cur: cur.execute(fSELECT * FROM {table} LIMIT 0) result [] for col in cur.description: result.append({ name: col[0], type: TYPE_MAP.get(col[1], TEXT), nullable: col[6], }) return result finally: conn.close()这样你拿到的就不只是表头名字而是一份可用来拼CREATE TABLE的字段描述。注意LIMIT 0对视图、临时表同样有效但对SHOW COLUMNS这类语句不适用那种场景直接用SHOW COLUMNS FROM table更直接。4. 验证请求与成功结果检查配置写完得验证。分两步先验证 MySQL 表头读取再验证 TaoToken 通道。第一步跑python db_header.py。如果config.toml里default_table填的是真实存在的表你会看到类似输出表头列名: [id, user_name, amount, created_at] 列数: 4如果表不存在会抛pymysql.err.ProgrammingError: (1146, Table your_db.orders doesnt exist)。这时候先确认database配置和表名拼写别急着改代码。第二步验证 TaoToken 通道。写一个最小请求脚本import tomllib, json, urllib.request def load_config(pathconfig.toml): with open(path, rb) as f: return tomllib.load(f) def ping_llm(cfg): llm cfg[llm] payload json.dumps({ model: llm[model], messages: [{role: user, content: 回复 OK 两个字母}], max_tokens: 10, }).encode() req urllib.request.Request( f{llm[base_url]}/v1/chat/completions, datapayload, headers{ Content-Type: application/json, Authorization: fBearer {llm[api_key]}, }, ) with urllib.request.urlopen(req, timeoutllm[timeout]) as resp: body json.loads(resp.read()) print(模型返回:, body[choices][0][message][content]) if __name__ __main__: ping_llm(load_config())运行后如果打印模型返回: OK说明 Key 和通道都正常。如果返回 401检查api_key是否复制完整返回 404检查base_url是否写成了https://taotoken.net/api而不是带路径的地址。第三步把两者串起来做一个真实场景读表头后让模型生成字段中文注释。代码片段如下def gen_comment(header: list[str], cfg: dict) - str: llm cfg[llm] prompt f为这些数据库字段生成简短中文注释每行一个格式字段名: 注释\n \n.join(header) payload json.dumps({ model: llm[model], messages: [{role: user, content: prompt}], max_tokens: 500, }).encode() req urllib.request.Request( f{llm[base_url]}/v1/chat/completions, datapayload, headers{ Content-Type: application/json, Authorization: fBearer {llm[api_key]}, }, ) with urllib.request.urlopen(req, timeoutllm[timeout]) as resp: return json.loads(resp.read())[choices][0][message][content]跑通后你会得到类似id: 主键、user_name: 用户名的输出。这一步验证的是“表头读取 模型调用”的完整链路也是很多数据导出脚本里真正需要的组合能力。5. 本篇常见错误排查这一节按报错信息组织你遇到哪个查哪个。报错一ModuleNotFoundError: No module named tomllibPython 3.11 以下没有tomllib。解决办法是pip install tomli然后把import tomllib改成import tomli as tomllib。注意tomli只读不写读配置够用。报错二pymysql.err.OperationalError: (2003, Cant connect to MySQL server)先确认 MySQL 服务在跑再确认host和port。如果你在容器里跑 Python127.0.0.1指向容器自身应该改成宿主机地址或服务名。config.toml里host不要带http://前缀只写 IP 或域名。报错三cursor.description返回None只有执行了SELECT类语句后description才有值。如果你执行的是INSERT、UPDATE、CREATEdescription就是None。读表头必须用SELECT ... LIMIT 0别用SHOW或DESCRIBE混着来。报错四表头列名是 bytes 而不是 str这是charset没配对。config.toml里charset utf8mb4连接时传给pymysql.connect。如果还是 bytes检查 MySQL 服务端字符集和表字符集是否一致用SHOW CREATE TABLE确认。报错五TaoToken 返回 401 或 403先确认api_key没有多余空格再确认请求头是Authorization: Bearer sk-xxx。如果 Key 是在控制台新建的确认没有过期或被禁用。排障时建议先打开 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 核对 Key 状态再对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 检查请求格式。报错六LIMIT 0在视图上返回空 description某些 MySQL 版本对视图加LIMIT 0时元数据不完整。这种情况改用SELECT * FROM view_name LIMIT 1然后不取数据只取description或者直接用information_schema.columns查询。后者更稳SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE FROM information_schema.columns WHERE table_schema %s AND table_name %s ORDER BY ordinal_position;把这条 SQL 的参数用config.toml里的database和表名传入同样能拿到表头和类型而且不依赖cursor.description的行为差异。报错七配置文件路径找不到load_config默认读当前工作目录的config.toml。如果你在子目录跑脚本用绝对路径或Path(__file__).parent / config.toml。别用相对路径../config.toml换 IDE 运行配置就容易断。6. 把配置和 Key 收拢后的下一步走到这里你已经有了三样东西一份可复用的config.toml骨架、一个用cursor.description读表头的函数、一条验证过的 TaoToken 通道。接下来怎么用取决于你的场景。如果你在做数据导出把get_table_header的返回值直接作为 DataFrame 的columns配合pandas.read_sql就能生成带正确表头的 Excel。如果你在做动态建表用describe_columns拿到字段名和类型后拼CREATE TABLE注意给字符串类型补上长度VARCHAR不带长度在严格模式下会报错。如果你在做字段校验把表头列表和预期列表做集合差上游改列名时第一时间告警。关于 Key 和通道的后续使用按场景分流排障和接入问题看 API Keys 和接入文档想先验证模型效果去模型对话页发几条消息长期做编码和 Agent 的了解 Coding Plan 会更省心。链接都在前面给过这里不重复贴。最后留一个实用技巧config.toml里的api_key不要写死用环境变量覆盖。在load_config里加一行cfg[llm][api_key] os.environ.get(TAOTOKEN_API_KEY, cfg[llm][api_key])这样本地开发和 CI 环境可以用不同的 Key配置文件本身可以安全提交。表头缓存也可以加header_cache_ttl那个字段就是留给functools.lru_cache或 Redis 的表结构不常变的话缓存能省掉大量重复查询。