把“帮我做个 AI 小工具”直接拿去接单,往往范围太大。一个更容易定义和验收的起点,是按指定编号整理 CSV:保留第一条、重复项单独输出、空编号进入待确认文件,并提供处理报告。这类需求可能来自资料维护和商品清单整理,但是否有人愿意付费,必须靠真实需求验证。

本文演示怎样用 AI 协助澄清规则、审查代码,再交付一个本地 Python 小工具。示例中的去重逻辑由确定性程序完成,运行时不调用模型,不上传客户表格;不虚构客户、订单或收入。代码是教学实现,适合少量普通文本资料,不是企业级数据平台。

一、先定义业务规则,再让 AI 写代码

“去掉重复项”至少有四种含义:整行相同、编号相同、姓名相同、模糊判断相似。本篇只做一种:对 编号 字段去除首尾空白后精确比较,保留第一次出现的记录。A01 与 a01 仍是不同编号,不自动合并。

需求单应明确:

  1. 输入:UTF-8 或带 BOM 的 UTF-8 CSV,第一行为唯一且非空的列名;本例字段为编号、名称、备注。
  2. 去重键:只看编号,首尾空白不参与比较;其他列不参与判断,不声称找出了所有业务上的同一商品。
  3. 空编号:单独进入待确认文件,不能把全部空值合并成一条,也不能替客户生成新编号。
  4. 输出:保留、重复、待确认三份 CSV,加一份统计报告;不覆盖原文件。
  5. 范围:教学版限制输入不超过 5 MB、数据行不超过一万行,超过时停止。它不处理 Excel 工作簿、多表关联或模糊匹配。

把这些规则提供给 AI,比只问“写个去重脚本”更容易得到可核对的实现。可用提示词:

请帮我审查一个本地 CSV 去重需求,不要先扩展功能。
按编号去首尾空白后的精确值比较,保留首次出现。
空编号单独待确认;重复行保留完整原记录;不覆盖输入。
指出可能漏掉的文件编码、引号、逗号、换行和表头问题。
先给验收用例,再解释实现方案;所有客户数据用虚构样例代替。

AI 可协助发现边界和解释代码,最终仍要运行测试。代码看起来专业,不代表它真的正确处理了带逗号或换行的单元格。

CSV 小工具交付边界:需求规则、保留记录、重复记录、空编号待确认与统计报告

上图为原创业务流程示意,不是实际客户数据。

二、准备环境与四行测试资料

安装可正常使用的 Python 3,建立一个独立目录。此工具只用标准库 csv、json 和 pathlib,不需要 API 密钥、数据库或云服务器;不会因为叫“AI 小工具”就凭空产生模型调用费。

将下面内容保存为 UTF-8 的 input.csv:

编号,名称,备注
A01,示例水杯,首条
A01,示例水杯,再次录入
,待补编号,人工确认
A02,示例笔记本,"含逗号,仍是一格"

预期是保留 2 条、重复 1 条、待确认 1 条,三类合计为 4。这个预期由规则和样例人工推导,不是模型估计。

用正式资料前,先确认拥有处理权限并保留原件。若工作要求不能联网,代码审查时也只给 AI 看脱敏样例和规则,不把整个客户目录上传。

三、实现一个不覆盖源文件的最小版本

保存下面代码为 dedup_csv.py。使用 csv.reader 而不是按逗号手动切割,才能按 CSV 规则处理引号中的逗号与换行。导出采用 UTF-8 BOM,便于部分表格工具识别中文,但仍需实机确认。

import csv
import json
import sys
from pathlib import Path

def process(source, target):
    source = Path(source)
    target = Path(target)
    if target.exists():
        raise ValueError("输出目录已存在,请换一个新目录,避免覆盖")
    if source.stat().st_size > 5 * 1024 * 1024:
        raise ValueError("教学版最多处理 5 MB 文件")
    with source.open(encoding="utf-8-sig", newline="") as file:
        rows = csv.reader(file, strict=True)
        header = next(rows, None)
        if not header or any(not x.strip() for x in header):
            raise ValueError("缺少表头或列名为空")
        if len(set(header)) != len(header) or "编号" not in header:
            raise ValueError("列名重复,或没有精确名为 编号 的列")
        index = header.index("编号")
        groups = {"kept": [], "duplicates": [], "review": []}
        seen = set()
        total = 0
        for row in rows:
            if not row:
                continue
            total += 1
            if total > 10000:
                raise ValueError("教学版最多处理一万行数据")
            if len(row) != len(header):
                raise ValueError(f"第 {total} 条数据列数不一致")
            key = row[index].strip()
            if not key:
                groups["review"].append(row)
            elif key in seen:
                groups["duplicates"].append(row)
            else:
                seen.add(key)
                groups["kept"].append(row)
    escaped = 0
    def safe(value):
        nonlocal escaped
        if value.lstrip().startswith(("=", "+", "-", "@")) or value.startswith(("\t", "\r")):
            escaped += 1
            return "'" + value
        return value
    target.mkdir(parents=True, exist_ok=False)
    for name, values in groups.items():
        with (target / (name + ".csv")).open("x", encoding="utf-8-sig", newline="") as file:
            writer = csv.writer(file)
            writer.writerow([safe(x) for x in header])
            writer.writerows([safe(x) for x in row] for row in values)
    report = {"total": total, **{name: len(values) for name, values in groups.items()},
              "spreadsheet_text_escapes": escaped}
    if total != sum(len(values) for values in groups.values()):
        raise RuntimeError("统计不一致")
    with (target / "report.json").open("x", encoding="utf-8") as file:
        json.dump(report, file, ensure_ascii=False, indent=2)
    return report

