ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

OpenCode实战:用Skill高效解析xlsx表格数据

2026/10/8 15:09:34 拓冰建站 浏览量
OpenCode实战:用Skill高效解析xlsx表格数据 前阵子有个朋友问我你天天用 OpenCode 写代码能不能让它帮我看看那个 Excel 表格里到底有什么。我说直接丢进对话里让它“分析一下”它十有八九只是在猜。因为 xlsx 根本不是文本而是一个 zip 压缩包里面是一堆 xml。后来我专门给 OpenCode 配了一个 skills把 xlsx 的解析和分析流程封装进去才算真正把表格分析这件事跑通。这篇就当是给同样想用 OpenCode 处理 xlsx 的朋友一份手记从 skill 怎么建、脚本怎么写到实际跑出来的效果和踩过的坑一次说清楚。1. 为什么xlsx必须交给skill而不是直接扔给对话1.1 xlsx本质上是个压缩包模型读不到很多人以为给 AI 一个 xlsx 文件路径它就能像人一样打开 Excel 看到表格内容。实际上xlsx 的后缀改一下变成 zip再解压开里面是[Content_Types].xml、xl/worksheets/sheet1.xml这些文件。真正的单元格数据都藏在那一堆 XML 节点里而且坐标、字符串、数值、样式全混在一起。这也意味着你在对话里让 Agent “看一下这个文件”它看到的只是二进制乱码。语言模型的上下文窗口能处理文本但没法直接理解压缩格式。OpenCode 作为终端工具可以帮你读取文本类型的文件但遇到二进制文件同样无能为力。所以第一步就得想清楚xlsx 不能直接投喂给模型必须先把里面的数据“解压”成能被模型理解的结构化文本或 JSON。1.2 一次性脚本最大的问题是每次都在重新造轮子有人会说那让 Agent 写一段 Python 不就行了确实可以。你告诉它“用 openpyxl 打开文件遍历每个 Sheet输出列名和统计信息”Agent 能在几十秒内生成一段能跑的代码。但问题是这种临时生成的代码基本不能复用。不同文件的 Sheet 名不同、列头不同、空值情况不同下一次换个文件你又要重新描述一遍需求Agent 又得重新构思、重新调试。我一开始就是这么干的连续折腾几个文件之后发现大量时间浪费在了重复沟通上。有时候它还会把列名里的空格处理错、漏掉某个 Sheet或者把日期列当成字符串。你会发现每次让 Agent 写临时脚本就像每次都让一个新实习生从零开始做同一个报表他可能做得出来但你得在旁边反复纠偏。1.3 skill把“操作手册”交给Agent让它自己照着做skills 机制的逻辑其实非常像公司里的操作手册。你不需要每次跟新人解释“先干嘛后干嘛”他会自己翻开手册照着规范执行遇到特殊情况也知道去找哪个工具排查问题。具体到 OpenCode一个 skill 就是一个文件夹里面放一个SKILL.md文件。OpenCode 会把所有已安装 skill 的描述集中起来给模型看当你的请求命中某条描述时模型就把这个技能的系统提示加载进来然后按部就班地执行。这就把“读 xlsx、出摘要、做统计”变成了一种可复用的能力下次你只需要说一句“分析这个文件”Agent 就会自己加载脚本、解析、汇总、输出结论。2. 先把OpenCode跑起来再给skill安个家2.1 安装OpenCode的两种实用方式我用的方式是 npm 全局安装终端里跑一句就行npm install -g opencode-ai装完验证一下opencode --version能看到版本号说明就绪了。如果你的机器上没有 Node.js记得先装一个 18 以上的版本。Ubuntu 用户如果用 apt 直接装 nodejs版本很可能比较老建议走 NVM 装新版本否则后续装依赖的时候会踩坑。官方 Releases 页面其实也提供免安装的二进制包下载下来解压就能用适合不想碰 Node 的人。2.2 skill该放全局还是项目内OpenCode 会读取两个位置的 skills一个是项目根目录下的.opencode/skills/一个是用户全局的~/.config/opencode/skills/。我的习惯是通用技能放全局项目相关的技能放项目内这样换项目时不会被无关技能干扰。比如“xlsx 分析”这种任何项目都可能用到的能力我就放在全局mkdir -p ~/.config/opencode/skills/xlsx-analyzer目录名字就是 skill 的名字这里我起名为xlsx-analyzer。里面只放一个SKILL.md是完全可以的也可以再放辅助脚本。2.3 一个SKILL.md就能让Agent工作起来一个最小的 SKILL.md 长这样--- name: xlsx-analyzer description: 当用户需要分析、查看、汇总、统计 Excel 或 xlsx 文件时使用这个 skill。 --- # xlsx 分析助手 你负责分析 Excel 文件。用户会给出 xlsx 文件路径请按以下步骤执行 1. 确认文件绝对路径存在。 2. 用 Python openpyxl 读取文件。 3. 遍历所有 Sheet输出 Sheet 名称、行数、列数。 4. 输出每列的名称、类型、缺失值数量。 5. 对数值列求均值、最大值、最小值。 6. 对文本列统计唯一值数量。 7. 用中文总结文件核心信息。这个地方有一个关键点description 一定要覆盖用户可能的各种说法。只写“当用户需要 xlsx 分析”远远不够因为用户可能说的是“看一下表”“统计一下”“汇总数据”或者直接给路径。把“分析、查看、汇总、统计、Excel”这些词都塞进 description命中率才高。这个细节我在实战中吃过亏后文细说。3. 我把“分析xlsx”的能力封装成了skill3.1 核心逻辑放脚本流程指令放SKILL.md我最早犯过一个错误试图把完整 Python 代码写进 SKILL.md让 Agent 照着抄。结果它经常抄错 openpyxl 的 API尤其是read_only这种参数稍微写错一个就崩。后来我换了个思路把可计算的逻辑写进独立的 Python 脚本SKILL.md 里只告诉 Agent “去跑这个脚本解析输出结果”。这个思路对几乎所有 skill 都成立。Agent 擅长的是根据流程做出判断、组织语言、处理异常而不是一字不差地背 API。把固定逻辑放进脚本等于给 Agent 提供了一个稳定可靠的“电钻”它只需要决定在哪个位置钻孔、钻完之后怎么验收而不是自己用牙咬。3.2 配套的解析脚本直接抄走就行我在xlsx-analyzer目录下放了一个analyze_xlsx.py内容如下#!/usr/bin/env python3 快速读取 xlsx 并输出结构化摘要。 import argparse import json import sys from datetime import datetime, timedelta from pathlib import Path from openpyxl import load_workbook def excel_serial_to_date(value): if isinstance(value, (int, float)) and value 20000: return (datetime(1899, 12, 30) timedelta(daysvalue)).date().isoformat() return value def analyze(path): wb load_workbook(path, read_onlyTrue, data_onlyTrue) result {file: str(path), sheets: []} for ws in wb.worksheets: rows ws.iter_rows(values_onlyTrue) try: header next(rows) except StopIteration: continue row_count 0 cols [] for h in header: cols.append({ name: str(h) if h is not None else col, types: set(), non_null: 0, numeric_samples: [], sample_values: [] }) for row in rows: row_count 1 for i, value in enumerate(row): if i len(cols): continue if value is not None: col cols[i] col[non_null] 1 col[types].add(type(value).__name__) if len(col[sample_values]) 3: col[sample_values].append(excel_serial_to_date(value)) if isinstance(value, (int, float)) and not isinstance(value, bool): col[numeric_samples].append(value) sheet_summary {name: ws.title, rows: row_count, cols: len(cols), columns: []} for c in cols: info { name: c[name], types: sorted(c[types]), non_null: c[non_null], samples: c[sample_values], } s c[numeric_samples] if s: info[mean] round(sum(s) / len(s), 2) info[min] min(s) info[max] max(s) sheet_summary[columns].append(info) result[sheets].append(sheet_summary) wb.close() return result if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(file) args parser.parse_args() if not Path(args.file).exists(): print(json.dumps({error: 文件不存在}), filesys.stderr) sys.exit(1) try: print(json.dumps(analyze(args.file), ensure_asciiFalse, indent2)) except Exception as e: print(json.dumps({error: str(e)}), filesys.stderr) sys.exit(1)这个脚本用到了read_onlyTrue几百 MB 的 xlsx 也能平稳吃下data_onlyTrue是为了读公式的缓存结果。脚本输出的是标准 JSONAgent 后续可以直接基于这些结构化数据做解读不需要再猜字段。我也把 Excel 的“数字日期”做了基础转换比如45252这种序列号会被转成2023-12-08避免 Agent 在日期问题上瞎猜。3.3 更新SKILL.md让Agent调用脚本现在把 SKILL.md 改成指向这个脚本--- name: xlsx-analyzer description: 当用户需要分析、查看、汇总、统计 Excel 的 xlsx 文件的字段、行数、数值、表结构时使用这个 skill。 --- # xlsx 分析助手 1. 获取用户提供的 xlsx 文件绝对路径若路径不存在则要求用户确认。 2. 运行命令 python3 ~/.config/opencode/skills/xlsx-analyzer/analyze_xlsx.py 文件绝对路径 3. 如果脚本报错根据 stderr 中的错误信息尝试定位问题例如文件损坏、编码问题、openpyxl 未安装等并输出修复建议。 4. 如果执行成功将输出的 JSON 摘要翻译成自然语言结论 - 文件包含哪些 Sheet - 每个 Sheet 有多少行、多少列 - 各列的数据类型和非空数量 - 数值列的均值、最大最小值 - 文本列的大致内容范围 5. 最终用中文给出简洁的总结指出文件的数据质量问题和统计结论。这样 Agent 的每一个动作都有明确依据不会再自由发挥乱写代码。你在实际使用的时候把~/.config/opencode/skills/换成你自己放 skill 的路径即可。4. 实战一句“分析这个文件”看Agent怎么干活4.1 description写不好skill一辈子不会触发我第一次把这个 skill 装好后对着 OpenCode 说“看看 data/sales.xlsx”结果 Agent 完全没反应直接写了个 Python 脚本还没跑对。原因就出在 description 里没有出现“看看”这种说法。模型在选择 skill 的时候是把用户输入和所有 description 做语义匹配的匹配不上它就认为是普通任务。我把 description 改成“分析、查看、汇总、统计、Excel 的 xlsx 文件”再把“字段、行数、数值、表结构”这些词汇都加上之后基本上一发出请求就能命中。这里给个建议写完 skill 之后多换几种表达方式在 OpenCode 里试比如“这表里有多少行”“给我看一下这个 excel 的统计信息”哪种触发不了就补哪种关键词到 description。4.2 一次完整对话从输入到输出假设文件是data/sales.xlsx我在 OpenCode 里输入分析一下 data/sales.xlsxAgent 命中 skill 后会自动执行我预设的流程。在终端里看到的输出大概是这样[agent] 正在使用 xlsx-analyzer skill 分析文件。 [agent] 运行命令: python3 ~/.config/opencode/skills/xlsx-analyzer/analyze_xlsx.py data/sales.xlsx [agent] 解析完成: { file: data/sales.xlsx, sheets: [ { name: 销售明细, rows: 1280, cols: 6, columns: [ {name: 日期, types: [datetime], non_null: 1280, samples: [2024-11-02, 2024-11-03]}, {name: 区域, types: [str], non_null: 1280, samples: [华东, 华北]}, {name: 商品, types: [str], non_null: 1280, samples: [保温杯, 雨伞]}, {name: 数量, types: [int], non_null: 1278, mean: 5.2, min: 1, max: 20}, {name: 金额, types: [float], non_null: 1278, mean: 356.0, min: 10.0, max: 1200.0}, {name: 渠道, types: [str], non_null: 1275, samples: [线下, 线上]} ] } ] } [agent] 总结文件包含一个名为“销售明细”的 Sheet共 1280 行、6 列。数量列有 2 个缺失值金额列有 2 个缺失值渠道列有 5 个缺失值。区域主要是华东和华北商品包含保温杯、雨伞等。数量平均 5.2 件金额平均 356 元。整体来看数据质量还可以缺失率不到 1%。整个过程我只需要给一句指令后面全是 Agent 自己完成的。这就是 skill 该有的体验。4.3 用skill和让Agent临时写代码差距在哪差异最大的地方是心态。用临时脚本的时候我总得盯着 Agent 的每一步因为它可能选错库、漏掉 Sheet 或者把输出搞成一行字符串。用 skill 之后我只需要关心结果对不对。脚本是固定的输出格式是确定的Agent 的工作只是“解读 JSON 给出结论”容错率高了很多。如果你也有这种“反复指挥同一个 Agent 干同一件事”的需求我强烈建议你做个 skill哪怕流程再简单也值得。5. 用了一个月之后xlsx分析里的固定坑5.1 日期字段不是字符串也不是时间戳xlsx 里的日期通常被 openpyxl 读成datetime对象这没什么问题。但有些导出工具会把日期写成 Excel 序列号比如45252如果你直接让 Agent 统计它可能当成数值处理。我在脚本里已经写了一个转换凡是大于 20000 的数字都会尝试转成1899-12-30起算的日期。如果你自己手写代码一定要记住这个边界否则日期列会被当成普通数字最终统计出的平均值毫无意义。5.2 带公式的单元格拿不到计算结果很多表格的“合计”“毛利率”是公式比如SUM(B2:B100)。如果这个文件从来没有被 Excel 或 WPS 打开计算过公式的缓存结果是不存在的data_onlyTrue读出来就是None。我的处理方式是在 SKILL.md 里加了一条如果某个数值列非空数量很低但列名看起来像“合计”“汇总”就提示用户“该列存在公式单元格需要先用 Excel 打开并保存一次”。如果你不想麻烦用户也可以让 Agent 用 LibreOffice 的无头模式批量计算但那个过程更慢适合在自动化流水线里做。5.3 大文件一load就内存爆炸有一次我拿到一个 200MB 的 xlsx直接用了默认模式加载OpenCode 所在的终端卡了快一分钟之后系统内存报警。后来我把所有读取都改成read_onlyTrue配合iter_rows(values_onlyTrue)一行一行迭代内存占用降到了十分之一。你写脚本的时候千万不要用ws.max_row这种需要遍历全部行的属性read_only模式下这个属性根本不准确还会直接抛异常。老老实实迭代行才是王道。5.4 依赖和相对路径最容易让skill当场翻车新环境最容易出两个问题一是没有装 openpyxl脚本 import 直接失败二是 Agent 运行脚本时的工作目录不在你的数据目录下相对路径找不到文件。我的解决办法是SKILL.md 里第 1 步永远是“确认绝对路径”并且脚本开头也做了Path(args.file).exists()检查不存在就走sys.exit(1)。至于依赖我干脆在脚本顶部放了 try-exceptimport 失败时输出“请执行 pip install openpyxl”的提示。这样 Agent 看到错误信息后自己能给出修复命令不需要你再查一遍。6. 顺着这个思路把skill扩展成通用表格工具6.1 多支持一个csv格式改动一句话csv 是纯文本模型可以直接读理论上不需要脚本。但 csv 也有编码和分隔符的问题。我在 SKILL.md 里加了一句“如果文件后缀是 csv优先用 pandas 读取注意编码 UTF-8 或 GBK分隔符可能是逗号或分号。”然后把 description 里的“xlsx”改成“Excel 表格xlsx 或 csv”。这样同一个 skill 就能覆盖两类常用表格实用性立刻翻倍。6.2 让Agent顺手把报告写成markdown文件分析完了光在终端里看结果过一会儿就忘了。我后来给 SKILL.md 加了一步“在完成总结后将结果整理成 markdown 报告保存到与源文件同级的report.md包含表格概览、字段说明、统计结论和数据质量提醒。”这样下次想回顾或者把结果发给同事直接把这个文件丢过去就行。Agent 做这件事非常顺手因为它已经拿到了所有结构化数据剩下的只是写文档。6.3 要是还想画图先把字体问题解决了如果你希望 Agent 进一步画柱状图、饼图可以在脚本里引入 matplotlib但一定要提前处理中文字体。我之前在 Linux 服务器上让 Agent 画图输出图片里所有中文标题都是方块排查了半天发现是系统没有中文字体。后来在脚本开头加了动态指定字体的逻辑比如plt.rcParams[font.sans-serif] [Noto Sans CJK SC]并在 SKILL.md 里注明“如果绘图报字体错误先安装字体再重试”。这个坑几乎每个做数据可视化的 Agent 都会遇到值得留心。6.4 保持skill的边界别把全家桶塞进去skill 不是越大越好。我在维护过程中发现描述越啰嗦模型越容易误触发脚本越复杂越难排查问题。我现在更倾向把一个 skill 只解决一个问题xlsx-analyzer就负责“读 xlsx 并输出摘要”报表生成、画图另开一个 skill或者让 Agent 在后续对话中临时做。边界清楚了技能之间的协作反而更顺模型也知道该在什么时候调用谁。这个 skill 我用了将近一个月最大的体会是把重复的、可确定性的工作从“对话”里剥离出来交给脚本让 Agent 专注在判断和表达上才是让 AI 工具真正省心的方式。如果你也经常在 OpenCode 里处理表格不妨照这个思路做一个自己的 xlsx skill先从最小的版本开始后边用着缺什么再补什么。