前言:面对几千行Excel客户名单,逐行人工排查既低效又容易漏判。本文分享一个Excel智能筛选小工具的完整实现:用自然语言规则定义判断标准,通过AI批量推理一次性给整表打分,高于阈值的行自动置灰,并复盘NaN序列化、列名索引等三个踩坑,让线索筛选这类重复劳动交给电脑完成。
我写了个小工具,让AI替我把几千行Excel看了一遍
先说一个做业务的朋友都懂的场景。
你手里有一份名单。可能是采集来的企业名录,也可能是某个行业群里导出的成员表,几千行,每一行有一个昵称,一个简介。需求很简单,把里面做资金过桥、摆账配资、大宗贸易的客户挑出来。
你打开表格,从第一行开始看。
「XX融资信息服务」,标记。「资金过桥拆借」,标记。下一行,老王农机厂。再下一行,绿色蔬菜种植合作社。再下一行,某某机械制造有限公司。。。
看到第一百行的时候,你的眼睛已经开始自动跳过简介列了。看到第五百行,你会开始怀疑人生。
这种活儿,我最近就碰上了。然后我的处理方式比较骚,我没有安排人肉,而是花了一个晚上写了个单文件的小工具,让AI替我把整个表扫了一变。
今天把整个过程摊开讲讲,包括我踩的三个坑。

先说这个工具干了啥。它叫 lavaDemo.py,名字随便起的,别在意这些细节。一个 Flask 加 pandas 加 Laya 的单文件小东西,流程一句话能说完,网页上拖进去一个 Excel,解析完交给 Laya 批量推理,给每行追加两列,一个「金融贸易相关分」,一个「是否目标客户」,分数高于阈值的行在页面上直接置灰,最后还能下载一个目标行带灰色背景的标记版 Excel。
首次推理会从 HuggingFace 下载 multilingual 权重(约 650MB)
对,置灰。不是删掉,是置灰,还带删除线。这个设计后面会说,我个人特别喜欢。
可能有小伙伴纳闷,Laya是个啥。你如果关注这个领域的话,可以把它理解成一个能听懂人话规则的批量判断引擎。以前你想让程序从一堆文本里筛东西,得写正则,得维护关键词表,词表永远追不上现实的变化。而 Laya 的玩法是,你用自然语言把判断标准写清楚,它来给你逐条打分。
核心就这几行。
questions = {
"is_financial_trade": laya.Noul(
instructions="判断该用户的昵称和简介是否属于金融行业、资金过桥、"
"摆账配资或大宗贸易资金增量业务。注意排除纯农业种植或机械制造类的实体厂家。"
)
}
注意这句规则里有个细节,我不光写了要什么,还写了排除什么。因为不写的话,那些农业种植、机械制造的实体厂家很容易被误判进去,人家简介里带个「供应链」三个字,分就上去了。规则里同时写正向和排除项,这是我踩过坑之后学到的。
然后是最关键的一个设计,我把它单独拎出来讲,因为我觉得这是整个 Demo 里最值钱的一行思路。
不要把推理放在循环里逐条调用。
你想想看,几千行数据,如果写成 for 循环一行一行去请求,那速度会让你回到拨号上网时代。正确的做法是把每一行的昵称和简介组装成一个 JSON 对象,塞进一个叫 batch_states 的数组,然后一次性交给 laya.evaluate_batch。
results = laya.evaluate_batch(states=batch_states, questions=questions)
scores = [float(r.is_financial_trade.probability) for r in results]
一次调用,处理整个数组。Laya 底层是张量并行化的,整批一起算,吞吐量和逐条请求完全不是一个量级。
写到这儿的时候,我脑子里蹦出来的是福特流水线。1913年之前,装配一辆车要十二个小时,流水线出来之后,九十分钟。车还是那些车,工人还是那些工人,变的只是组织方式。逐条调用和批量推理的关系,就是十二个小时和九十分钟的关系。数据还是那些数据,模型还是那个模型。
返回的每个结果带一个 probability,零到一之间的小数。我设的阈值是 0.8,超过的标记成目标客户,写回 DataFrame,追加两列,完事。
说到这儿都是顺的。接下来是踩坑环节,这部分我写的时候手都是抖的,因为每一个坑都真实地浪费过我的生命。
坑一,rows 必须按列名索引。后端一开始我偷懒,返回了按列位置排列的数组,结果前端用 r[‘昵称’] 去取值,全部拿到 undefined,页面上表格有框,里面空空如也,置灰判断也跟着失效。我盯着那个空表格愣了好一会儿,还以为是前端渲染的问题,排查了半天才反应过来是数据结构的事。后来统一改成「列名做key的对象数组」,世界清净了。
坑二更阴间。Excel 里的空单元格,pandas 会读成 NaN,而 Flask 默认会把它序列化成 NaN 字面量。兄弟们,这不是合法的 JSON。浏览器拿到响应,res.json() 直接给你抛一个 SyntaxError: Unexpected token ‘N’。你敢信???一个 NaN,能把整条链路干碎。
更骚的是,我一开始想用 df.where(pd.notna(df), None) 一把梭搞定,结果发现在 pandas 3.x 的混合类型列上它压根不生效。最后老老实实逐值转换。
rows = [
{str(k): (None if pd.isna(v) else v) for k, v in rec.items()}
for rec in df.to_dict(orient="records")
]
坑三最朴素,pip 直连 pypi 超时,国内环境老生常谈了,加个清华镜像就好,不展开。

