102400423刘传浩
软件工程实践第二次作业——与AI结对编程(顶会热词统计)
| 这个作业属于哪个课程 | 2601_FZU_SE · 软件工程实践 |
|---|---|
| 这个作业要求在哪里 | 软件工程实践第二次作业——与AI结对编程(顶会热词统计) |
| 这个作业的目标 | 与 AI 结对完成需求分析(NABCD)、原型设计、编程实现与云端部署,并完整记录人机协作过程;掌握 Git 分支与 Release 协作 |
| 其他参考文献 | 《构建之法(第四版)》第3、4、8章;DBLP 公开数据;OpenAlex API;ECharts 文档 |
目录
- 一、作业描述
- 二、Git 仓库链接、代码规范链接与 AI 工具说明
- 三、PSP 表格
- 四、NABCD 需求分析
- 五、原型设计与原型链接
- 六、设计实现过程
- 七、成品展示
- 八、结对(人机)讨论过程描述
- 九、代码说明
- 十、心路历程、收获
- 十一、对 AI 结对伙伴的评价
一、作业描述
小刚是一个电影迷,因为一部电影里的机器人能快速分类视野中的物体,对计算机视觉产生了兴趣。但 CVPR、ICCV、ECCV 三大顶会论文数量极其庞大——据会议官网,仅 CVPR 2025 就有 13008 篇投稿、2878 篇被录取——靠人工逐篇阅读来总结研究热点几乎不可行。
本次作业要求与 AI 结对,设计并实现一个"顶会热词统计平台",至少包含 5 项基础功能:论文信息爬取、论文列表管理、热门方向分析、关键词图谱、热度走势对比(动图),并部署到华为云服务器。
我做的平台:一个纯前端(HTML + CSS + 原生 JavaScript + ECharts)的顶会热词统计平台,数据持久化在浏览器 localStorage,无需后端即可运行与部署。
二、Git 仓库链接、代码规范链接与 AI 工具说明
2.1 仓库与规范链接
| 项目 | 链接 |
|---|---|
| CodeArts 仓库(学号命名) | https://devcloud.cn-north-4.huaweicloud.com/codehub/project/19c4f4b120bc43f7a93cda0c3fc8e4d2/codehub/3087051/repo |
| 代码规范 | 仓库内 codestyle.md |
| 部署访问地址 | |
| 原型发布链接 |
2.2 AI 编程助手说明
| 工具 | 版本 / 模型 | 主要用途 |
|---|---|---|
| DeepSeek | deepseek-v4-flash | 需求分析、信息架构设计、功能模块编码、代码审查与 Bug 排查、文档撰写 |
本次结对全程使用 DeepSeek 作为 AI 编程助手,协作方式为:我提出需求 → AI 产出方案或代码 → 我审查、实测、修改 → 再反馈给 AI 迭代。
2.3 可解释性声明
- 项目中由 AI 生成初稿的部分:分层架构建议、
analytics.js统计函数、charts.js的 ECharts 配置、crawler.js初稿、style.css视觉样式。 - 由我编写或修改的部分:内置示例数据整理、页面交互与字段设计、统计口径定义、以及下表中三个关键缺陷的修复。
- 我能够解释项目中全部代码的设计思路,包括 AI 生成的部分。未直接提交任何未经理解的代码。
三、PSP 表格
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 35 |
| • Estimate | • 估计这个任务需要多少时间 | 30 | 35 |
| Development | 开发 | 1290 | 1580 |
| • Analysis | • 需求分析(包括学习新技术) | 120 | 150 |
| • Design Spec | • 生成设计文档 | 60 | 70 |
| • Design Review | • 设计复审 | 30 | 40 |
| • Coding Standard | • 代码规范 | 40 | 45 |
| • Design | • 具体设计 | 120 | 140 |
| • Coding | • 具体编码 | 600 | 720 |
| • Code Review | • 代码复审 | 120 | 180 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 200 | 235 |
| Reporting | 报告 | 240 | 300 |
| • Test Report | • 测试报告 | 60 | 70 |
| • Size Measurement | • 计算工作量 | 30 | 25 |
| • Postmortem & Process Improvement Plan | • 事后总结,并提出过程改进计划 | 150 | 205 |
| 合计 | 1560 | 1915 |
预估与实际偏差分析:
- 代码复审 +100%、测试 +17.5% —— 最大偏差来源。我原以为"AI 写的代码能跑就行",但实际上 AI 生成的爬虫初稿在浏览器里根本拿不到数据(详见第八节案例 C1),需要联网实测每个数据源才发现问题,这部分时间完全没预估到。教训:AI 产出的代码必须"实测验证",而不是"读懂逻辑"就算通过。
- 事后总结 +37% —— 排查过程中发现的三个缺陷都很有代表性,值得认真记录,因而超出了预估。
- **编码 -0%**(基本符合)—— AI 在"写出来"这件事上确实高效,节省的时间主要在这里。
四、NABCD 需求分析
按照《构建之法》第 8 章的 NABCD 模型,从需求(Need)、做法(Approach)、好处(Benefit)、竞争(Competitors)、推广(Delivery) 五个维度分析。
4.1 N(Need,需求)
用户:以"小刚"为代表的计算机视觉方向初学者 / 低年级研究生 / 想快速了解领域现状的开发者。
痛点(我先让 AI 从不同角色视角列举,再人工筛选):
| 痛点 | 说明 | 我的判断 |
|---|---|---|
| 论文量太大 | CVPR 2025 投稿 13008 篇,人工逐篇读不可行 | ✅ 核心痛点 |
| 不知道从哪看起 | 没有"热门方向"的量化参考,容易在冷门方向浪费时间 | ✅ 核心痛点 |
| 检索工具不面向"趋势" | Google Scholar / DBLP 面向"找某篇论文",不面向"看领域趋势" | ✅ 核心痛点 |
| 想知道方向是升温还是降温 | 只看某年的榜单无法判断趋势 | ✅ 我补充的痛点 |
| 想快速跳到原文 | 需要摘要 + 原文链接一站式获取 | ✅ 基础需求 |
我的判断与取舍:AI 一开始还列了"论文笔记管理""引用关系分析"等需求。我拒绝了——理由是这些需求偏离了作业"热词统计"的核心,且实现成本高、与"帮助小刚了解热点"这一目标关联弱。需求分析要做减法。
4.2 A(Approach,做法)——重点
整体做法:构建一个"采集 → 管理 → 统计 → 可视化"的流水线。
- 采集:多源降级链爬取论文题录(OpenAlex → CrossRef → DBLP → 模拟数据兜底),自动抽取并归一化关键词。
- 管理:论文列表持久化于浏览器 localStorage,支持增删改查、精确/模糊查询,缺失时可即时联网补爬。
- 统计:以"关键词文档频次"为热度口径,统计 Top10 方向、年份增长率、关键词共现关系。
- 可视化:ECharts 力导向共现图谱 +
timeline时间轴动图,把统计结果变成可交互、可播放的图表。
关键技术决策:
| 决策 | 选择 | 理由 |
|---|---|---|
| 前端还是全栈 | 纯前端 | 作业明确允许;无需服务器与数据库依赖,部署成本最低,平台兼容性最好 |
| 持久化方案 | localStorage | 作业允许;刷新不丢数据,满足"论文列表管理"需求 |
| 关键词口径 | 文档频次(同篇论文内去重) | 简单、可解释、可比较;避免长摘要论文因篇幅长而被高估 |
| 关键词归一化 | 领域词典映射到 canonical | "monocular depth"与"depth estimation"必须合并,否则热点被稀释、趋势不可比 |
| 数据源 | 多源降级链 | 单一数据源不可靠(实测 DBLP 已无法在浏览器调用),降级保证功能始终可用 |
4.3 B(Benefit,好处)
- 对用户:三步即可掌握领域热点——打开首页看 Top10 方向、点关键词看相关论文、播放动图看趋势演变。
- 对平台:零后端依赖,双击
index.html即可运行,部署到任意静态托管即可上线。 - 数据可信:每条论文都带
source标签(OpenAlex / CrossRef / DBLP / 内置 / 模拟),绝不把模拟数据伪装成真实爬取结果。
4.4 C(Competitors,竞争)——重点
| 竞品 | 优势 | 劣势(本平台的切入点) |
|---|---|---|
| Google Scholar | 数据全、引用统计强 | 面向"找具体论文",不提供热词排行与趋势动图;国内访问不稳定 |
| DBLP | 权威题录、有公开 API | 只给题录,不给摘要与关键词;无任何可视化;且已启用反爬,浏览器端难以直接调用 |
| Connected Papers | 引用关系图谱直观 | 图谱是论文关系而非关键词热词;无中文;无趋势对比 |
| CNKI / 万方 | 中文友好 | 面向中文文献,不覆盖 CVPR/ICCV/ECCV 三大顶会;需付费 |
| 会议官网 | 数据最权威 | 只有论文列表,无任何聚合分析 |
本平台的差异化优势:
- 面向"趋势"而非"检索" —— 直接回答"这几年什么方向在升温",这是上述竞品都不做的。
- 热词动图 + 共现图谱 —— 把抽象的统计结果变成直观、可播放的可视化,门槛低。
- 零门槛运行 —— 不需要账号、不需要付费、不需要联网(内置数据),双击即用。
诚实地说:本平台的数据量与权威性远不如 Google Scholar(45 篇示例数据 vs 数万篇真实论文)。它的价值在于验证"热词统计"这一产品形态,而不是替代专业检索工具。
4.5 D(Delivery,推广)
- 目标渠道:班级社区、课程群、CSDN 博文。
- 推广策略:以"作品即广告"——把热度走势动图(GIF)作为推广素材,动图本身直观、易传播。
- 后续:扩充真实论文数据量、支持更多会议(ICML/NeurIPS)、增加按作者/机构的分析维度。
五、原型设计与原型链接
5.1 原型工具
使用 墨刀(Modao) 设计平台全部页面原型。
原型发布链接:【待填:墨刀分享链接】
5.2 与 AI 协作设计的过程
- 我先让 AI 从信息架构角度给出页面清单建议,AI 给出 6 个页面;
- 我结合作业要求做了删减与合并——作业要求至少包含"首页/热门方向总览、热度走势对比、论文列表管理、论文爬取/导入、详情或关于"5 类页面,我把 AI 建议的"详情页"合并进列表页的弹窗中,最终定为 5 个页面;
- 配色上,AI 建议了深色科技风,我改为浅色 + 蓝色主色——理由是平台面向学术数据展示,浅色背景更能凸显图表、长时间阅读不易疲劳。
5.3 页面与交互逻辑
| 页面 | 核心内容 | 交互逻辑 |
|---|---|---|
| 首页/热门方向总览 | Top10 热门方向柱状图 + 关键词共现图谱 + 统计卡 + 会议/年份分布 | 点击柱子或图谱节点 → 弹出相关论文列表;顶部按钮跳转爬取页/走势页 |
| 论文爬取/导入 | 单个标题输入框 + 批量文本框 + 文件导入 + 结果日志 | 提交后逐条爬取,实时输出日志与来源标签 |
| 论文列表管理 | 查询工具栏 + 论文表格 | 模糊/精确查询联动;无结果时显示"联网爬取"提示条;查看/编辑/删除弹出模态框 |
| 热度走势对比 | timeline 动图 + 单热词折线 + 统计口径说明 | 时间轴自动播放;切换关键词下拉 → 折线图重绘 |
| 了解更多 | 三大顶会背景 + 数据来源 + 统计口径 | 静态内容页 |
5.4 原型截图
【截图占位:墨刀原型 5 个页面截图,建议并排 2×3 展示】
5.5 页面间数据流转关系
内置示例数据 ──┐
├──> localStorage(论文列表)──> 统计分析层 ──> 图表层(首页/走势页)
爬取/导入 ─────┘ ↑
│
增删改查(列表页)──> 未命中时触发联网爬取 ──┘
关键点:所有页面共享同一份 localStorage 数据源,因此在爬取页新增论文后,首页 Top10 与走势动图会自动反映新数据——这条数据流转关系是我在原型阶段就必须理清的,否则会出现"爬了论文但统计没变"的体验断裂。
六、设计实现过程
6.1 功能结构图
顶会热词统计平台
├── 功能1:论文信息爬取
│ ├── 单个题目爬取
│ ├── 批量导入(换行/逗号/分号/顿号)
│ ├── txt 文件导入
│ └── 多源降级链(OpenAlex → CrossRef → DBLP → 模拟)
├── 功能2:论文列表管理
│ ├── 新增 / 删除 / 修改
│ ├── 精确查询(题目完全匹配)
│ ├── 模糊查询(编号/关键词/作者/会议/年份/摘要)
│ └── 未命中时联网爬取并入库
├── 功能3:热门方向分析
│ ├── Top10 热门方向(文档频次)
│ ├── 年份增长率
│ └── 涉及会议标注
├── 功能4:关键词图谱
│ ├── 关键词共现网络(力导向)
│ └── 点击关键词 → 相关论文列表
├── 功能5:热度走势对比动图
│ ├── timeline 时间轴自动播放(多年间 × 三大顶会)
│ └── 单热词年度折线走势
└── 扩展:了解更多(顶会背景 / 统计口径 / 数据来源)
6.2 系统分层设计
应用层 app.js 路由、交互、渲染、模态框
↓
可视化层 charts.js ECharts 五种图表封装
↓
统计层 analytics.js Top10 / 共现 / 热度走势 / 增长率
↓
爬取层 crawler.js 多源降级链 + 关键词抽取与清洗
↓
存储层 store.js localStorage 增删改查 + 精确/模糊查询
↓
数据层 data.js 内置示例论文数据(44 篇)
分层的意义:上层依赖下层,各层职责单一。例如要把数据源从 OpenAlex 换成会议官网,只需改 crawler.js,其余各层不受影响。
6.3 遇到的主要问题与解决方式
| # | 问题 | 定位过程 | 解决方式 |
|---|---|---|---|
| 1 | 爬取功能形同虚设:AI 初稿调用 DBLP API,但浏览器里永远拿不到数据,全部静默降级为模拟数据 | 联网实测发现 DBLP 返回 HTML 验证页而非 JSON | 改为多源降级链,主源换为支持 CORS 的 OpenAlex |
| 2 | 批量导入中文标点失效:粘贴中文列表时整段被当成一个标题 | 单元测试覆盖多种分隔符时发现 | 修正分隔符正则,补齐 ;、、 |
| 3 | 热词统计被噪声污染:while 等停用词、OpenAlex 的 Cartography/Shot (pellet) 等无关词进入 Top10 | 打印爬取结果的关键词时肉眼发现明显不合理的词 | 补全停用词表 + 新增三层关键词校验 |
| 4 | 切换页面时动图重新开始播放 | 交互走查发现 hash 变化触发重复渲染 | hashchange 增加"页面已激活则跳过"判断 |
七、成品展示
【截图/动图占位:至少 10 张,建议按下列顺序,每张配一句文字说明】
- 首页总览:统计卡(论文总数/覆盖会议/年份跨度/关键词总数)+ Top10 热门方向柱状图
- 关键词图谱:力导向共现网络,节点大小代表论文数
- 点击关键词下钻:弹出该关键词的相关论文列表(功能4 交互闭环)
- 会议分布 / 年份分布:环形图与柱状图
- 单个论文爬取:输入 "Segment Anything" 后的爬取结果与来源标签
- 批量导入:5 行标题批量爬取的结果日志
- txt 文件导入:导入论文列表文件
- 论文列表管理:增删改查表格全貌
- 模糊查询:输入关键词命中多条论文
- 精确查询未命中 + 联网爬取提示条(功能2 关键交互)
- 手动新增 / 编辑论文:模态框表单
- 热度走势动图(GIF):timeline 自动播放的年份演变 ← 重点,建议录屏转 GIF
- 单热词年度折线:某热词在三大顶会的走势对比
- 了解更多页:三大顶会背景介绍
八、结对(人机)讨论过程描述
8.0 协作方式总述
本次是人机结对而非人人结对。我的协作模式是:
我提需求 → AI 给方案/代码 → 我审查逻辑 → 我实测验证 → 发现问题 → 带着具体现象反馈给 AI → 迭代
↑
这一步是传统结对里"驾驶员/领航员"互查的等价物
关键体会:AI 在"写出看起来对的代码"上极强,但在"这段代码在真实环境里能不能跑通"上几乎不主动验证——而这恰恰是结对编程中"领航员"该干的事。所以我把人类角色定位在验证者与质疑者,而不是打字员。
8.1 案例 A:用 AI 辅助需求分析与原型设计
提示词(Prompt):
我要做一个"CVPR/ICCV/ECCV 顶会热词统计平台",
用户是刚接触计算机视觉的学生,需求是快速了解研究热点。
请从「初学者 / 研究生 / 开发者」三个不同角色视角,
分别列出他们可能的使用场景和痛点,并给出竞品分析要点。
AI 输出摘要:AI 从三个角色给出了 12 条痛点,并列出 Google Scholar、DBLP、Connected Papers 等竞品。但它额外建议了"论文笔记管理""引用关系分析""团队协作"三个功能。
我的采纳与拒绝:
| AI 建议 | 我的决定 | 理由 |
|---|---|---|
| 多角色痛点列举 | ✅ 采纳 | 视角全面,帮我发现了"想知道方向是升温还是降温"这个我自己没想到的痛点 |
| 竞品分析框架 | ✅ 采纳并扩写 | 我补充了 CNKI/万方、会议官网两个本土竞品 |
| 论文笔记管理 | ❌ 拒绝 | 偏离"热词统计"核心,且与目标用户痛点无关 |
| 引用关系分析 | ❌ 拒绝 | 实现成本高(需引用数据),作业范围内收益低 |
| 团队协作 | ❌ 拒绝 | 单人课程作业场景不成立 |
| 深色科技风配色 | ⚠️ 修改后采纳 | 改为浅色 + 蓝色主色——学术数据展示需要凸显图表,深色背景长时间阅读易疲劳 |
这次协作让我明白:AI 倾向于"多给",而人类的价值在于"敢砍"。需求分析的核心能力是做减法。
【截图占位:案例 A 的 Prompt 与 AI 回复截图】
8.2 案例 B:用 AI 辅助编码实现关键词图谱模块
提示词(Prompt):
功能4 需要"关键词图谱,点击某个关键词可展现相关论文"。
我的数据是每篇论文带 keywords 数组。
请用 ECharts 实现一个力导向图,节点是关键词、大小按词频,
连线表示两个关键词出现在同一篇论文里,点击节点回调关键词。
AI 输出摘要:AI 给出了 ECharts graph + layout: 'force' 的完整配置,包括 symbolSize 计算、force.repulsion 参数、click 事件回调。
我做的修改:
- AI 给的节点尺寸是线性映射(
value * 3),导致高频词节点过大、低频词几乎看不见。我改为14 + Math.sqrt(value) * 9,用开方压缩让节点大小差异更柔和、层次更清晰。 - AI 没有处理"关键词对"的去重——A-B 和 B-A 会被算成两条边。我补了排序后拼接 key 的去重逻辑:
const key = ks[i] < ks[j] ? ks[i] + "||" + ks[j] : ks[j] + "||" + ks[i];
edgeMap[key] = (edgeMap[key] || 0) + 1;
- AI 的
tooltip对边和节点用了同一个 formatter,导致悬停连线时显示undefined。我改为按p.dataType分支处理。
【截图占位:案例 B 的 Prompt、AI 生成代码、我的修改对比截图】
8.3 案例 C:用 AI 辅助调试与修复 Bug —— 发现 AI 代码的真实错误
这个案例最能说明"人机结对"里人类审查的价值。
C1(最重要):AI 写的爬虫在真实浏览器里根本拿不到数据
现象:功能1 代码逻辑读起来完全正确——调用 DBLP API、resp.json() 解析、取 hits.hit[0].info。但实测时**每条爬取都降级成了"模拟数据"**。
我的排查过程:
- 先用命令行直接请求 DBLP 接口,发现返回的是
text/html,内容是Making sure you're not a bot!; - 意识到 DBLP 已启用 Anubis 反爬虫工作量证明(PoW)挑战,直接请求只能拿到验证页;
- 进一步检查响应头,发现 没有
Access-Control-Allow-Origin—— 即使解决了反爬,浏览器端fetch也无法跨域读取。
我把这两个事实反馈给 AI:
反馈提示词:
你写的 DBLP 爬取在浏览器里拿不到数据。我实测:
1) GET https://dblp.org/search/publ/api?q=...&format=json 返回的是
text/html,内容是 "Making sure you're not a bot!"(Anubis 反爬挑战)
2) 该响应没有 Access-Control-Allow-Origin 头
请分析这对纯前端方案意味着什么,并给出替代数据源建议。
AI 输出摘要:AI 承认初稿假设了"DBLP API 可直接调用"这一前提,而该前提已不成立;并建议评估 OpenAlex、CrossRef、Semantic Scholar。
我的处理:我没有直接采信 AI 的建议,而是亲自对四个数据源做了联网实测:
| 数据源 | CORS | 提供摘要 | 实测结论 |
|---|---|---|---|
| OpenAlex | ✅ | ✅ | 选为主源 |
| CrossRef | ✅ | ❌ | 备用源 |
| DBLP | ❌ | ❌ | 仅兜底 |
| Semantic Scholar | — | ✅ | 无 Key 时高频返回 429,不采用 |
最终实现四级降级链,并实测验证拿到真实数据:爬取 CLIP 论文成功返回 1202 字符的真实摘要与关键词 vision-language | representation learning | zero-shot learning。
这个案例的意义:AI 的代码"语法正确、逻辑自洽、注释完整",但前提假设是错的(认为第三方 API 永久可用且支持跨域)。如果不实测,这个 Bug 会一直隐藏到演示时才暴露。**"读懂 AI 的代码"不等于"验证了 AI 的代码"。**
【截图占位:AI 初稿代码、我的实测证据(命令行返回 HTML)、修改后的降级链代码】
C2:批量导入不识别中文分号
现象:编写测试用例覆盖多种分隔符时,发现输入 Paper B,Paper C;Paper D 只解析出 2 条而不是 3 条。
原因:AI 写的正则只考虑了英文标点 /[\r?\n|,|;]/,漏掉了中文输入法下的全角 ; 和 、。
修复:
.split(/\r?\n|[,,;;、]/)
反思:这是典型的"AI 按理想化英文场景写代码"。中文用户实际粘贴的列表几乎必然带全角标点,这类问题只有站在真实用户角度测试才会发现。
C3:热词统计被噪声污染
现象:爬取 ViT 论文后,输出的关键词是 vision transformer | transformer | while | image (mathematics) | cartography | electrical engineering | geography。
问题:while 是漏网的停用词;后面几个是 OpenAlex 自动生成的通用概念关键词,与论文主题无关。它们会进入 Top10 和走势统计,直接污染功能3、功能5 的结果。
我的修复(三层过滤):
- 补全停用词表(原先漏了 while / when / which / than 等);
- 数据源关键词校验:含括号的丢弃(多为消歧义概念)、命中通用词黑名单的丢弃、未真实出现在标题/摘要中的丢弃、仅出现一次的单词丢弃;
- 高频词兜底仅在词典与数据源都无结果时启用。
修复后:ViT 的关键词变为干净的 vision transformer | transformer。
【截图占位:案例 C 的 Prompt、报错/异常输出、修复前后对比截图】
九、代码说明
以下摘取项目关键代码并解释设计思路(约 300 行)。项目分层:data → store → crawler → analytics → charts → app。
9.1 存储层:精确查询与模糊查询的设计(store.js)
/**
* 查询:支持精确 / 模糊
* - 精确查询:title 完全等于 query(忽略首尾空格、大小写不敏感)
* - 模糊查询:在标题、关键词、编号、作者、摘要中做子串匹配
*/
find(query, exact) {
const papers = this.getAll();
const q = (query || "").trim().toLowerCase();
if (!q) return papers;
if (exact) {
return papers.filter((p) => p.title.trim().toLowerCase() === q);
}
return papers.filter((p) => {
// 把一条论文的所有可检索字段拼成一个"大字符串",再做子串匹配。
// 这样用户输入编号/关键词/作者/年份都能命中,而无需为每种字段写一套逻辑。
const hay = [
p.title, p.id, p.conference, String(p.year),
p.abstract || "", (p.keywords || []).join(" "), (p.authors || []).join(" ")
].join(" ").toLowerCase();
return hay.indexOf(q) !== -1;
});
}
思路:作业要求"输入论文题目精确查询,也支持模糊查询(输入论文编号、关键词等基本信息)"。精确查询用全等比较;模糊查询我采用字段拼装 + 子串匹配,好处是新增检索维度(比如以后加"机构")只需往数组里加一项,不需要改动匹配逻辑。
一个细节:**先 trim() 再 toLowerCase()**,保证用户输入首尾空格、大小写不一致时都能正确命中。
9.2 爬取层:多源降级链(crawler.js)
const SOURCES = [
{ name: "openalex", label: "OpenAlex", run: fromOpenAlex },
{ name: "crossref", label: "CrossRef", run: fromCrossRef },
{ name: "dblp", label: "DBLP", run: fromDblp }
];
async function crawlByTitle(title) {
const q = (title || "").trim();
if (!q) return { paper: null, status: "empty", message: "标题不能为空" };
// 1) 本地已存在 → 不重复爬取
const existing = window.PaperStore.find(q, true);
if (existing.length > 0) {
return { paper: existing[0], status: "exists", message: "本地已存在该论文,未重复爬取" };
}
// 2) 依次尝试各数据源(降级链)
const errors = [];
for (let i = 0; i < SOURCES.length; i++) {
const src = SOURCES[i];
try {
const paper = await src.run(q);
if (paper && paper.title) {
return { paper, status: src.name, message: "已从 " + src.label + " 获取论文信息" };
}
errors.push(src.label + ": 未检索到匹配结果");
} catch (e) {
// 网络失败 / CORS 被拦截 / 反爬验证 / 解析失败 → 尝试下一个源
console.warn("数据源 " + src.label + " 失败,尝试下一个:", e);
errors.push(src.label + ": " + e.message);
}
}
// 3) 全部失败 → 模拟数据兜底,保证功能可用且来源可追溯
const paper = {
title: q, authors: [], conference: "CVPR",
year: new Date().getFullYear(),
abstract: "(模拟数据)所有数据源均不可用,此为根据标题生成的演示记录……",
keywords: mergeKeywords(extractKeywords(q), [], q, 8),
url: "https://api.openalex.org/works?search=" + encodeURIComponent(q),
source: "simulated"
};
return { paper, status: "simulated", message: "数据源均不可用,已生成模拟记录(" + errors.join(";") + ")" };
}
思路:这是本项目最关键的改动。原始设计只调 DBLP,而实测发现 DBLP 的接口在浏览器端已经不可用。降级链的设计要点:
- 顺序尝试:
for循环 +try/catch,任一源成功即返回,避免单点故障; - 错误累积:
errors数组记录每个源失败的原因,最终展示给用户,让失败原因可见,而不是静默吞掉; - 兜底可追溯:全部失败时返回标注
source: "simulated"的记录,界面上用橙色标签明示为模拟数据——绝不让用户误以为这是真实爬取结果; - 顺序而非并发:批量爬取时逐条执行,避免触发数据源限流(Semantic Scholar 无 Key 时高频返回 429 就是教训)。
9.3 爬取层:关键词归一化与噪声过滤(crawler.js)
/**
* 领域词典:把常见表述归一化到规范关键词。
* 命中任一 pattern 就输出 canonical。
*/
const CV_TERMS = [
{ canonical: "object detection", patterns: ["object detection", "detector", "detect"] },
{ canonical: "segmentation", patterns: ["segmentation", "segment anything", "mask"] },
{ canonical: "diffusion model", patterns: ["diffusion", "denoising diffusion", "ddpm"] },
{ canonical: "depth estimation", patterns: ["depth estimation", "monocular depth", "metric depth"] },
// ... 共 40 余条,覆盖检测/分割/Transformer/扩散/三维/多模态等方向
];
function extractKeywords(text) {
const t = " " + String(text || "").toLowerCase() + " ";
const found = [];
CV_TERMS.forEach((term) => {
const hit = term.patterns.some((p) => t.indexOf(p.toLowerCase()) !== -1);
if (hit && found.indexOf(term.canonical) === -1) found.push(term.canonical);
});
return found;
}
思路:为什么一定要归一化? 如果直接把 "monocular depth"、"depth estimation"、"metric depth" 当作三个不同关键词,同一个研究方向会被拆成三份计数,Top10 排名失真、走势曲线断裂。归一化到唯一 canonical 后,不同论文的同一方向才能被合并统计——这是热词排行可比较的前提。
/**
* 数据源关键词校验:OpenAlex 的自动关键词噪声大,
* 会出现 "image (mathematics)"、"cartography"、"shot (pellet)"
* 这类与论文主题无关的通用概念,会污染 Top10 与热词走势。
* 过滤规则:
* 1) 含括号的(多为消歧义通用概念)丢弃;
* 2) 命中通用词黑名单的丢弃;
* 3) 未真实出现在标题/摘要中的丢弃;
* 4) 单个单词需至少出现 2 次,避免 generality/usability 这类偶然词。
*/
function mergeKeywords(dictKeywords, apiKeywords, text, limit) {
const out = dictKeywords.slice();
const t = String(text || "").toLowerCase();
(apiKeywords || []).forEach((kw) => {
const k = String(kw || "").toLowerCase().trim();
if (!k || k.length < 3) return;
if (k.indexOf("(") !== -1 || k.indexOf(")") !== -1) return; // 规则1
if (GENERIC_KEYWORDS.has(k)) return; // 规则2
const occurrences = countOccurrences(t, k);
if (occurrences === 0) return; // 规则3
if (k.indexOf(" ") === -1 && occurrences < 2) return; // 规则4
if (out.indexOf(k) === -1) out.push(k);
});
// 词典与数据源都无结果时,才退回高频实词
if (out.length === 0) {
frequentWords(text, 2).forEach((w) => out.push(w));
}
return out.slice(0, limit || 8);
}
思路:规则3(必须真实出现在正文中)是最有效的一条——它一句话就过滤掉了 cartography、geography、electrical engineering 这类"OpenAlex 认为相关但论文根本没提"的词。规则4 则解决了抽象名词的误入。
9.4 统计层:Top10 与热度走势(analytics.js)
/** 关键词 -> 文档频次(同一篇论文内重复出现只计 1 次) */
function keywordFrequency(papers) {
const freq = {};
papers.forEach((p) => {
(p.keywords || []).forEach((k) => {
freq[k] = (freq[k] || 0) + 1;
});
});
return freq;
}
/** Top N 关键词 */
function topKeywords(papers, n) {
const freq = keywordFrequency(papers);
return Object.keys(freq)
.map((k) => ({ name: k, value: freq[k] }))
.sort((a, b) => b.value - a.value || a.name.localeCompare(b.name))
.slice(0, n || 10);
}
思路:词频统计用文档频次(一篇论文内同一关键词只算 1 次)。这个口径的选择理由:如果用"出现总次数",摘要写得长的论文会天然占优,热度会偏向"话多的论文"而非"热门的方向"。sort 里加了 || a.name.localeCompare(b.name) 是为了让同分词频的结果稳定排序,避免每次刷新 Top10 顺序抖动。
/**
* 热度走势数据(功能5)
* 返回 { years, keywords, counts: { 关键词: { 会议: [每年词频] } } }
*/
function heatTrend(papers, topN) {
const ys = years(papers);
const kws = topKeywords(papers, topN || 8).map((k) => k.name);
const counts = {};
kws.forEach((k) => {
counts[k] = {};
CONFERENCES.forEach((c) => (counts[k][c] = ys.map(() => 0)));
});
// 把论文"投射"到 关键词 × 会议 × 年份 的三维计数矩阵上
papers.forEach((p) => {
const yi = ys.indexOf(p.year);
if (yi === -1) return;
(p.keywords || []).forEach((k) => {
if (counts[k] && counts[k][p.conference]) {
counts[k][p.conference][yi] += 1;
}
});
});
return { years: ys, keywords: kws, counts };
}
思路:先把计数矩阵按年份数初始化全 0,再遍历论文累加。这样做的好处是即使某个关键词在某年某会议没有论文,矩阵里也有对应的 0,保证 ECharts 的 series 数据长度一致——否则时间轴播放到某一年时图表会因为数据长度不齐而错位。这是"数据形状必须先对齐"的工程细节。
9.5 可视化层:热度走势动图(charts.js)
/**
* 功能5:热度走势对比「动图」
* ECharts timeline + 分组柱状图:自动播放年份 → 形成动图效果
* 每个年份帧展示该年在 CVPR/ICCV/ECCV 上各热词的词频对比
*/
function renderHeatTimeline(dom, trend) {
const { years, keywords, counts } = trend;
const colorMap = { CVPR: "#4f7cff", ICCV: "#22c1a4", ECCV: "#f5a524" };
// 每一帧的 series:三个会议各一条柱状系列
const confSeries = (yi) =>
["CVPR", "ICCV", "ECCV"].map((c) => ({
name: c,
type: "bar",
data: keywords.map((k) => (counts[k] && counts[k][c] ? counts[k][c][yi] : 0)),
itemStyle: { color: colorMap[c], borderRadius: [4, 4, 0, 0] },
barMaxWidth: 26
}));
// 为每个年份生成一帧 option
const options = years.map((y, yi) => ({
title: { text: y + " 年", subtext: "Top 热词在三大顶会的词频对比", left: "center" },
series: confSeries(yi)
}));
chart.setOption({
baseOption: {
timeline: {
axisType: "category",
autoPlay: true, // ← 自动播放,形成"动图"效果
loop: true,
playInterval: 1400,
data: years.map(String)
},
legend: { data: ["CVPR", "ICCV", "ECCV"] },
xAxis: { type: "category", data: keywords },
yAxis: { type: "value", name: "论文数" },
series: confSeries(0)
},
options: options // ← 每一帧
});
}
思路:作业要求"以动图的形式呈现"。我选择 ECharts timeline + baseOption/options 而不是生成 GIF,原因有三:
timeline的autoPlay天然产生动画效果,且用户可暂停、可拖动时间轴,交互性优于固定 GIF;- 不需要引入 GIF 生成依赖,纯前端即可实现;
- 数据变化时(比如新爬了论文)动图会自动重绘,而 GIF 是静态的死数据。
baseOption 放所有帧共享的配置(坐标系、图例、时间轴),options 放每帧独有的配置(标题、series 数据),两者合并渲染——这是 ECharts timeline 的标准用法。
9.6 应用层:模态框与页面路由(app.js)
/** 按需渲染对应页面(保证图表容器可见后尺寸正确) */
function renderPage(page) {
if (page === "home") renderHome();
else if (page === "papers") renderPapers();
else if (page === "trend") renderTrend();
}
function navigate(page) {
$$(".nav-item").forEach((a) => a.classList.toggle("active", a.dataset.page === page));
$$(".page").forEach((p) => p.classList.toggle("active", p.id === "page-" + page));
if (location.hash !== "#" + page) location.hash = page;
renderPage(page); // ← 必须在页面可见之后再渲染
}
思路:这是一个容易踩坑的地方。ECharts 在容器 display:none 时初始化会得到 0×0 尺寸,图表不可见。所以渲染顺序必须是"先切换页面可见 → 再初始化图表",而不是在页面加载时一次性初始化全部图表。
配合的另一个细节是 hashchange 的处理:
window.addEventListener("hashchange", function () {
const page = (location.hash || "#home").slice(1);
const el = $("#page-" + page);
// 仅在该页面尚未激活时跳转,避免 navigate() 改写 hash 后重复渲染
// (重复渲染会重建图表,导致热度走势动图重新开始播放)
if (el && !el.classList.contains("active")) navigate(page);
});
思路:navigate() 内部会改写 location.hash,这会再次触发 hashchange,造成重复渲染。表现出的 Bug 是"进入走势页后动图刚播两秒就重新开始"。加一个"已激活则跳过"的判断即可打破这个循环。
十、心路历程、收获
10.1 我对"结对"的理解被改写了
《构建之法》第 4 章讲两人合作,强调驾驶员与领航员的分工、代码复审、及时沟通。我原以为"和 AI 结对"就是"我描述需求、AI 出代码",效率高、不用吵架。
真正做下来才发现:AI 是一个执行力极强、但不会质疑自己前提的搭档。
最典型的例子就是爬取功能。AI 写的 DBLP 爬虫,代码结构清晰、注释完整、错误处理齐全,我第一遍读代码时完全找不出问题。直到我实际跑一遍,才发现它从头到尾就没拿到过数据——因为 DBLP 早已启用反爬、且不支持跨域。AI 不会告诉我"我假设了这个 API 可用,但这是我假设的",它只会自信地输出。
那一刻我才真正理解**"领航员"的价值:不是写得比驾驶员快,而是盯着驾驶员没在看的地方。在人人结对里,这个角色由同伴承担;在人机结对里,必须由我自己承担,且没有第二个人兜底**。
10.2 三个具体收获
**"读懂"不等于"验证"**。我以前判断 AI 代码好坏的标准是"逻辑我能不能看懂"。现在我的标准是"我有没有跑过、有没有看过真实输出"。案例 C1 就是活教材——逻辑满分,实际零分。
AI 会按"理想化场景"写代码。中文分号
;那个 Bug 很小,但很说明问题:AI 默认用户输入是规范的英文标点。真实用户的输入永远比想象中脏,这类问题只有站在用户角度测试才会暴露。统计类项目里,"口径"比"算法"重要。热词统计没有复杂算法,难点全在口径定义上:词频按文档频次还是总次数?同义关键词怎么合并?噪声词怎么过滤?这些决定结果有没有意义。我花在口径上的时间远超写统计代码的时间。
10.3 关于"数据诚实"的体会
爬取功能降级到模拟数据时,我做了个选择:给每条数据打上 source 标签,在界面上用不同颜色明示来源(OpenAlex 绿色、模拟橙色)。
这其实增加了一点开发成本,但我觉得这是必要的。一个"热词统计平台"如果混入了假数据却不告诉用户,它输出的所有分析结论都是不可信的——这比功能少一个更严重。技术上的降级可以接受,但降级必须可见。
【截图占位:界面上的 source 来源标签截图】
十一、对 AI 结对伙伴的评价
11.1 贡献
- 速度:编码效率远超我自己写。
charts.js的 ECharts 配置、style.css的整体样式,AI 一次输出就基本可用。 - 广度:需求分析阶段从三个角色视角列出的痛点,帮我发现了自己没想到的需求点。
- 耐心:反复迭代、随时可以推倒重来,不会像人类搭档那样有情绪成本。
- 解释能力:当我问"为什么这样设计"时,AI 能给出合理的技术理由(比如为什么用开方压缩节点大小),这些解释本身有学习价值。
11.2 局限(都是我在本次作业里真实踩到的)
| 局限 | 本次的具体表现 | 我的应对 |
|---|---|---|
| 不会主动验证前提 | 默认 DBLP API 可被浏览器直接调用,实际早已反爬 + 无 CORS | 所有涉及外部依赖的代码,必须亲自联网实测 |
| 知识可能过时 | 第三方接口的反爬策略、限流规则会变,AI 的知识未必是最新的 | 把"实测结果"反馈给 AI,让它基于新事实重新设计 |
| 倾向于"多给" | 需求分析时多列了 3 个不相关功能;代码里加了不必要的复杂度 | 人类负责做减法,敢于拒绝 AI 的产出 |
| 按理想场景编码 | 分隔符正则只考虑英文标点,漏掉中文全角符号 | 站在真实用户角度设计测试用例 |
| 可能自信地给出错误方案 | 若我不实测,爬取功能会带着"正确"的外表一直错到演示 | 把 AI 当执行者而非决策者,关键结论必须自己验证 |
11.3 总结
人机结对 vs 人人结对:
| 维度 | 人人结对 | 人机结对(本次) |
|---|---|---|
| 角色分工 | 驾驶员 + 领航员,可轮换 | 我 = 驾驶员 + 领航员,AI = 高速执行器 |
| 沟通成本 | 高(需要同步理解) | 极低(描述清楚即可) |
| 代码审查 | 同伴会主动质疑 | AI 不会质疑自己的前提,审查责任全在人类 |
| 情绪因素 | 存在(分歧、疲劳) | 无 |
| 兜底 | 有第二个人 | 没有第二个人,自己是唯一防线 |
一句话总结:AI 把"写代码"这件事的速度提升了数倍,但它同时把"验证代码"的责任全部压回到了我身上。这次作业里,真正决定成品能不能用的,不是 AI 写得多快,而是我有没有老老实实去跑它、看它、质疑它。
所以我的结论是:AI 结对不是"省事",而是把人的精力从"敲代码"转移到了"做判断"——判断需求、判断口径、判断 AI 说的到底对不对。
附录:项目仓库与运行方式
# 克隆仓库(注意:git 克隆地址使用「仓库名」,与网页 URL 中的数字 ID 不同)
git clone https://codehub.devcloud.cn-north-4.huaweicloud.com/19c4f4b120bc43f7a93cda0c3fc8e4d2/102400423.git
# 运行(纯前端,零依赖)
# 方式一:直接双击 index.html
# 方式二:启动本地静态服务器
python -m http.server 8080 # 然后访问 http://localhost:8080
自测脚本:
node test/selftest.js # 离线自测(52 项)
node test/selftest.js --live # 含联网实测(55 项)
相关推荐
产业园区运营负责人如何利用数智平台实现创新资源的精准导入与高效配置?.docx
科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。
国央企创新负责人如何利用平台实现技术转移与科研成果的系统性管理?.docx
科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。
车辆轨迹跟踪MPC、神经网络NN+自适应神经模糊系统ANFIS优化模型预测仿真(带参考文献)
内容概要:本文围绕车辆轨迹跟踪控制展开,结合模型预测控制(MPC)、神经网络(NN)与自适应神经模糊推理系统(ANFIS)构建优化的仿真模型,旨在提升自动驾驶或智能车辆在复杂工况下的路径跟踪精度与鲁棒性。研究采用线性三自由度车辆动力学模型,并融合无迹卡尔曼滤波(UKF)对关键状态变量(如横摆角速度、质心侧偏角和车速)进行精确估计,实现高可靠性状态反馈。在此基础上,引入NN与ANFIS对MPC控制器进行参数优化与非线性补偿,增强系统对不确定性和外部扰动的适应能力。全文配有参考文献与仿真说明,提供了完整的Matlab/Simulink实现方案,具有较强的可复现性与工程应用价值。; 适合人群:从事智能车辆控制、自动驾驶算法研发、先进驾驶辅助系统(ADAS)研究的研究生、科研人员及工程技术人员,具备一定的控制理论基础与Matlab仿真经验者更佳。; 使用场景及目标:① 掌握MPC在车辆轨迹跟踪中的建模与实现方法;② 学习NN与ANFIS在非线性控制系统中的参数优化与自适应调节机制;③ 实现UKF状态估计与MPC-NNS-ANFIS联合仿真的集成设计,提升控制系统的智能化水平与实际适应能力。; 阅读建议:建议结合文中提供的参考文献深入理解控制算法的理论基础,并按照仿真步骤逐步搭建模型,重点调试MPC权重系数、神经网络结构及模糊规则的设计,以充分掌握多方法融合的系统优化思路。
高校技术转移办公室人员在技术成果推广中如何实现精准识别与快速匹配?.docx
科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。
基于一致性理论的三机并联独立微网二次控制模型研究(Simulink仿真实现)
内容概要:本文研究了基于一致性理论的三机并联独立微网二次控制模型,旨在解决微网系统在孤岛运行模式下频率和电压的精确恢复问题。通过构建三台分布式发电单元(DG)并联运行的微网系统模型,设计并实现了一种基于一致性算法的二次控制策略,有效补偿了一次控制引入的频率与电压偏差,实现了无静差调节。研究详细阐述了系统建模、控制策略设计、一致性协议应用及稳定性分析全过程,并利用Simulink平台进行仿真实验。仿真结果验证了该控制策略能够快速消除偏差,使各DG单元输出趋于一致,显著提升了微网系统的电能质量与运行稳定性。; 适合人群:电力系统、自动化、电气工程等相关专业的科研人员、研究生及从事微网控制技术开发的工程技术人员。; 使用场景及目标:①用于独立微网系统中实现频率和电压的协同控制与精确调节;②为研究多智能体一致性理论在电力系统中的应用提供实例参考;③目标是提升微网在孤岛运行模式下的电能质量与稳定运行能力。; 阅读建议:此资源结合理论分析与Simulink仿真实践,建议读者在理解一致性算法基本原理的基础上,动手搭建仿真模型,重点关注控制策略的实现细节与参数整定方法,通过对比一次控制与二次控制的仿真结果,深入掌握其补偿机制与优越性。


