止观
AI前沿观察

[F9]拆完1个竞品小程序,我倒推出整本需求清单(附截图取证法)

2026-10-03 · AI 项目实战 · 作者:潇然 · 全文约 4100 字 · 阅读约 13 分钟

上周开开发评审,产品同事问我需求文档是哪来的。我说是一百多张截图喂出来的,他以为我在开玩笑。那份清单后来成了开发排期的唯一依据:4 个页面模块、54 条功能条目、前后补证 11 轮,全程没写一行产品代码,也没开一次“到底要不要这个功能”的会。

拆的是某家减脂类小程序。这个赛道工具同质化严重,与其自己憋需求,不如把一个活得不错的竞品逆向成一本文档能直接用的规格书。这篇讲整套做法:怎么取证、怎么把截图变成结构化条目、怎么从功能倒推出需求,以及怎么用两条小脚本防住大模型在中间掺假。

※ ※ ※

📸 第一原则:界面不撒谎,但它只说一半

竞品分析最容易死在两条路上:找来的“分析报告”全是二手观点,或者自己试用十分钟凭印象写小作文。前者的结论属于别人,后者的结论属于想象。

界面不一样。界面是竞品现在的样子,需求文档是竞品过去想要的样子,中间隔着无数次改版和妥协,只有界面保留了最终答案。答案里最有价值的往往不是功能本身,是这三个不起眼的角落:

数值边界。 我拆的这个 App,手动录入食物的四个营养值各带校验范围:热量 0-1500 kcal,蛋白、脂肪、碳水各 0-200g 封顶。这些数字是它给自己定的业务规则,比你猜一百遍“它支持不支持自定义食物”都准。做增肌计划评估时还有个体脂率校验 3%-60% 的硬拦截,说明这个字段在它的模型里是必填入参,不是可选项。

空状态文案。 一个页面在“还没有正在执行的计划”的时候说什么、推荐你去哪,暴露的是它认为新用户的下一步动作应该是什么。空状态是产品最诚实的时刻,因为没有数据可演。

提示和免责声明。 AI 估算热量之后跟一句“油、酱汁、份量靠文字补充”,AI 代谢评估报告打上“非医学诊断”。这类句子是竞品法务和产品一起付过学费的结论,你照着抄进自己文档,等于白拿一次交学费的机会。

但界面只说一半:它只展示阳面。教练端的界面、拼团页面的规则,用户侧截图永远拍不到。所以取证纪律的第一条就是承认盲区,并且把盲区记下来。怎么记,下一节。

※ ※ ※

🗂 逐轮取证:清单是长出来的,不是写出来的

我给这次拆解除了一个纪律:每一轮只拍一条完整任务链路,回来当天就归档进同一个 markdown 文件,禁止攒着。攒到第二天,你就忘了当时点过哪个按钮、页面报过什么错。

归档文件的书写约定简单到不需要工具,纯 markdown,三种标记:

## Tab:今日
- 一键记录饮食弹窗:拍照最多2张+一句话描述→AI生成该餐热量估算 ✅
- 云端库检索→称量页:重量滑块实时换算,出口双按钮"先不加入/加入今日饮食" ✅
- 教练端界面:用户侧不可达 ⏳

✅ 已取证,⏳ 待补。就这么两个符号,作用比你想的大:它把“我知道的”和“我不知道但知道我缺的”分开了。到最后这份清单 110 行、54 条功能条目,其中 1 条挂着 ⏳——那是明确承认的盲区,而不是漏了。盲区挂着比删掉值钱:写规格书的人看到 ⏳,知道那里要标“待确认”,而不是想当然。

11 轮取证里真正关键的转折发生在第 3 轮。前两轮我把“添加自定义食物”拍完了就打了 ✅,第三轮重看截图时发现:AI 按名称估算的结果是一个草稿,回显、可修改、必须人工确认才入库;拍成分表也是草稿;连一键记录拍饭菜生成的“该餐总热量”,也要在确认弹窗里核对明细(AI 自动把红油酱料拆成了单独一条)才落账。