这三个坑填完,工具就能跑了。拖入 Excel,页面显示总条数、高于阈值的条数,还有当前用的推理引擎。对,这里要坦诚一点,如果你没装 Laya 的 SDK,脚本不会报错,它会自动降级成一个本地关键词 mock 打分,命中「资金」「过桥」「摆账」这些词就加分,命中「种植」「机械」就扣分。这个 mock 只能演示流程,不具备真实的语义判断能力,我自己测试的时候是装了 SDK 跑真推理的,但代码里保留了完整的降级路径,方便没条件的朋友先把流程跑通。
还有个我自己挺得意的小交互。阈值输入框绑了 oninput,你拖动阈值的时候,前端在本地重新渲染,不重新请求后端。0.8 调到 0.7,页面上置灰的行唰唰地实时变化,那种手感,怎么说呢,像在拧一台收音机的旋钮找台。
回到置灰这个设计。为什么是置灰加删除线,而不是直接删掉。
因为我后来想明白一件事,这些被筛掉的行,不是垃圾。那个「老王农机厂」,人家老王可能真的在某个时刻需要一笔过桥资金,只是今天的他不是我的客户。筛掉的意思不是「你是错的」,是「你不是现在的答案」。所以灰色,划掉,但留着。
聊到这儿,我想再往深了说两句。
这个 Demo 表面上是个线索筛选工具,但我写着写着发现,它其实是一个更通用的模板。凡是「用一句自然语言规则,给表格每一行打分」的需求,线索筛选、风险标记、文本分类,全都能往里套。questions 字典里加一个 laya.Noul,就是加一个判断维度,取结果的时候对应取新字段就行。改规则就是改一句人话,改阈值就是改一个数字。
我一直觉得,软件世界里有个特别顽固的东西,叫「规则的表达成本」。以前表达一条业务规则,要翻译成代码,翻译成关键词表,翻译的人还得懂技术。现在表达成本变成了一句话。一句话和一段代码之间隔着的,就是当年十二个小时和九十分钟之间隔着的东西。
大时代啊,朋友们。
最后给想上手的朋友交个底。环境是 Python 3.12 加 pandas 3.0 加 Flask 3.1,装依赖记得走镜像,SDK 按官方渠道装,没装也能跑 mock。判断阈值在文件顶部的 THRESHOLD,想换判断维度就去 questions 里加规则。坦率的讲,这玩意儿就是个晚上写出来的 Demo,肯定有粗糙的地方,但它把「批量推理该怎么做」这条路完整走通了,你拿去改造成自己的东西,应该比从零开始快得多。
那个让眼睛报废的、看五百行怀疑人生的活儿,现在是我的电脑在干。
我只是写了一句规则。
剩下的,交给张量。
(完)
/ 注意,本项目仅在 SyncMein 的500人小群分享。
/ 谢谢你看完了我的文章,我们下次再见吧。
/ 作者:明察
/ 投稿或爆料,请联系邮箱:mingcha@gqmg.com













暂无评论内容