场景:上周五下午快6点了,我正盯着屏幕调一个接口的返回格式,工位隔板上探出一颗脑袋。是我们部门总监,手里攥着手机,微信界面亮着。
“小王,上个月华东地区Top5的产品销售数据,整理一下发我。”
我叹了口气,打开那个叫“2025全年销售汇总v3_final最终版.xlsx”的文件。十几个sheet,列名还不一样。找到“销售明细”那个sheet,筛选华东,排序,复制前5行,粘贴到微信。
刚要继续干活,手机又震了。“按产品分类再整理一份。”
那天下午,我被打断了十几次。领导想看各种维度的数据,但那个Excel太复杂,他自己搞不定,只能找我。我也没法怪他,那表格确实乱,我自己有时候都要找半天。
但我也不能一直当人工查询机。一天被打断十几次,正事全耽误了。
后来我想了个办法。既然现在大模型这么聪明,能不能让它来读Excel,领导问什么就答什么?我花了一个晚上写了个小工具,把Excel变成了一个可以问答的数据库。
思路很简单,三步走。先用pandas把Excel读进来,自动生成一份数据结构的描述(就是哪些列、什么类型、长什么样)。然后把这份描述发给大模型,让它把领导的自然语言问题翻译成pandas代码。最后我这边执行代码,拿到结果。
就这么个事。
完整代码
# pip install pandas openai openpyxlimport pandas as pdfrom openai import OpenAI# 建连接,key换成你自己的client = OpenAI(api_key="your_api_key_here")def load_and_describe(filepath):"""读Excel,顺便生成数据结构的描述"""df = pd.read_excel(filepath)# 拼一段描述文字,告诉大模型这个表长什么样desc = f"这个DataFrame变量名叫df,一共有{len(df)}行,{len(df.columns)}列。\n\n"desc += "列信息如下:\n"for col in df.columns:dtype = str(df[col].dtype)nulls = df[col].isna().sum()# 取几个示例值给大模型看看samples = df[col].dropna().head(3).tolist()desc += f"- {col}: 类型是{dtype}, 有{nulls}个空值, 示例值: {samples}\n"# 如果行数太多,也给大模型看几行真实数据desc += f"\n前3行数据预览:\n{df.head(3).to_string()}"return df, descdef generate_pandas_code(question, schema):"""让大模型把自然语言问题转成pandas代码"""prompt = f"""你是一个pandas数据分析专家。用户有一个已经加载好的DataFrame,变量名叫df。数据结构如下:{schema}请根据用户的问题,写一段pandas代码来回答。要求:1. df变量已经存在,不需要重新读取文件2. 把最终答案存到变量result里3. 只写代码,不要写解释文字4. 代码要简洁,能一行搞定的别写三行5. 如果涉及日期,列名可能叫 date、order_date 或 created_at6. 如果是数值统计,记得处理空值用户的问题:{question}"""resp = client.chat.completions.create(model="gpt-4o",messages=[{"role": "user", "content": prompt}],temperature=0# 写代码用低温,稳定一点 )code = resp.choices[0].message.content# 把markdown代码块标记去掉code = code.replace("```python", "").replace("```", "").strip()return codedef run_query(question, df, schema):"""生成代码并执行,返回结果"""# 第一步:让大模型写代码code = generate_pandas_code(question, schema)print(f"[大模型生成的代码]\n{code}\n")# 第二步:执行代码# 把df和pd放到local变量里,exec才能用local_vars = {"df": df, "pd": pd}try:exec(code, {}, local_vars)result = local_vars.get("result", "代码执行了但没有result变量")except Exception as e:result = f"代码执行出错了: {e}"return resultdef chat_with_excel(filepath):"""主函数:加载Excel,进入问答循环"""print(f"正在加载文件: {filepath}")df, schema = load_and_describe(filepath)print(f"加载完成,{len(df)}行数据。")print("="*50)# 进入交互模式,可以一直问while True:question = input("\n请输入你的问题(输入q退出): ")if question.lower() == "q":print("拜拜。")breakprint(f"\n>>> 问题: {question}")result = run_query(question, df, schema)print(f">>> 结果:\n{result}")print("-"*40)if __name__ == "__main__":# 换成你自己的Excel路径chat_with_excel("sales_2024.xlsx")运行效果
拿销售数据试了一下(数据脱敏过了),效果还不错。
问题1:“上个月哪个产品销售最多”
[大模型生成的代码]# 假设日期列叫order_daterecent = df[df['order_date'] >= '2024-11-01']result = recent.groupby('产品名称')['销售额'].sum().sort_values(ascending=False).head(5)>>> 结果:产品名称智能手环Pro 285600.0蓝牙耳机X3 234100.0充电宝20000 198500.0手机壳套装 156800.0数据线三合一 123400.0问题2:“各个地区的销售占比是多少”
[大模型生成的代码]result = df.groupby('地区')['销售额'].sum()result = (result / result.sum() * 100).round(2)>>> 结果:地区华东 35.42华南 28.17华北 18.63西南 11.28其他 6.50问题3:“退货率最高的5个产品”
[大模型生成的代码]refund_rate = df.groupby('产品名称').agg( 退货数=('是否退货', 'sum'), 总数=('订单号', 'count'))refund_rate['退货率'] = (refund_rate['退货数'] / refund_rate['总数'] * 100).round(2)result = refund_rate.sort_values('退货率', ascending=False).head(5)>>> 结果: 退货数 总数 退货率产品名称USB转接头 89 423 21.04耳机收纳袋 45 267 16.85屏幕贴膜 112 789 14.20大模型生成的代码基本都能跑。偶尔有列名猜错的情况,但schema里已经给了所有列名,大部分时候不会搞错。
几个值得说的点
关于schema描述。 这个是最关键的。大模型写代码准不准,全看schema描述得清不清楚。我把列名、类型、空值数量、示例值都放进去了,大模型一看就知道该怎么写。如果示例值不够,还可以加一列unique值的数量,让大模型知道这列是不是适合做groupby。
关于安全性。 exec()这个东西嘛,懂的都懂。如果给领导用,最好加个白名单,只允许pandas操作,别让他执行什么os.system("rm -rf /")。我在代码里用的是空的globals字典,只传了df和pd进去,已经做了基本隔离。但如果是部署到线上,还是要再加固一下。
关于执行出错怎么办。 代码里有try-except,出错了会返回错误信息。你可以把错误信息再发给大模型,让它修正代码重试。我试了一下,大部分错误重试一次就能修好。
后来
这个工具我在自己电脑上跑了一周。领导再来问数据,我直接把问题丢进去,几秒钟出结果,复制粘贴发过去。
以前每次查数据要5到10分钟,现在30秒。一天省下来的时间够我多喝两杯咖啡了。
“无他,惟手熟尔”!有需要的用起来!关注微信公众号「Nicholas与Pypi」获取更多Python实战!------加入知识库与更多人一起学习------https://ima.qq.com/wiki/?shareId=f2628818f0874da17b71ffa0e5e8408114e7dbad46f1745bbd1cc1365277631c