三个入口共用同一个“AI 出草稿、人做确认”的组件。这是个模式,不是巧合。怎么确认它是模式而不是巧合?我的办法是回头做反证检索:在清单里搜所有出现过“生成中”加载态的地方,逐个点开看落库前有没有确认步骤。搜出来四处,四处全有。反过来,全产品找不到任何一处 AI 直接写入用户数据的路径。一个否定用例都找不到的规律,才配写进需求表的“全局规则”,而不是散在四个功能条目里各写一遍。开发看到四条独立的“支持 AI 估算”,会做出四个互不相通的弹窗;看到一条“AI 产物一律进草稿确认组件”,才知道要封装复用。

如果我只拍两轮就收工,这个贯穿全产品的核心交互范式就漏了。它恰恰是后面整本需求清单里权重最高的一条。

取证轮次之间不要平均用力。我的节奏是:第一轮拍主干(每个 tab 走一遍),第二到五轮按“哪个页面信息密度最高”下钻,之后的轮次全是补洞,每次写需求时引用不到具体截图,就再出门补一轮。截图先占文件夹名字编号,归档按模块归位。

补一句拍法上的讲究,因为它直接决定归档效率:同一弹窗栈里的每一层单独拍,不要只拍最终结果。“添加食物”的弹窗栈有三层——选分组、填数值、草稿回显——只拍第三层,你就丢掉了“分组是贴标签而不是文件夹”这个交互结论(它是从第二层“选择此分组”的确认式按钮看出来的)。宁可多拍废片,不要漏拍中间态:废片删掉一秒钟,漏掉的中间态要用一整轮补证来还。

※ ※ ※

🔧 把 markdown 清单变成可检查的数据

清单超过 50 条之后,纯文本开始失控:引用不到、统计不了、看不出哪个模块还没拍完。所以我写了第一个小脚本,把清单解析成 JSON 并打印取证覆盖率。全部逻辑四十行以内,没有任何依赖:

#!/usr/bin/env python3
"""把截图取证清单(markdown)转成结构化 JSON,并统计取证覆盖率。
清单书写约定:
  - 顶层条目以 "- " 开头;小节标题以 "## " 开头
  - 行内含 ✅ = 已取证;⏳ = 待补截图
用法:python parse_inventory.py inventory.md"""
import json, re, sys

def parse(path):
    module, items = "", []
    for ln in open(path, encoding="utf-8"):
        ln = ln.rstrip("\n")
        m = re.match(r"^## (.+)", ln)
        if m:
            module = m.group(1).strip(); continue
        if ln.startswith("- "):
            text = ln[2:].strip()
            status = ("done" if "✅" in text else
                      "pending" if "⏳" in text else "raw")
            name, _, desc = text.partition(":")
            items.append({"module": module, "name": name.strip(),
                          "desc": desc.strip(), "status": status})
    return items

if __name__ == "__main__":
    items = parse(sys.argv[1])
    mods = {}
    for it in items:
        mods.setdefault(it["module"] or "(全局)", []).append(it)
    done = sum(1 for it in items if it["status"] == "done")
    print(f"条目 {len(items)} | 模块 {len(mods)} | 已取证 {done}")
    for mod, rows in mods.items():
        flag = "✅" if all(r["status"] != "pending" for r in rows) else "⏳"
        print(f"  {flag} {mod}: {len(rows)} 条")
    out = sys.argv[1].rsplit(".", 1)[0] + ".json"
    json.dump(items, open(out, "w", encoding="utf-8"),
              ensure_ascii=False, indent=1)
    print("写出", out)

在样例清单上实跑,输出长这样:

$ python parse_inventory.py inventory_sample.md
条目 7 | 模块 3 | 已取证 0
  ✅ Tab:我的: 2 条
  ✅ Tab:今日: 3 条
  ✅ Tab:计划: 2 条
写出 inventory_sample.json
取证管道六站:逐轮截图→清单→结构化JSON→需求推导→一致性校验→规格书