if __name__ == "__main__":
    if len(sys.argv) != 3:
        raise SystemExit("用法:python dedup_csv.py 输入.csv 新输出目录")
    try:
        print(json.dumps(process(sys.argv[1], sys.argv[2]), ensure_ascii=False))
    except (OSError, UnicodeError, ValueError, csv.Error) as error:
        raise SystemExit("处理停止:" + str(error))

这里有一个容易忽略的交付边界:某些表格程序会将以等号等字符开头的文本解释成公式。示例导出时对部分危险前缀加单引号,并记录改写次数。这不是完整的表格安全沙箱,也会改变导出文本的字面值,例如负数文本也可能被加前缀。客户要求逐字符完全一致时,应先约定安全导入方案,不能悄悄修改后声称原样交付。

四、运行并逐文件验收

在输入与脚本所在目录执行:

python dedup_csv.py input.csv output-demo

若系统使用 py 或 python3 启动 Python,就使用对应启动命令。output-demo 必须是尚不存在的新目录。运行成功应得到:

  • kept.csv:A01 的首次记录,以及 A02。
  • duplicates.csv:A01 的再次录入记录。
  • review.csv:编号为空的记录。
  • report.json:总数与三个分组数量,以及表格文本保护次数。

对本篇四行示例,统计预期为 total=4、kept=2、duplicates=1、review=1。打开三份 CSV 验证原来的备注有没有错列,特别检查“含逗号,仍是一格”没有被拆成两列。

编号去首尾空白只用于比较,不会把原单元格的所有空白强行重写。因此,首条原记录如果写成 A01 ,导出仍保留其内容;是否清洗展示值是另一项需求,不应与去重偷偷合并。

五、补齐容易暴露问题的测试

至少追加以下用例,再决定是否交付:

  1. 相同编号、不同备注:保留第一条,后续完整记录进重复文件,不根据 AI 判断谁更“正确”。如果客户要保留最新记录,需要另加可信时间字段与排序规则。
  2. 多个空编号:都进入待确认,不能互相覆盖。空行可以跳过,包含空单元格的数据行仍应按规则处理。
  3. 大小写与前导零:A01、a01、001、1 按本例是不同字符串。用 Excel 打开时若自动转换格式,要通过文本导入核对,不把显示变化怪到去重算法上。
  4. 引号和换行:在带引号的备注里加入逗号、换行,确认仍是一条记录;不要用物理行数直接代替 CSV 记录数。
  5. 异常输入:缺少编号列、重复列名、非 UTF-8 编码、列数不一致,应明确停止,不能输出一个看似成功但漏行的结果。
  6. 已有输出目录:再次使用同名目录应停止,不覆盖上次结果。更换新目录前先检查旧任务状态,避免误把不同版本混用。

CSV 工具验收清单:数量守恒、规则边界、异常停止和可恢复交付

上图为原创验收示意。教学实现仍有边界:文件写出中途遇到磁盘错误,可能留下不完整的输出目录,需要保留错误、标记该次任务失败,再换新目录重跑,不能只看某个 CSV 已存在就判定成功。

六、把程序变成一份可交付服务

交付目录可以包括源代码、运行说明、虚构样例、测试记录和客户确认过的规则。客户实际数据与源代码分开保存,按约定处理备份和留存。运行记录写任务编号、输入文件名、处理数量和异常,不必记录敏感字段全文。

报价范围应写明支持的编码、列名、去重规则、文件量、运行环境与修改次数。新增“支持 Excel 多工作表”“按地址模糊合并”“自动同步业务系统”,都属于新范围,不能当成顺手增加的小按钮。

获客时展示脱敏的输入、三个输出分组和验收记录,比只说“AI 一键清洗数据”更能解释服务。成本包括需求确认、代码审查、样例测试、客户环境适配和后续支持;不能把写出几十行代码等同于已经具备稳定收入。

如果后面做成网页,需重新设计认证、上传限制、临时文件清理和权限隔离。不要为了演示方便,把客户目录暴露给一个临时文件服务器。本文完成的是本地、单任务、规则明确的最小交付,先验证真实需求,再决定是否扩展。

官方资料