1. 从零到上架:为什么我选择用 WorkBuddy 做剧本快读小程序
去年年底,我脑子里冒出一个想法:做一个能帮人快速读完剧本的小程序。不是那种全文阅读器,而是把剧本拆成角色、场景、关键对白,让用户能在十分钟内抓住一个剧本的核心脉络。想法挺简单,但真动手的时候问题就来了——我并不是一个全职开发者,日常还有别的工作要忙,写代码的时间都是碎片化的。如果按照传统方式从零搭框架、写页面、调接口、做审核,这个项目大概率会烂尾。
后来我试了 WorkBuddy 这个工具,它的定位是帮非专业开发者快速搭建小程序原型并完成上架流程。我花了大概三周左右的业余时间,把“剧本快读”从想法变成了一个真正能在微信里搜到、能正常使用的小程序。这篇文章就是整个过程的完整记录,包括我为什么选这个方案、每一步怎么操作、参数怎么定、踩了哪些坑、最后怎么过的审核。
如果你也是那种“有想法但代码功底一般”的人,或者你正在找一个能快速验证产品思路的工具,这篇内容应该能帮你省下不少试错时间。我会把技术选型的逻辑、核心功能的实现细节、审核被拒的排查过程都摊开来讲,尽量做到你看完就能照着复现。
2. 项目整体设计与技术选型思路
2.1 剧本快读的核心需求拆解
先把这个小程序要解决的问题说清楚。剧本和普通小说不一样,它的结构是高度格式化的:角色名、场景标题、对白、舞台指示,这些元素天然就是分块的。用户想“快读”,本质上需要的是三件事:第一,快速知道这个剧本讲了什么故事;第二,快速了解有哪些角色、谁和谁有对手戏;第三,快速定位到关键情节节点。
所以我的核心功能设计就围绕这三个需求展开:
- 剧本摘要生成:用户上传或粘贴剧本文本后,系统自动提取主要角色和核心冲突,生成一段三百字以内的故事梗概。
- 角色关系图谱:把剧本中同时出现的角色对白进行统计,用简单的可视化方式展示角色之间的互动频率。
- 场景速览卡片:按场景切分剧本,每个场景生成一张卡片,显示场景标题、出场角色、关键对白摘录。
这三个功能都不算复杂,但都需要一定的文本处理能力。如果自己从零写,光是文本解析和界面渲染就够我喝一壶的。
2.2 为什么最终选了 WorkBuddy 而不是自己写
我对比过几种方案。自己写原生小程序代码,灵活性最高,但时间成本太大;用低代码平台,上手快但定制能力有限;WorkBuddy 吸引我的点在于它在两者之间找到了一个平衡——既有可视化的页面搭建,又允许我在关键逻辑处插入自定义的文本处理脚本。
具体来说,WorkBuddy 帮我解决了这几个问题:
- 页面结构和路由:它内置了小程序的标准页面模板,我只需要拖拽组件、绑定数据源,不用手写 WXML 和 WXSS。
- 数据存储和接口:它提供了云数据库和云函数的封装,我不用自己搭服务器,也不用处理复杂的鉴权逻辑。
- 审核发布流程:它有一键提交审核的功能,并且会提前检查一些常见的合规问题,比如类目选择、隐私协议配置。
当然,它也不是万能的。后面我会详细讲哪些地方它搞不定、我是怎么绕过去的。
2.3 技术方案的整体架构
整个小程序的数据流是这样的:
- 用户在首页粘贴剧本文本,或者从文件导入。
- 文本经过预处理模块,去掉多余的空格、换行、页眉页脚。
- 预处理后的文本进入解析模块,按行识别角色名和对白,按场景标题切分场景。
- 解析结果存入云数据库,同时触发摘要生成和角色统计。
- 前端从数据库读取结果,渲染成卡片和列表。
这个架构里,最核心的是解析模块。我试过纯正则表达式,但剧本格式太灵活了,有的用“角色名:对白”,有的用“角色名(情绪):对白”,还有的用缩进来区分。后来我改成“正则+规则引擎”的方式,先用正则做初步切分,再用规则引擎处理特殊情况。这部分后面会详细展开。
3. 核心功能实现与关键细节解析
3.1 剧本文本的预处理与清洗
剧本来源五花八门,有的是从 PDF 复制的,有的是从网页粘贴的,格式乱七八糟。预处理这一步看着简单,但如果不做好,后面的解析准确率会大打折扣。
我总结了几条清洗规则:
- 统一换行符:把
\r\n和\r都替换成\n,避免行分割出错。 - 去除首尾空白:每一行都做
trim(),但要注意保留行首的缩进信息,因为有些剧本用缩进表示对白。 - 过滤空行和分隔线:连续两个以上的空行合并成一个,去掉全是
-或=的行。 - 处理全角半角:把全角冒号、全角括号统一转成半角,方便后续正则匹配。
这里有个坑:有些剧本用中文全角空格做缩进,直接trim()会把缩进也去掉。我的做法是先检测行首是否有全角空格,如果有,先替换成特殊标记,等解析完再还原。
// 预处理核心逻辑示例 function preprocessScript(rawText) { let text = rawText.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); let lines = text.split('\n'); let processed = lines.map(line => { // 保留行首缩进标记 let indentMatch = line.match(/^[\u3000\s]+/); let indent = indentMatch ? indentMatch[0].length : 0; let content = line.trim(); // 全角转半角 content = content.replace(/:/g, ':').replace(/(/g, '(').replace(/)/g, ')'); return { indent, content }; }); // 过滤空行和分隔线 processed = processed.filter(item => item.content !== '' && !/^[-=]{3,}$/.test(item.content) ); return processed; }这段代码看起来简单,但实际跑下来,清洗后的解析准确率比不清洗高了至少三成。
3.2 角色对白的识别与场景切分
这是整个项目最核心也最头疼的部分。剧本的格式千变万化,我不可能穷举所有情况,只能抓主要矛盾。
我的识别策略是分两步走:
第一步,识别场景标题。场景标题通常有固定格式,比如“第一场”、“场景一”、“INT. 客厅 - 日”或者“1. 咖啡厅 白天”。我用一组正则去匹配这些模式,匹配上的行就作为场景分割点。
第二步,识别角色对白。角色对白最明显的特征是“角色名: 对白内容”这种结构。但角色名本身也有讲究:有的剧本角色名是中文,有的是英文,有的带括号注释。我的做法是先用正则提取冒号前面的内容,然后判断它是否符合角色名的特征——长度不超过十个字、不包含标点符号、在剧本中重复出现多次。
// 角色对白识别核心逻辑 function parseDialogue(lines) { const dialogues = []; const roleCount = {}; lines.forEach((line, index) => { // 匹配 "角色名: 对白" 或 "角色名(情绪): 对白" const match = line.content.match(/^([^::]{1,10})(?:\([^)]*\))?[::]\s*(.+)$/); if (match) { const roleName = match[1].trim(); const content = match[2].trim(); // 过滤掉不是角色名的情况 if (isValidRoleName(roleName)) { dialogues.push({ role: roleName, content, lineIndex: index }); roleCount[roleName] = (roleCount[roleName] || 0) + 1; } } }); return { dialogues, roleCount }; } function isValidRoleName(name) { // 排除常见非角色词 const excludeWords = ['注', '旁白', '说明', '提示', '时间', '地点']; if (excludeWords.includes(name)) return false; // 排除包含数字或特殊符号的 if (/[\d@#$%^&*]/.test(name)) return false; return true; }这里有个经验:不要试图一次性做到完美识别。我一开始追求百分之百准确,结果规则越写越复杂,反而容易误判。后来我改成“宁可漏掉,不要错判”,把置信度低的直接跳过,让用户手动补充。这样整体体验反而更好。
3.3 摘要生成与角色关系统计的实现
摘要生成我没有用复杂的 NLP 模型,原因有两个:一是小程序端跑不动大模型,二是调用外部 API 有延迟和成本问题。我的方案是基于规则的抽取式摘要。
具体做法是:从每个场景中提取关键对白,按角色出现频率和对白长度打分,选出得分最高的几句话,拼成一段摘要。虽然不如生成式摘要流畅,但胜在速度快、可控性强。
角色关系统计更直接:统计每两个角色在同一场景中同时出现的次数,次数越多说明互动越频繁。然后用一个简单的矩阵展示出来。
| 角色A | 角色B | 同场次数 |
|---|---|---|
| 林晓 | 陈默 | 12 |
| 林晓 | 苏晴 | 8 |
| 陈默 | 苏晴 | 5 |
这个表格在前端渲染成热力图或者简单的条形图,用户一眼就能看出谁和谁的对手戏最多。
3.4 前端页面搭建与交互细节
WorkBuddy 的页面搭建是拖拽式的,但我发现几个细节需要特别注意:
- 数据绑定要提前规划:每个组件绑定哪个数据字段,最好在搭建之前就列清楚,不然后面改起来很麻烦。
- 列表渲染的性能:剧本场景多的时候,列表会很长。我用的是分页加载,每次只渲染十个场景卡片。
- 加载状态和空状态:用户粘贴文本后,解析需要时间,一定要有 loading 提示。如果解析失败,也要有友好的空状态页面,引导用户检查文本格式。
提示:WorkBuddy 的预览功能很方便,但真机调试和预览有时候会有差异,尤其是字体和间距。建议在提交审核前,至少在两种不同尺寸的手机上真机跑一遍。
4. 实操过程与核心环节实现
4.1 项目初始化与页面结构规划
在 WorkBuddy 里新建项目后,我先把页面结构定下来。整个小程序一共四个页面:
- 首页:输入剧本文本的入口,支持粘贴和文件导入。
- 解析结果页:显示摘要、角色列表和场景卡片。
- 角色详情页:点击某个角色后,显示该角色的所有对白和互动关系。
- 关于页:简单的使用说明和反馈入口。
页面之间的跳转关系用 WorkBuddy 的路由配置搞定,不需要手写navigateTo。但要注意,传递参数的时候,如果数据量大,不要直接放在 URL 里,而是存到全局状态或者云数据库,用 ID 来传递。
4.2 云数据库设计与数据读写
云数据库我建了两张表:
- scripts 表:存剧本的基本信息,包括标题、原始文本、解析状态、创建时间。
- scenes 表:存场景数据,包括所属剧本 ID、场景标题、出场角色、关键对白。
读写逻辑是这样的:用户提交文本后,先在 scripts 表插入一条记录,状态设为“解析中”。然后云函数异步执行解析,解析完成后更新 scripts 表的状态,并把场景数据批量写入 scenes 表。前端通过监听 scripts 表的状态变化来决定什么时候刷新页面。
// 云函数:解析剧本并写入数据库 const cloud = require('workbuddy-cloud'); const db = cloud.database(); exports.main = async (event) => { const { scriptId, rawText } = event; // 预处理 const lines = preprocessScript(rawText); // 解析场景和对白 const scenes = parseScenes(lines); const { dialogues, roleCount } = parseDialogue(lines); // 生成摘要 const summary = generateSummary(scenes, dialogues); // 批量写入场景数据 const batchSize = 20; for (let i = 0; i < scenes.length; i += batchSize) { const batch = scenes.slice(i, i + batchSize); await Promise.all(batch.map(scene => db.collection('scenes').add({ data: { scriptId, ...scene, createTime: db.serverDate() } }) )); } // 更新剧本状态 await db.collection('scripts').doc(scriptId).update({ data: { status: 'completed', summary, roleCount, updateTime: db.serverDate() } }); return { success: true }; };这里有个性能上的坑:批量写入的时候不要一次性写太多。我一开始把整个剧本的所有场景一次性Promise.all,结果云函数超时了。后来改成每批二十条,分批次写入,就稳定了。
4.3 解析模块的调试与优化过程
解析模块是整个项目里我花时间最多的部分。第一版跑下来,准确率大概只有六成,主要问题是角色名识别错误和场景切分不准。
我用了三个方法来优化:
方法一:增加样本测试。我找了十几个不同格式的剧本片段,每个都手动标注了正确的角色和场景,然后跑解析脚本,对比结果。这样能快速定位是哪类格式出了问题。
方法二:引入置信度机制。对于角色名识别,我设置了一个置信度分数。如果角色名在剧本中出现次数少于三次,置信度降低;如果角色名包含特殊字符,置信度降低。置信度低于阈值的,不自动识别,而是标记为“待确认”,让用户手动选择。
方法三:允许用户手动修正。解析结果页里,每个角色名旁边都有一个编辑按钮,用户可以修改识别错误的角色名,修改后系统会重新统计。这个功能虽然简单,但大大提升了用户的容忍度。
| 优化阶段 | 准确率 | 主要问题 | 解决方式 |
|---|---|---|---|
| 第一版 | 约60% | 角色名误判多 | 增加排除词列表 |
| 第二版 | 约75% | 场景切分不准 | 优化场景标题正则 |
| 第三版 | 约88% | 特殊格式漏判 | 引入置信度+手动修正 |
| 最终版 | 约92% | 极少数异常格式 | 用户反馈持续优化 |
4.4 提交审核与上架流程实录
开发完成后,就是提交审核。WorkBuddy 提供了一键提交的功能,但有几个地方需要提前准备好:
- 小程序类目:我选的是“工具-效率”类目,这个类目对个人开发者比较友好,不需要额外的资质。
- 隐私协议:因为涉及用户上传文本,必须配置隐私协议,说明数据用途和存储方式。
- 截图和描述:审核时需要提供小程序截图和功能描述,描述要简洁明了,不要堆砌关键词。
我第一次提交被拒了,原因是“功能描述不清晰,无法判断具体用途”。后来我把描述改成“帮助用户快速提取剧本中的角色、场景和关键对白,生成摘要卡片”,第二次就过了。
注意:审核期间不要频繁修改代码,否则可能导致审核版本和线上版本不一致。建议审核通过后再进行后续迭代。
5. 常见问题与排查技巧实录
5.1 解析准确率低的排查思路
如果你也做类似文本解析的功能,遇到准确率低的问题,可以按这个顺序排查:
- 检查预处理是否到位:换行符、全角半角、缩进标记,这些基础问题占了解析错误的很大比例。
- 检查正则是否过于宽松或严格:正则太宽松会误判,太严格会漏判。建议先用小样本测试,逐步调整。
- 检查是否有特殊格式未覆盖:比如有的剧本用“【角色名】”而不是“角色名:”,这种就需要额外加规则。
- 检查是否有噪声数据:页眉页脚、页码、注释行,这些都会干扰解析,需要在预处理阶段过滤掉。
5.2 云函数超时与性能问题
云函数超时是我遇到的第二个大坑。默认的超时时间是三秒,但解析一个长剧本可能需要五秒以上。解决方案有两个:
- 增加超时时间:WorkBuddy 的云函数支持配置超时时间,我调到了二十秒。
- 拆分任务:把解析拆成多个步骤,每个步骤一个云函数,用消息队列串联。不过这个方案比较复杂,我最后没用,因为调超时时间就够了。
另外,数据库读写也要注意。批量写入的时候,每批不要超过二十条,否则容易触发限流。
5.3 审核被拒的常见原因与应对
除了前面说的功能描述问题,我还整理了几个常见的审核被拒原因:
| 被拒原因 | 应对方式 |
|---|---|
| 类目选择错误 | 根据实际功能选择最匹配的类目,不确定就选“工具” |
| 隐私协议缺失 | 在后台配置隐私协议,说明数据收集和使用范围 |
| 功能不完整 | 确保所有页面都能正常打开,没有空白页或报错 |
| 诱导分享 | 不要在小程序内强制用户分享,分享按钮要用户主动触发 |
| 内容违规 | 用户上传的文本要有过滤机制,避免出现违规内容 |
5.4 用户反馈中的典型问题与处理
上线后收到了一些用户反馈,比较典型的有:
- “粘贴长文本后卡顿”:原因是前端一次性渲染了太多场景卡片。改成虚拟列表或者分页加载后解决了。
- “角色名识别错了”:增加了手动修正功能,用户可以直接在结果页修改。
- “摘要不够准确”:这是抽取式摘要的固有局限,我在关于页里说明了摘要仅供参考,建议用户结合场景卡片一起看。
6. 个人实操心得与后续扩展思路
这个项目做下来,我最大的体会是:不要追求一步到位,先跑通再优化。我一开始想把解析做得特别完美,结果卡在细节上很久。后来我改变策略,先做一个能用的版本,上线后根据反馈迭代,反而效率更高。
另外,WorkBuddy 这类工具确实能帮非专业开发者快速验证想法,但它也有边界。复杂的文本处理、高性能的并发请求,这些还是需要一定的编程基础。我的建议是,把工具当成加速器,而不是替代品。核心逻辑该自己写的还是要自己写,工具只是帮你省去那些重复的、模板化的部分。
后续我打算加两个功能:一是支持多人协作标注剧本,二是增加剧本对比功能,让用户能同时看两个剧本的异同。这两个功能都需要更复杂的数据结构和交互设计,我还在摸索中。如果你也在做类似的东西,欢迎交流踩坑经验。