值得说一句“已取证 0”:样例条目没打 ✅ 标记,覆盖率统计如实归零。工具比记忆诚实,这正是我要它进管道的原因。真实清单上跑出的是 54 条、7 个模块,一眼能看出哪个 tab 还没拍完。这一步的意义不在 JSON 本身,在于从此“拆到哪了”变成可查询的状态,而不是“我感觉差不多了”。

※ ※ ※

🕵️ 从功能到需求:每个条目追问三个问题

截图清单只是事实库,从事实到需求要靠追问。我对每条高价值条目固定问三个问题,这三问决定了倒推的深度:

它在解决什么问题?“复用记录”入口会提示“今天还没有可复用的饮食记录”。表面是个快捷功能,追问下去是:用户每天吃的东西高度重复,记三餐的最大成本是重复劳动。于是需求不是“加个复制按钮”,而是“饮食记录要支持按餐次模板和往日记录两种复用路径”。

它的实现规则是什么?“添加自定义食物”有三条入口:手动录入(带数值校验范围)、AI 按名称估算、拍成分表识别。三条路殊途同归到一个草稿确认页。规则层的结论是:食物库是用户自有的,AI 是录入加速器,不是替代决策者。

它在拿什么换什么?这是最有产品味的一问。监督小队功能里,队友之间互相可见的是完整当日账本:晨重、热量进度、三大营养素克数连目标值都公开。它拿“数据全裸的社交压力”换“记录坚持率”。减脂工具最大的流失原因就是懒得记,它把记录变成有人盯着的社交义务。倒推出这条,我们自己的产品就必须回答同一个问题:要不要拿隐私换留存?不回答不行,因为竞品已经替你回答了一个版本。

类似的账还有好几笔。增肌计划的模板全部以教练名字命名、公式直接写在页面上(连“初始目标=BMR+40分钟×强度8”这种参数都摊开),它拿公式透明换用户对教练 IP 的信任;注销流程要求二次确认,还把后果量化成“1 份资料、4 份 AI 报告”逐条列出来,拿操作成本换“别让冲动用户流失”的拦截窗口。每一笔交换都是需求文档里“为什么这样设计”的证据。

“它在拿什么换什么”这一问还顺手解决了优先级问题。倒推出来的 54 条不能全是 P0。我给的定级标准就两条:动了这笔交换的条目(比如草稿确认、队内数据全裸)定 P0,因为砍掉它等于换了一套产品哲学;纯执行层便利(kg 和斤切换、日期回补)按实现成本定 P1/P2。这样排出来的优先级不是拍脑袋,每个 P0 背后都站着一笔看得见的商业交换,评审时扛得住质疑。

三问的答案分层写:证据层引用截图条目,规则层写字段、范围、状态流转,推断层写商业动机。必须标注哪层是推断——推断错了是观点问题,证据错了是信任问题,不能混着写。

※ ※ ※

🚧 大模型会编需求,所以要有闸门

清单和分层笔记齐了之后,剩下的扩写交给大模型:让它把 54 条取证条目展开成带编号、带优先级、带验收标准的需求表。这一步提效最狠,坑也最深:它会编。

第一次跑完,需求表里出现了“支持语音报餐记录”,写得像模像样,验收标准都有。我回截图清单搜了两遍:没有任何一条取证支持这个功能存在。它把“AI 拍照估算”顺手脑补成了“AI 语音报餐”,一个不存在的竞品功能就这样进了规格书。如果没人拦,我们下一期就要照着它开工。

所以有了第二个脚本:给每条需求加一个“来源”字段,值必须是取证清单里的条目名,然后机器对账:

#!/usr/bin/env python3
"""需求清单一致性检查:每条需求必须能追溯到取证条目。
用法:python check_consistency.py inventory.json requirements.md"""
import json, re, sys

items = {it["name"] for it in json.load(open(sys.argv[1], encoding="utf-8"))}
bad_ref, orphan_p0 = [], []
for ln in open(sys.argv[2], encoding="utf-8"):
    m = re.match(r"^\|\s*(R\d+)\s*\|.*?来源[::]\s*([^|]+)\|", ln)
    if not m:
        continue
    rid, refs = m.group(1), m.group(2)
    for ref in re.split(r"[、,,]\s*", refs.strip()):
        if ref and ref not in items:
            bad_ref.append((rid, ref))
    if "P0" in ln and not any(r in items for r in
            re.split(r"[、,,]\s*", refs)):
        orphan_p0.append(rid)

print(f"引用不存在的取证条目:{len(bad_ref)} 条")
for rid, ref in bad_ref:
    print("  ", rid, "->", ref)
print("P0 无取证锚点(孤儿需求):", orphan_p0 or "无")
sys.exit(1 if bad_ref or orphan_p0 else 0)

故意塞一条假引用试跑:

$ python check_consistency.py inventory_sample.json requirements_sample.md
引用不存在的取证条目:1 条
   R02 -> 不存在条目
P0 无取证锚点(孤儿需求): 无
exit=1

闸门很简单:需求表每行末尾的“来源”必须能在 JSON 条目名里找到,找不到就非零退出。规则也不公平:P0 必须有取证锚点,P2 允许写“竞品未见,我们想做”的自主需求,但必须显式声明。一句话:让大模型负责扩写,让人和脚本负责判真假。“来源”字段后来成了评审会上最硬的字段,谁质疑某条需求,直接把来源截图甩过去。

※ ※ ※

✅ 交付:规格书长什么样,怎么验收

最终交付物是一本文档四件套:取证清单(原始 markdown+JSON)、需求表(R 编号+优先级+验收标准+来源)、一页功能地图(按 tab 画出页面和跳转关系)、开放问题清单(所有 ⏳ 和推断存疑项汇总)。开发拿需求表排期,产品拿开放问题去补证,谁也不用再问“这个功能到底有没有”。

验收标准就一条:任何一条需求,要么指向一张截图,要么明确写着“自主设计”。 拿不到截图依据又没标自主的,退回重拆。倒推出的需求永远不如原厂的完整,但它是你亲眼见过、亲手验证过的,开会时每个字都有底气。

还有一条不成文的验收:开放问题清单的长度不算丢人。这次交付时“教练端界面长什么样”还挂着 ⏳,加上拼团页、推荐积分页两个未见入口,我在规格书里如实写了“竞品未验证,需自行设计并做用户测试”。承认不知道的部分,恰恰是这份文档比“分析报告”值钱的地方——分析报告什么都敢说,规格书知道每句话的边界在哪。

分层写入:证据层→规则层→推断层,需求表逐行回链截图条目

这套流程里大模型的位置也值得交代。它的边界从头到尾没越权:取证环节不在它手里,截图只能人拍,拍不到的它就是不知道;判断环节也不在它手里,三问的结论得人自己下,它敢替你答“这个功能解决什么问题”,答的一定是概率最高的常见答案,不是这个竞品真实的答案。剩下扩写、归类、格式化这些体力活,全是它的。管道是“人取证→人推导→机器扩写→脚本对账”,两头攥在人手里,中间交给机器。顺序反了,就会得到一份看起来很全、实际上有幽灵功能的需求文档。那种文档的破坏力比没有文档更大。

这篇是 AI 项目实战系列第三篇。上一篇拆定时任务的时候我说过,自动化里最脆的一环往往不在模型;这次拆完竞品更确信一件事:AI 能替你写的东西越多,替你验证的部分就得越硬。

评论区聊聊你拆竞品时翻过什么车,或者想看哪个环节展开写。如果这篇对你有用,点个在看,转给那个还在凭印象写需求文档的同事。

下一篇预告:把一本小说交给 AI 做成短剧,走到第三个分镜的时候,我笑不出来了。那条管道里最反直觉的一课,跟生成质量没有一分钱关系。

※ ※ ※

资料来源

  • ▪ 本次拆解除的截图取证清单(某减脂类小程序,App 名称已打码,账号信息不入文)
  • ▪ parse_inventory.py / check_consistency.py 均为本机实跑,输出原样粘贴(样例文件已脱敏,不含竞品原文)
  • ▪ 文中数值(54 条、11 轮、校验范围 0-1500 kcal 等)取自取证清单实测记录
  • ▪ 生成日期:2026-10-02。文中示例主机与账号均为虚构,非真实环境

评论 (0)