资讯详情

资讯详情

DeepSeek API 表格数据自动导出 Excel/CSV 全链路实战

这两年 AI 对话工具越来越普及之后我遇到最多的需求并不是帮我写一段代码或者总结一下这份文档反而是很多人拿着一大段带表格的对话内容问我怎么把这个结果变成 Excel 方便我交差其实大家都有类似的经历——让 AI 模型给你生成一份季度销售统计表消息里确实整整齐齐地列出了产品名、销售额、环比增幅但真要把它导到 Excel 里继续透视、筛选、做图表就非常尴尬。复制粘贴到 Excel 往往变成一列挤一列格式全部乱掉让模型生成 CSV 代码又得手动跑一遍脚本更麻烦的是数据一多总不可能每次都在对话里复制来复制去。所以从 API 调用、表格解析到自动化导出这套链路就成了让我反复折腾了很久、也踩了不少坑的课题。这篇文章就完整盘一盘我自己在 DeepSeek 上做表格导出全链路的落地过程调用 API 拿到数据只是起点真正的核心在于怎么把模型返回的非结构化内容解析成干净的行列结构怎么把解析结果稳定地写入 Excel / CSV以及怎么做成定时自动跑的批处理链路。整个方案不挑框架、不依赖私有平台Python 就能全部搞定。不管你是刚接触 API 调用的小白还是想直接把这套流程接进自己分析管道里的开发者应该都能从中找到可以直接复用的代码和思路。1. 为什么AI 给出表格和拿到真表格之间隔着重重困难先说说痛点不然你可能觉得我在小题大做。AI 模型很擅长在对话里呈现看起来是表格的内容比如常见的 Markdown 表格| 月份 | 销售额 | 环比 | |------|--------|------| | 1月 | 120万 | 8% | | 2月 | 132万 | 10%|这种格式人眼看着特别清楚但它是为阅读设计的不是为数据处理设计的。你把它原样复制到 Excel 里大概率会遇到三类问题第一不同单元格内容全部挤在一个 A1 单元格里字段分隔符|、空格没有被识别成列第二中英文混排的时候Excel 可能把| 120万 |整体当成文本导致数字没法求和第三一旦表格行数很多比如上百行复制粘贴这种手工操作的出错率和时间成本一下子就上来了。有人可能会说那我让 AI 直接给 CSV 啊对这是个思路但随之而来的问题是AI 在生成 CSV 时经常不按标准格式输出列值里如果包含逗号或换行它未必会恰当地加引号包裹。更常见的问题是它生成的 CSV 文本被对话界面做了字符转义真正的逗号分割符可能变成了全角逗号或者半角全角混合解析起来又是一堆脏活儿。所以当我认真考虑把这个需求工程化的时候很快就意识到如果让模型直接输出 CSV 文本人肉去看没问题机器去解析会非常痛苦如果让它直接输出规范 JSON再由我自己的代码去转 Excel整条链路反而可控得多。这算是我做完之后首先想分享的一个判断。下面整个方案都是沿着这条思路展开的。2. DeepSeek API 返回里的表格长什么样一切的起点是拿到响应2.1 调用 API 的基本姿势与超参选择DeepSeek 的接口兼容 OpenAI 的调用形式Python 里直接用 openai 库改一个 base_url 就行。这个细节很多文章讲过但我在实际项目中还是遇到几次低级问题如果环境变量没配好或者 base_url 写成了类似平台三级域名调用就会一直报连接错误而这些其实跟模型本身没有任何关系。一个稳定能跑的调用代码长这样import os import json from openai import OpenAI client OpenAI( api_keyos.environ.get(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com ) def ask_deepseek(prompt: str, response_format: dict | None None) - str: kwargs { model: deepseek-chat, messages: [ {role: system, content: 你是数据整理助手。}, {role: user, content: prompt} ], temperature: 0.0, # 表格导出场景必须低温杜绝随机发挥 max_tokens: 4096 } # 如果平台支持结构化输出加上 response_format 进一步约束 if response_format: kwargs[response_format] response_format resp client.chat.completions.create(**kwargs) return resp.choices[0].message.content这里我的第一个经验是temperature 一定不要给高。默认的 API 参数往往带一点随机性写文案没问题但做表格导出时模型一旦在某一行数据上自由发挥改了个数字你导出后的表格就是错的而且非常难发现。什么数值转换、字段缺失往往就藏在一次不低不高的 temperature 里。所以我直接把 temperature 固定成 0宁可让结果显得机械也不能让它创造性发挥。第二个经验是如果模型支持 JSON 结构化输出优先打开。DeepSeek 目前也支持 response_format 设置成 json_object 这类约束这意味着返回的内容可以直接 json.loads()不用去和 Markdown 里的 json 围栏搏斗。能用这个就用这个能省掉后面一大部分麻烦。2.2 拿回来的东西到底长什么样先看一个最基础的返回模型在对话里给的表格内容通常长这样当然可以以下是各门店销售额统计表 | 门店 | Q1销量 | Q2销量 | 合计 | |------|--------|--------|------| | 华东 | 120 | 145 | 265 | | 华北 | 98 | 132 | 230 |表面上这是一个 Markdown 表格但在 Python 变量里它就是一个普通字符串里面混着换行、竖线、中文空格甚至可能还有多余的回车。你要是直接拿 split(|) 去切会遇到各种边界情况表头行和分隔行混进来、单元格首尾有空格、某些列值里可能真的包含了竖线比如 A|B 这种型号名称一刀切也不是不行但鲁棒性很差。所以我在做解析层的时候没有直接写一个解析 Markdown 表格的函数了事而是重新设计了一套从提示词层面就开始约束的数据管线。这套管线的核心逻辑往下看。3. 解析层的核心决策与其事后费力清洗不如让模型输出 JSON3.1 思路转变从人看到机读我第一版方案确实只写了解析 Markdown 表格的代码大概逻辑是把返回内容按行分割找到包含 | 的行过滤分隔行就是那些 ---- 的行然后按 | 分割字段并清理空白。这个方案能跑但真的不稳定。为什么会不稳定因为模型的输出偶尔会带额外解释文字比如表格前加一句请查收以下数据表格后又来一句如果需要更多信息请告诉我。解析函数得想办法把这些噪声行剔除掉即便剔除了表格里一旦出现跨行单元格或者某个字段里真的包含竖线解析又会错位。后来我做了一个特别关键的转向给模型的指令里明确要求不要输出 Markdown只输出 JSON同时在系统提示词里写上角色约束你只是一台数据生成器禁止输出任何解释性文字。这样改造之后API 返回内容的模样就从带格式的文本变成了干净的结构化数据{ table: [ {月份: 1月, 销售额: 120, 环比: 0.08}, {月份: 2月, 销售额: 132, 环比: 0.10} ], summary: {总销售额: 252} }这个转换看似只是换了种表达本质上是把解析任务从自然语言理解降维成了字段名映射。模型对 JSON 的结构遵循能力很强只要提示词写清楚它返回的 key 就不会乱变。这样后续无论写 Excel、写 CSV、写数据库代码都是同一套逻辑根本不用关心表格是怎么渲染的。3.2 提示词模板一句话告诉模型该干什么我最终长期使用的提示词模板大概是这样的请根据用户要求整理数据并严格输出 JSON。 要求 1. 不要输出 Markdown 表格不要使用代码围栏不要输出多余文字。 2. 输出 JSON 格式为 {table: [{字段名: 值, ...}], summary: {字段名: 值}} 3. 所有数值字段必须是数字类型int 或 float不要加千分位逗号不要加单位。 4. 字段名使用简短中文名确保每一行的 key 完全一致。 5. 如果数据在原文中不明确使用 null 占位不要自行编造。 用户需求如下 {{用户需求}}这里清单里有一条最容易被忽略不要加千分位逗号不要加单位。因为模型默认倾向于模仿给人看的表格把数字写成120,000 元或65%。你回头解析数字类型就非常费劲120,000 转 float 直接报错还得先去掉逗号65% 还得先去掉百分号再除以 100。从源头掐断这些格式延伸后面的处理省心十倍。3.3 如果平台没有结构化输出JSON 围栏也得处理虽然 DeepSeek 支持 response_format 设置 json_object但偶尔返回内容里还是会带有 json 围栏这通常是因为模型在很长的输出中途切换了模式。稳妥的做法是解析层写一个容忍函数import re import json def extract_json(raw: str): # 去掉可能的代码围栏 raw raw.strip() # 匹配 json ... 或 ... fence_match re.search(r(?:json)?\s*([\s\S]*?), raw) if fence_match: raw fence_match.group(1).strip() # 直接尝试解析失败再看是不是被包在普通文本里 try: return json.loads(raw) except json.JSONDecodeError: # 简单粗暴从头找第一个 {从尾部找最后一个 } start raw.find({) end raw.rfind(}) 1 if start -1 or end 0: raise ValueError(响应中没有找到 JSON 对象) return json.loads(raw[start:end])这个函数不完美但覆盖率很高能处理 90% 以上的模型偏不听话场景。剩余的 10% 会在下面的坑位记录部分展开讲。4. 落盘导出从内存表格到能用的 Excel / CSV4.1 为什么我优先选 Excel 而不是 CSV如果只是导数据CSV 确实最轻量pandas 一行就能搞定。但落到真实项目里CSV 有两个让人头疼的问题一是 CSV 没有真正的单元格格式数字如果带前导零编号 001234Excel 打开后会自动摘掉前导零数据直接变脏二是 CSV 的编码在 Excel 上需要特别注意UTF-8 的 CSV 用 Excel 打开经常乱码改为 UTF-8-SIG 会让部分旧工具读不了。所以我的建议是如果这份表格是要给人继续做分析的直接在导出层用 openpyxl 生成 .xlsx如果这份表格是要给程序批量消费的CSV 也完全可以但编码务必要注意。优先 Excel 还有一个好处可以顺便把列宽、表头样式、数字格式一起做掉给使用方省掉很多手工步骤。4.2 用 openpyxl 写入工作簿在拿到解析后的 JSON 之后写入 Excel 的核心代码可以写成一个方法。重点不只是写进去还包括列宽自适应、冻结首行、数字格式设置。下面是一个我实际用的版本from openpyxl import Workbook from openpyxl.utils import get_column_letter from openpyxl.styles import Font, Alignment def write_json_to_excel(json_data: dict, output_path: str, sheet_name: str Sheet1): rows json_data.get(table, []) if not rows: raise ValueError(table 为空无法导出) wb Workbook() ws wb.active ws.title sheet_name # 写表头取第一行数据的 key保证顺序稳定 headers list(rows[0].keys()) ws.append(headers) # 写数据行 for row in rows: ws.append([row.get(h, ) for h in headers]) # 列宽自适应 for col_idx, header in enumerate(headers, start1): max_len len(str(header)) for row in rows: cell_val str(row.get(header, )) if len(cell_val) max_len: max_len len(cell_val) ws.column_dimensions[get_column_letter(col_idx)].width min(max_len 6, 50) # 表头样式 for cell in ws[1]: cell.font Font(boldTrue) cell.alignment Alignment(horizontalcenter) # 冻结首行 ws.freeze_panes A2 wb.save(output_path) print(f已导出 {len(rows)} 行 - {output_path})这里我几次踩坑之后总结出的细节是提前用list(rows[0].keys())固定表头顺序绝对不要在遍历的时候动态获取字典 key。因为 Python 3.7 以后 dict 虽然保证插入顺序但解析后的 JSON 如果某些行的 key 顺序不一致动态取 key 会导致同一列在不同行之间跑偏。固定表头之后后面用row.get(h, )去取字段缺失也能补一个空值而不是直接让程序崩掉。4.3 数字格式与空值处理最容易忽略的两件事模型输出 JSON 时数字类型基本没问题但空值很容易出幺蛾子。比如某个字段没有数据模型可能输出 null、空字符串 或者干脆不写这个 key。统一处理策略是对确实没有值的字段写空字符串数字字段如果 null 也写空这样使用方用 Excel 透视表时不会因为字符串 NULL 而误判但对必须为 0 的字段比如 环比增长率要允许模型输出 0不能因为要简洁就不给 key否则代码里取不到值就变成空了和 0 在语义上完全不同。这个问题在数据验证环节会放大所以在写 Excel 的逻辑里我额外加了一个字段类型约束表FIELD_TYPES { 销售额: number, 环比: number, 门店: text, 备注: text, }写到单元格前根据类型做一次转换number 类型就把值转成 int 或 float转不动就先记录日志再置空。这个约束表写死在代码里不会从模型输出里动态推断。原因是动态推断在数据量小时偶尔灵一旦遇到某一列全是字母数字混合模型输出类型就飘了容易把数值列识别成文本列。所以宁可维护一张字段表也不做花哨的自动识别。5. 自动化链路让导出任务定时自己跑起来5.1 简单的调度设计不只导出还要带增量手动调脚本导出一次表格没什么难度真正的价值在于把每天/每周把 AI 整理好的数据落到工作簿里这件事自动化。我做的方案分三块任务调度、增量处理、异常预警。任务调度选了最简单可控的 APScheduler。定时任务长这样from apscheduler.schedulers.blocking import BlockingScheduler from datetime import datetime def job_export_sales_report(): print(f[{datetime.now()}] 开始导出销售报表) try: raw ask_deepseek(REPORT_PROMPT, response_format{type: json_object}) parsed extract_json(raw) write_json_to_excel(parsed, sales_report.xlsx, sheet_name月度销售) print(导出完成) except Exception as e: print(f导出失败: {e}) # 这里应该接一个 webhook 或者邮件/钉钉通知 scheduler BlockingScheduler() scheduler.add_job(job_export_sales_report, cron, day_of_weekmon-fri, hour22, minute0) scheduler.start()这个代码看着平常但我在做增量处理时多走了一点弯路。刚开始我每次都是全量让模型输出所有数据然后再写 Excel后来发现输出 tokens 消耗大不说模型生成几百行表格时还会出现中途截断max_tokens 不够导致表格少几行而程序没有感知到。后来改成了**查漏补缺策略**第一次全量导出后把数据集缓存一份比如存 CSV 或者 SQLite之后每次定时跑都只把上次时间节点之后的新数据传给模型输出后追加到 Excel 底部同时更新缓存。这样做的好处是每次 API 调用量小、返回内容短模型几乎不会截断或出错坏处是逻辑多了一层要维护哪些数据已经导出过的状态。不过当数据来源是一个持续变动的业务表时这层维护非常值。5.2 失败重试与幂等保护别让脚本半夜白跑自动化脚本最怕的就是半夜跑挂了第二天早上你才发现表没出来。我在这个环节加了三重保障第一调用 API 失败自动重试最多三次用指数退避。这招对临时网络抖动特别管用。import time def ask_deepseek_with_retry(prompt, retries3, base_delay2): for attempt in range(retries): try: return ask_deepseek(prompt, response_format{type: json_object}) except Exception as e: if attempt retries - 1: raise delay base_delay * (2 ** attempt) print(fAPI 调用失败{delay}s 后重试{e}) time.sleep(delay)第二写 Excel 前先把内容备份到临时文件等完整写入并校验通过后再替换正式文件。不然如果写一半断电正式文件会损坏之前的版本也没了。用 Python 的话可以一条os.replace()搞定先写临时文件再替换这是从写数据库事务那里借来的思路。第三生成后的校验。我在写入后重新用 openpyxl 打开文件对比行数和源 JSON 的行数是否一致。如果不一致就报错。这个校验逻辑虽然简单但能拦住 90% 的看着跑完了其实丢数据的场景。from openpyxl import load_workbook def validate_export(excel_path, expected_rows): wb load_workbook(excel_path) ws wb.active real_rows ws.max_row - 1 # 去掉表头 assert real_rows expected_rows, f行数不匹配期望 {expected_rows}实际 {real_rows} print(f校验通过{real_rows} 行)5.3 日志和可观测性自动化最容易被忽视的一环自动化脚本跑久了你一定会遇到某天跑出来了但数据不对的情况。没有日志排查难如登天。我现在的做法是定义了一个简单的结构化日志每次任务记录请求开始时间、消耗 tokens、返回字符数、解析是否成功、写入行数、校验结果。用 CSV 或 SQLite 存起来都行关键是要把这些写入一条记录里方便事后对账。这里特别推荐记录API 返回的 token 消耗。如果某一天数据量没变但 token 消耗突然暴涨大概率是模型开始输出冗长的解释文字了——这是提示词失效的前兆。提前发现才能提前修不然等数据出了问题再翻日志会很被动。6. 实测踩坑记录解析错位、格式溢出和并发限流6.1 模型偶尔多输出一个行解析错位的隐形炸弹我在测试时遇到过一次极其隐蔽的错误模型返回的 JSON 里有一行的字段 key 多了个空格比如{门店: 华东, 销售额: 123}。JSON 解析不会报错但到了写入 Excel 时这行数据的销售额取不到值因为 key 是 销售额而不是销售额于是这一列就变成空值。整张表 99% 的内容都是对的只有那一格是空的肉眼还很难发现。刚开始我还以为是模型随机丢失字段后来打好日志才发现 key 前面多了个空格。把这层清洗逻辑加进 extract_json 之后问题就稳定消除def clean_keys(obj): if isinstance(obj, dict): return {k.strip(): clean_keys(v) for k, v in obj.items()} if isinstance(obj, list): return [clean_keys(item) for item in obj] return obj这个坑提醒我一件事模型输出的 JSON 在语法上不一定规范即便能 parse里面的字符串也可能包含多余空白、全角字符等情况。所有取自模型输出的 key 和 value在进入业务逻辑前至少要做一次 strip 处理。6.2 遇到过长内容被截断表格被腰斩但程序不报错大表格导出时API response 的 max_tokens 限制会导致返回被截断。截断后的 JSON 自然是无法解析的extract_json 会抛异常这反而不是最可怕的。最可怕的是提示词让模型输出 JSON结果模型在输出完毕前刚好用完了 token截断位置恰好在 JSON 尾部闭合大括号之前但前面的内容都是完整的——这种情况下 JSON 解析会失败但如果截断时候恰好碰上了某个能形成合法前缀的节点或者返回内容里缺失的部分不影响整体 parse程序就可能在无感知的情况下写出了一部分数据。我的应对方案见第 5.2 节的校验逻辑同时自己在提示词里明确标注尽量精简字段名不要输出冗余解释减少 token 占用。更重要的一点是对自定义的导出脚本如果表格行数可能超过 100 行直接把表格拆成多次请求处理单次调用只处理一部分数据用多次结果合并成最终表格比祈祷一次请求输出所有内容靠谱得多。6.3 并发限流自动化脚本跑得好好的突然 429当脚本进入定时任务阶段每天都会固定打 API一旦某段时间多个任务叠加比如几个报表刚好同一分钟触发就很容易撞上限流。主要表现是 HTTP 429 或者连接超时。解决办法不复杂把任务触发时间打散让各任务错峰执行同时在代码层做指数退避重试。另外一个特别容易被忽略的点是DeepSeek 的 API 在单账号上有并发限制。日志里要专门记录每次调用的耗时如果耗时开始不稳定地上升往往说明已经接近限流阈值了。此时应该降低调用频率而不是继续加大并发。我在坑里撞过之后现在默认做法是所有批量任务强制串行化加入一个全局队列锁宁可慢一点不求快。6.4 一个特殊问题模型把百分比符号保留导致数字转换失败我在提示词里要求输出数字类型但模型偶尔还是会把环比增长 8%写成环比: 8%。这会让 Excel 里的数字列变成文本无法参与计算。我加了一个兜底转换逻辑def to_number(value): if isinstance(value, (int, float)): return value if isinstance(value, str): s value.replace(,, ).replace(%, ).strip() try: return float(s) except ValueError: return None return None同时把这个兜底逻辑放进字段类型约束表里遇到 number 类型字段就用它转。代码上多了一个分支但确实挽救了第二次模型抽风造成的脏数据。我在日志里也单独标记了所有发生过类型转换的位置方便后续检查是不是该优化提示词而不是依赖兜底逻辑。最后关于这套方案我的几个个人判断整套链路跑下来我最大的感受是AI 导表的难点不在导而在约束。从提示词写清楚输出 schema到 API 参数里固定低 temperature再到解析层的空白清理和类型兜底核心始终是把模型愿意输出什么和程序需要什么这两件事对齐。模型能力再强也不能把宝全押在它身上——所有关键节点都要有一层校验和容错像是 Excel 写出后的二次打开像是 token 消耗的日志监控又像是失败后的自动重试。这些琐碎但必要的东西才是自动化链路真正稳下来的基础。在项目里实际跑了几个月后我还发现一个省事的小技巧把已经验证过的提示词模板和字段定义表固化成配置用 YAML 单独维护。以后模型版本升级或者字段变化时只动配置不动代码就能快速迭代不用每次重新走一遍全链路流程。这个方法我从销售报表扩到了周报汇总、库存核对、竞品数据整理等场景套路完全一致搬过去改个配置就能用。有需要细化的地方比如多表合并、异常告警接入群机器人、数据库直接落表的写法欢迎在评论区留言我看到后会继续补充实测细节。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →