102400416_李明学_软件工程第二次作业

102400416李明学 2026-09-24 22:58:57

计算机视觉学术热点追踪工具 —— 人机结对编程作业报告

学号:102400416

这个作业属于哪个课程https://bbs.csdn.net/forums/2601_CS_SE_FZU
这个作业要求在哪里https://bbs.csdn.net/topics/620526318
这个作业的目标以 AI(Claude Code)为结对伙伴,完成一个「计算机视觉学术热点追踪工具」,并如实记录人机协作的完整过程
其他参考文献arXiv API 官方文档、networkx 文档、matplotlib 文档

目录

  • 计算机视觉学术热点追踪工具 —— 人机结对编程作业报告
  • 一、AI 结对工具说明
  • 1. 使用方式
  • 2. 主要辅助内容
  • 3. 协作模式
  • 二、NABCD 需求分析
  • 三、原型设计与信息架构
  • 墨刀原型设计(MockingBot)
  • 四、人机协作记录(迭代过程)
  • 迭代 1:需求澄清与选题
  • 迭代 2:技术方案设计
  • 迭代 3:环境搭建
  • 迭代 4:编码实现三个模块
  • 迭代 5:发现 Bug —— 爬虫遇到网络超时崩溃(对应案例 C)
  • 迭代 6:发现设计缺陷 —— 逐年趋势图是「平线」(对应案例 C)
  • 迭代 7:发现 Bug —— 终端中文乱码(对应案例 C)
  • 迭代 8:单元测试发现关键词合并缺陷(对应案例 C 的第四处 Bug)
  • 五、三个代表性协作案例
  • 案例 A:AI 辅助需求分析与原型设计
  • 案例 B:AI 辅助编码实现功能模块
  • 案例 C:AI 辅助调试与修复 Bug(含「发现 AI 代码错误并纠正」)
  • 六、可解释性声明
  • 七、成品展示
  • ① 关键词共现网络图
  • ② 逐年发文量增长动图
  • ③ 热门关键词条形图竞赛动图
  • 八、结对(人机)讨论过程描述
  • 九、设计实现过程
  • 十、代码说明(分模块阐述)
  • 9.1 模块 1:论文爬虫 crawler.py
  • 9.2 模块 2:关键词图谱 keyword_graph.py
  • 9.3 模块 3:热度走势动图 trend_animation.py
  • 9.4 模块 4:入口与配置 main.py / config.py
  • 9.5 模块 5:单元测试 tests/
  • 十一、心路历程、收获
  • 十二、对 AI 结对伙伴的评价
  • 附录
  • PSP 表格
  • 华为云代码仓库链接
  • 墨刀原型设计网页链接
  • 代码规范(在华为云代码仓库)
  • 代码commit截图

一、AI 结对工具说明

本次项目使用 Claude 作为人机结对编程辅助工具,全程参与项目开发与文档撰写。

1. 使用方式

以自然语言对话形式进行结对协作,通过精准提示词提出开发需求、修改要求与文档规范,由 Claude 输出初稿,我负责审核、校验、修改、最终决策。

2. 主要辅助内容

  • 项目需求梳理、NABCD 需求分析撰写
  • 项目结构搭建、代码文件拆分、分批迭代开发
  • Python 后端代码生成、接口编写、数据处理逻辑实现
  • 关键词统计、热词分析、可视化数据逻辑优化
  • 协助排查代码 bug、优化代码规范、修复逻辑漏洞
  • 辅助撰写 PSP 表格、博客文档、总结反思等作业材料

3. 协作模式

Claude 负责快速产出代码、文档初稿与方案参考,我负责需求判断、代码审查、逻辑校验、迭代修改与最终负责,实现人机结对开发模式。

二、NABCD 需求分析

N(Need,需求)
计算机视觉(cs.CV)是近几年论文数量爆发式增长的领域(arXiv 上从 2016 年的约 3600 篇增长到 2025 年的约 35000 篇)。研究者、学生在选方向、读论文之前,常常需要快速回答两个问题:「这个领域最近在火什么?」「哪些子方向在上升、哪些在退潮?」。逐篇翻阅海量论文显然不现实,需要一个能自动汇总趋势的工具。

A(Approach,做法)
用 arXiv 官方开放 API 抓取 cs.CV 的论文元数据(标题、摘要、年份),然后:

  1. 从标题中抽取关键词,统计两两「共同出现在同一篇论文标题」的频率,构建关键词共现网络;
  2. 按年份统计发文量与热门关键词,做成逐年动图,直观展示热度走势。

B(Benefit,好处)

  • 自动化:一次运行即可完成「抓取 → 分析 → 可视化」全流程;
  • 直观:图谱一眼看清子方向聚类,动图一眼看清趋势;
  • 轻量:不依赖数据库和重型 NLP 框架,逻辑透明、可解释、可复现。

C(Competitors,竞品)
Semantic Scholar、Connected Papers 等工具也能做相似的事,但它们更重、偏通用,且可视化不可定制。本工具聚焦单一领域、代码开源可改,适合教学与二次开发。

D(Delivery,交付)
Python 脚本 + 生成的三类产出物(CSV、共现网络 PNG、两个 GIF),以及本报告。源码托管在代码仓库(链接见「成品展示」)。


三、原型设计与信息架构

系统信息架构(数据流)

flowchart LR
    A[arXiv API] -->|fetch| B[crawler.py<br/>论文爬虫]
    B -->|papers.csv| C[keyword_graph.py<br/>关键词图谱]
    B -->|yearly_counts.csv| D[trend_animation.py<br/>热度动图]
    C -->|keyword_graph.png| E[成品]
    D -->|papers_per_year.gif<br/>keyword_race.gif| E

原型设计说明

本平台的原型分两个层次:

  1. 程序成品形态(命令行 + 静态产出物):python main.py 一键完成全流程,产出 5 个文件——① 论文数据表(CSV);② 关键词共现网络图;③ 两张热度动图。

  2. 墨刀高保真 Web 原型:见下文「墨刀原型设计」。

墨刀原型设计(MockingBot)

选用墨刀(MockingBot)这一专用原型设计工具,为平台设计了 5 个页面。

制作过程:

  1. 先撰写《原型设计提示文稿》(仓库内 prototype_prompt.md),明确全局设计规范——深科技蓝主色 #2C6EF2、卡片白底圆角、顶部 5 菜单导航栏;
  2. 据此在墨刀中逐页搭建:拖拽组件搭骨架 → 统一配色与导航 → 填入数据占位(Top 10 榜单、网络图、动图占位样式)→ 设置交互(弹窗、页面跳转、悬停、播放)

img

img

5 个页面一览:

页面主要内容
首页 · 热门方向总览3 个统计卡片(累计论文 1200 篇 / 年份 2016–2025 / 热门方向 40 个);左侧 Top 10 热门方向榜单;右侧关键词共现图谱(40 节点、173 条边,节点大小=词频、颜色=社区)
热度走势对比页左「逐年发文量增长」柱状动图(3631 → 34987);右「热门关键词条形图竞赛」;控制条(年份区间、播放/暂停、速度)
论文列表管理页搜索/筛选工具栏 + 数据表格(ID/标题/作者/年份/分类/操作),支持新增、编辑(弹窗表单)、删除(二次确认)、查询、分页
论文爬取/导入页左「在线爬取」配置(年份区间、分类、采样数)+ 进度条 + 实时日志;右「本地导入」上传 CSV
详情/关于页项目简介、数据来源(arXiv API)、技术栈、功能模块、人机协作说明

页面流转:导航栏 5 菜单互通;首页图谱点击关键词 → 跳转热度走势页;爬取完成 → 自动跳转论文列表。

**原型截图:

img

img

img

img

img

  • 原型设计提示文稿:仓库内 prototype_prompt.md;另附可交互 HTML 原型 prototype/index.html(浏览器直接打开预览)。

四、人机协作记录(迭代过程)

说明:以下是我与 AI 结对伙伴(Claude Code)在本次作业中的真实协作记录。关键提示词以代码块给出,AI 输出用摘要描述,并说明我采纳/拒绝的理由与修改。

迭代 1:需求澄清与选题

我的提示词(核心):

帮我做一个「论文爬虫 + 关键词图谱 + 热度走势动图」的项目……

AI 输出摘要:AI 没有立刻动手写代码,而是先确认了两个关键决策——追踪的主题领域、论文数据源,并给出了每个选项的可行性(如知网反爬严格、arXiv 有免费开放 API)。

我的修改与理由:

  • 采纳:AI 提议先澄清需求再动手,避免了方向性返工;
  • 选择:主题定为「计算机视觉」,数据源定为「arXiv API」——因为 arXiv 无需登录、官方提供 API、最稳定,适合演示完整流程。拒绝了「知网」选项(反爬严格、需要登录,实现不可控)。

迭代 2:技术方案设计

AI 输出摘要:给出三模块划分(爬虫 / 关键词图谱 / 热度动图)与技术栈(requests + 标准库 xml 解析 + networkx + matplotlib),并强调关键词抽取用「停用词过滤 + 词频 + bigram 合并」的轻量方案而非重型 NLP 库。

我的修改与理由:

  • 采纳:三模块划分清晰,正好对应作业要求的三个功能;
  • 采纳:轻量关键词抽取方案——代码透明、我能逐行看懂并解释,满足「禁止盲抄」的要求。

迭代 3:环境搭建

AI 输出摘要:检测到 Python 3.14.4,缺少依赖库,用 pip 安装了 requests、networkx、matplotlib、pandas、pillow。

我的修改与理由:直接采纳,无修改。

迭代 4:编码实现三个模块

AI 输出摘要:一次性生成了 crawler.py、keyword_graph.py、trend_animation.py、main.py,并对每个函数写了中文 docstring 说明设计思路。

我的修改与理由:先做小规模测试(2 年 × 30 篇)验证全流程能跑通,再决定是否扩大规模——这是我自己加的「先小后大」的验证策略。

迭代 5:发现 Bug —— 爬虫遇到网络超时崩溃(对应案例 C)

现象:完整运行时,抓到 2021 年时抛出 ReadTimeout,程序直接崩溃,且之前抓取的 5 年数据没有落盘。

AI 输出摘要:AI 定位为「缺重试机制 + 未增量落盘」,并修改了 fetch_page(加指数退避重试、超时 30s→60s)和 crawl(每抓完一年就写一次 CSV、某年失败只跳过不影响整体)。

我的修改与理由:

  • 采纳:重试机制和逐年落盘都是合理且必要的容错设计,我 review 了代码逻辑后认可;
  • 补充验证:我要求 AI 重新完整跑一遍,确认 1200 篇全部抓取成功。

迭代 6:发现设计缺陷 —— 逐年趋势图是「平线」(对应案例 C)

现象:重新出图后我发现,「逐年发文量」柱状图每个年份都是 120(因为每年均匀采样 120 篇),根本体现不出热度增长趋势,违背了「热度走势」的本意。

AI 输出摘要:AI 承认这是设计缺陷,提出用 arXiv 返回的 totalResults 字段直接读取每年「真实发文总数」(一次请求即可),而不是用采样数。

我的修改与理由:

  • 采纳:这是关键改进。我认可「采样数只能用于关键词分析,趋势必须用真实总数」的判断;
  • 结果:真实数据呈现 2016 年 3,631 篇 → 2025 年 34,987 篇,近 10 倍增长,趋势一目了然。

迭代 7:发现 Bug —— 终端中文乱码(对应案例 C)

现象:在 Windows 终端运行 python main.py 时,打印的中文日志(如 [crawl] 2016: 120 篇 中的「篇」字)显示成一串乱码,影响可读性。

AI 输出摘要:AI 定位为 Windows 终端默认编码(GBK)与 Python 输出 UTF-8 不一致,在入口处加 sys.stdout.reconfigure(encoding="utf-8") 修复。

我的修改与理由:

  • 采纳:一行改动即可解决,我 review 后认可;
  • 补充验证:重新运行确认中文日志正常显示。

迭代 8:单元测试发现关键词合并缺陷(对应案例 C 的第四处 Bug)

现象:我为 keyword_graph.py 补充了单元测试,运行后测试失败——本该被合并成 "object detection" 的 bigram 关键词,在结果里一个都没有。

我的定位:我逐行读代码找到根因——extract_keywords 里 solid 集合存的是元组(如 ("object", "detection")),但合并后的关键词是字符串("object detection"),最后的过滤条件 m in solid 拿字符串去和元组集合匹配,永远匹配不上,于是所有合并出的 bigram 都被过滤掉了。

AI 输出摘要:我向 AI 指出这个问题后,AI 确认是逻辑错误,并把过滤条件改成「含空格的字符串就是已合并的 bigram(" " in m)」,不含空格的单词才按词频阈值过滤。

我的修改与理由:

  • 采纳:修复逻辑正确、简洁,我 review 后认可;
  • 价值:这是「用单元测试主动发现并纠正 AI 代码错误」的典型例子——测试不是摆设,它真的能拦住 AI 代码里的缺陷。

这里是与ai进行细节讨论的截图

img

五、三个代表性协作案例

案例 A:AI 辅助需求分析与原型设计

  • 头脑风暴:我只有模糊想法(「做个论文爬虫和图」),AI 通过提问帮我把需求收敛成「计算机视觉学术热点追踪」这一明确主题;
  • 信息架构:AI 把系统拆成「数据获取 / 图谱分析 / 趋势可视化」三层,并画出 mermaid 数据流图;
  • 文案优化:AI 把输出从干巴巴的「bar chart」优化成「关键词条形图竞赛(bar chart race)」,让功能描述更直观。

案例 B:AI 辅助编码实现功能模块

  • 论文爬虫(crawler.py):用 requests 调 arXiv API,标准库解析 Atom XML,按年份抓取;
  • 关键词图谱(keyword_graph.py):停用词过滤 + 词频统计 + bigram 合并抽取关键词,构建共现网络,networkx + matplotlib 出图;
  • 热度走势动图(trend_animation.py):matplotlib.animation 逐帧更新,pillow 保存 GIF。

案例 C:AI 辅助调试与修复 Bug(含「发现 AI 代码错误并纠正」)

这是本次作业最想强调的案例,包含四次「发现 AI 代码问题并纠正」:

  1. 网络超时崩溃:AI 初版爬虫没有重试机制,遇到瞬时 ReadTimeout 直接崩溃,且不落盘导致数据丢失。我复现问题后,AI 补上重试 + 增量落盘。
  2. 趋势图平线缺陷:AI 初版用「采样数」画逐年柱状图,导致所有年份都是 120、趋势是平的。这是我主动发现的设计缺陷,AI 据此改用 totalResults 真实发文量修复。
  3. 终端中文乱码:Windows 终端默认编码与 Python 的 UTF-8 输出不一致,导致中文日志显示乱码。AI 在入口处加 sys.stdout.reconfigure(encoding="utf-8") 修复。
  4. 关键词 bigram 合并被误过滤:我为 keyword_graph.py 写单元测试时,测试失败暴露出一个隐蔽缺陷——solid 集合存的是元组、过滤时却拿字符串做 m in solid 匹配,导致所有合并出的 bigram(如 "object detection")都被丢弃。这是通过单元测试主动发现并纠正的 AI 代码错误。

关键收获:AI 生成的代码不是「一次到位」的,需要人工 review、实际运行、质疑结果(甚至写测试去验证),才能发现并纠正其中的缺陷——这正是结对编程中「人」不可替代的价值。


六、可解释性声明

按照作业要求,如实声明代码来源与我的理解程度。

文件 / 代码来源我是否理解其设计思路
crawler.py 整体框架、解析逻辑AI 生成是(能解释 arXiv API 参数、Atom XML 解析、重试机制)
crawler.py 的容错改进(重试/落盘)AI 提出,我 review 后采纳是
keyword_graph.py 关键词抽取与建图AI 生成是(能解释停用词/bigram/共现的定义)
trend_animation.py 动图逻辑AI 生成是(能解释 FuncAnimation 逐帧更新的原理)
main.py 参数与流程编排AI 生成是
需求澄清、选题、数据源决策我主导—
发现趋势平线缺陷、bigram 合并缺陷我主动发现(后者通过单元测试)—

七、成品展示

运行 python main.py 后得到以下成品:

成品说明
papers.csv1200 篇 cs.CV 论文元数据(2016–2025,每年 120 篇采样)
yearly_counts.csv每年真实发文总数(3631 → 34987,近 10 倍增长)
keyword_graph.png关键词共现网络图(40 个关键词、173 条边,按社区着色)
papers_per_year.gif逐年发文量增长柱状动图(10 帧)
keyword_race.gif历年热门关键词条形图竞赛(10 帧)

① 关键词共现网络图

img

② 逐年发文量增长动图

img

③ 热门关键词条形图竞赛动图

img


八、结对(人机)讨论过程描述

讨论主要围绕三个问题展开,方式是我提问 / 质疑、AI 解释 / 修改:

  1. 「先做什么?」 —— AI 坚持先澄清主题和数据源再做,避免返工;
  2. 「为什么崩了?」 —— 针对 ReadTimeout,AI 解释是瞬时网络错误 + 缺重试,我认可后采纳其修复方案;
  3. 「这图对吗?」 —— 我发现趋势图是平线,质疑 AI 的采样方案,AI 承认缺陷并改用真实发文量。

整个讨论是「我提出需求与质疑 → AI 提出方案 → 我 review 并反馈 → AI 修改」的循环。


九、设计实现过程

  1. 数据层:crawler.py 按年份构造 submittedDate 区间查询,翻页抓取,解析 Atom XML 得到标题/摘要/年份等字段;crawl_counts 读 totalResults 得到真实发文量。
  2. 分析层:keyword_graph.py 用 extract_keywords 抽取关键词(含 bigram 合并),build_cooccurrence 统计两两共现,build_graph 建网络。
  3. 展示层:trend_animation.py 用 FuncAnimation 逐帧生成两张动图;main.py 串联全流程并提供命令行参数。
  4. 容错:重试机制、逐年落盘、--no-crawl 复用已有数据,保证流程可断点续跑。

十、代码说明(分模块阐述)

项目按「数据获取 → 图谱分析 → 趋势可视化」三层拆成 4 个模块 + 1 组单元测试。下面分模块说明职责与关键设计点,并选录最能体现核心逻辑的关键代码(完整代码见仓库,均有中文注释)。

9.1 模块 1:论文爬虫 crawler.py

职责:通过 arXiv 官方 API 抓取 cs.CV 论文元数据(标题/摘要/年份/作者/分类)存为 CSV,并抓取每年真实发文总数。

关键设计点:

  • 礼貌抓取:每次请求间隔 3s(REQUEST_DELAY),遵守 arXiv 限速要求;
  • 容错重试:fetch_page 失败后按 5s、10s 退避重试,最多 3 次(对应迭代 5 的 ReadTimeout 崩溃);
  • 增量落盘:crawl 每抓完一年就写一次 CSV,某年失败只跳过不拖垮整体,中途崩溃也不丢已抓数据;
  • 真实发文量:fetch_year_count 只读 opensearch:totalResults(一次请求),得到每年真实总数(对应迭代 6 的「趋势平线」缺陷)。

关键代码(重试退避):

def fetch_page(search_query, start=0, max_results=100,
               sort_by="submittedDate", sort_order="ascending",
               retries=3, timeout=60):
    """抓取一页结果,遇到瞬时网络错误会自动重试(退避 5s、10s)。"""
    params = {"search_query": search_query, "start": start,
              "max_results": max_results, "sortBy": sort_by,
              "sortOrder": sort_order}
    for attempt in range(retries):
        try:
            resp = requests.get(BASE_URL, params=params, timeout=timeout)
            resp.raise_for_status()
            root = ET.fromstring(resp.text)
            return [parse_entry(e) for e in root.findall("a:entry", ATOM_NS)]
        except (requests.exceptions.RequestException, ET.ParseError) as exc:
            if attempt == retries - 1:
                raise
            wait = 5 * (attempt + 1)          # 指数退避
            print(f"[crawl] 请求失败({exc.__class__.__name__}),{wait}s 后重试...")
            time.sleep(wait)
    return []

关键代码(真实发文量):

def fetch_year_count(year, category="cs.CV", retries=3, timeout=60):
    """查询某一年论文的真实总数(读 opensearch:totalResults,一次请求即可)。"""
    query = f"cat:{category} AND submittedDate:[{year}01010000 TO {year}12312359]"
    params = {"search_query": query, "start": 0, "max_results": 1}
    for attempt in range(retries):
        try:
            resp = requests.get(BASE_URL, params=params, timeout=timeout)
            resp.raise_for_status()
            root = ET.fromstring(resp.text)
            node = root.find("o:totalResults",
                             {"a": ATOM_NS["a"], "o": OPENSEARCH_NS["o"]})
            return int(node.text) if node is not None else 0
        except (requests.exceptions.RequestException, ET.ParseError) as exc:
            if attempt == retries - 1:
                raise
            time.sleep(5 * (attempt + 1))
    return 0

9.2 模块 2:关键词图谱 keyword_graph.py

职责:从论文标题抽取关键词,统计两两共现,构建 networkx 网络图并保存 PNG。

关键设计点:

  • 轻量关键词抽取:停用词过滤 + 词频统计 + bigram 合并,不依赖重型 NLP 库,逻辑透明可解释;
  • bigram 合并:相邻且词频够高的两个词合并成词组(如 "object" + "detection" → "object detection");
  • 过滤逻辑(迭代 8 修复点):合并后只保留「已合并的 bigram(含空格)」或「高频单词」,修掉了原来拿字符串去匹配元组集合、导致 bigram 全被误过滤的 bug;
  • 共现建边:两个关键词出现在同一篇标题里 = 一条边,权重 = 共现次数;
  • 可视化:节点大小 = 词频、颜色 = 社区(贪心模块度)、边粗细 = 共现强度。

关键代码:

def extract_keywords(titles, stopwords=STOPWORDS, min_freq=3):
    """从标题列表中抽取关键词,返回关键词列表、单词词频、二元组词频。"""
    tokenized = [
        [w for w in tokenize(t) if w not in stopwords and len(w) > 2]
        for t in titles
    ]
    word_freq = Counter(w for toks in tokenized for w in toks)
    bigram_freq = Counter()
    for toks in tokenized:
        for a, b in zip(toks, toks[1:]):
            bigram_freq[(a, b)] += 1

    # 词频达到阈值的二元组,才认为是稳定搭配,可合并成关键词
    solid = {bg for bg, c in bigram_freq.items() if c >= min_freq}

    keywords_per_title = []
    for toks in tokenized:
        merged = []
        i = 0
        while i < len(toks):
            if i + 1 < len(toks) and (toks[i], toks[i + 1]) in solid:
                merged.append(toks[i] + " " + toks[i + 1])
                i += 2
            else:
                merged.append(toks[i])
                i += 1
        # 只保留「高频单词」或「已合并的 bigram」:
        # 含空格的字符串是已合并的 bigram;不含空格的单词按词频阈值过滤。
        merged = [m for m in merged
                  if (" " in m) or word_freq.get(m, 0) >= min_freq]
        keywords_per_title.append(merged)
    return keywords_per_title, word_freq, bigram_freq

9.3 模块 3:热度走势动图 trend_animation.py

职责:用 matplotlib.animation 生成两个 GIF——逐年发文量增长图、热门关键词条形图竞赛。

关键设计点:

  • 逐帧动画:FuncAnimation + update(i) 逐帧画第 i 年,pillow 保存 GIF;
  • 柱状图:用 yearly_counts.csv 的真实发文总数,逐年累加呈现近 10 倍增长;
  • 条形图竞赛:全局关键词池统一配色、固定横轴范围,避免逐帧跳动。

关键代码:

def animate_keyword_race(df, out_gif="keyword_race.gif", top_k=10,
                         fps=2, min_freq=2):
    """动画 2:历年热门关键词的条形图竞赛。"""
    years = sorted(df["year"].unique())
    per_year = {}
    for y in years:
        titles = df[df["year"] == y]["title"].dropna().tolist()
        kw_per_title, _, _ = extract_keywords(titles, min_freq=min_freq)
        per_year[y] = Counter(k for kw in kw_per_title for k in kw)

    pool = set()
    for y in years:
        pool.update(k for k, _ in per_year[y].most_common(top_k))
    cmap = plt.cm.tab20
    colors = {k: cmap(i % 20) for i, k in enumerate(sorted(pool))}  # 统一配色
    global_max = max(max(per_year[y].values(), default=0) for y in years) or 1

    fig, ax = plt.subplots(figsize=(10, 6))

    def update(i):
        ax.clear()
        y = years[i]
        top = per_year[y].most_common(top_k)[::-1]   # 倒序,最大的在最上面
        labels = [t[0] for t in top]
        vals = [t[1] for t in top]
        ax.barh(labels, vals, color=[colors[l] for l in labels])
        for idx, v in enumerate(vals):
            ax.text(v, idx, f" {v}", va="center", fontsize=9)
        ax.set_xlim(0, global_max * 1.15)           # 固定横轴,避免跳动
        ax.set_xlabel("Frequency")
        ax.set_title(f"Top {top_k} Keywords in {y} (cs.CV)")

    anim = animation.FuncAnimation(fig, update, frames=len(years),
                                   interval=800, repeat=True)
    anim.save(out_gif, writer="pillow", fps=fps)
    plt.close()

9.4 模块 4:入口与配置 main.py / config.py

职责:命令行入口串联三个模块;config.py 集中管理抓取范围、采样数、阈值等参数。

关键设计点:

  • 终端编码修复:fix_console_encoding 用 sys.stdout.reconfigure(encoding="utf-8") 解决 Windows GBK 终端中文乱码(对应迭代 7);
  • 参数化:--years、--max-per-year、--no-crawl 支持不同规模和断点续跑;
  • 流程编排:爬虫 → 发文量 → 图谱 → 动图,依次调用。

关键代码:

def fix_console_encoding():
    """让中文输出在各类终端(含 GBK 的 Windows 终端)都正常显示。"""
    for stream in (sys.stdout, sys.stderr):
        try:
            stream.reconfigure(encoding="utf-8")
        except Exception:
            pass


def main():
    fix_console_encoding()
    parser = argparse.ArgumentParser(description="CV 学术热点追踪工具")
    parser.add_argument("--years", default=config.DEFAULT_YEARS)
    parser.add_argument("--max-per-year", type=int, default=config.MAX_PER_YEAR)
    parser.add_argument("--no-crawl", action="store_true")
    args = parser.parse_args()

    if args.no_crawl:
        print("[main] 使用已有 CSV,跳过爬虫")
    else:
        start, end = map(int, args.years.split("-"))
        years = range(start, end + 1)
        crawler.crawl(years, max_per_year=args.max_per_year,
                      category=config.CATEGORY, out_csv=args.csv)
        crawler.crawl_counts(years, category=config.CATEGORY,
                             out_csv="yearly_counts.csv")

    keyword_graph.run(csv_path=args.csv, out_png="keyword_graph.png",
                      min_freq=config.MIN_FREQ, top_n=config.TOP_N)
    trend_animation.run(csv_path=args.csv, counts_csv="yearly_counts.csv")

config.py 全文:

CATEGORY = "cs.CV"            # arXiv 分类
DEFAULT_YEARS = "2016-2025"   # 默认抓取年份区间
MAX_PER_YEAR = 120            # 每年最多采样论文数
MIN_FREQ = 3                  # 关键词最低词频
TOP_N = 40                    # 图谱保留的高频关键词数

9.5 模块 5:单元测试 tests/

职责:对不依赖网络的纯函数做单元测试,验证解析与关键词抽取的正确性。

关键设计点:

  • bigram 合并测试:构造 3 条含 "Object Detection" 的标题,断言合并出 "object detection"、过滤掉低频词——正是这个测试暴露了迭代 8 的 bug;
  • Atom 解析测试:构造最小 <entry>,断言版本号被去掉、空白被压平、作者/分类解析正确。

关键代码:

class TestExtractKeywords(unittest.TestCase):
    def test_bigram_merge(self):
        titles = [
            "Object Detection with Neural Networks",
            "Object Detection in Images",
            "Object Detection Survey",
        ]
        kw_per_title, _, _ = extract_keywords(titles, min_freq=2)
        all_kw = {k for kws in kw_per_title for k in kws}
        self.assertIn("object detection", all_kw)      # 相邻 3 次,应合并
        self.assertNotIn("neural", all_kw)              # 低频词应被过滤


class TestParseEntry(unittest.TestCase):
    def test_parse_title_year_and_version(self):
        xml = """<entry xmlns="http://www.w3.org/2005/Atom">
          <id>http://arxiv.org/abs/2101.12345v2</id>
          <title>  Deep Learning  for  Vision </title>
          <published>2021-01-15T18:59:59Z</published>
          <author><name>Alice</name></author>
          <category term="cs.CV"/>
        </entry>"""
        p = parse_entry(ET.fromstring(xml))
        self.assertEqual(p["arxiv_id"], "2101.12345")   # 版本号 v2 被去掉
        self.assertEqual(p["title"], "Deep Learning for Vision")
        self.assertEqual(p["year"], "2021")
        self.assertEqual(p["primary_category"], "cs.CV")

十一、心路历程、收获

说实话,一开始我对「人机结对编程」的理解很肤浅——以为就是把需求丢给 AI、它写完我交上去。真正做起来才发现完全不是这么回事。

第一次被触动,是趋势图出问题的时候。AI 给的代码能跑、也能出图,但「逐年发文量」的柱状图每个年份都一样高——因为每年均匀采样 120 篇,画出来是一条「平线」,根本体现不了「热度走势」。那一刻我才意识到:AI 会自信地给你一个「能跑」的答案,但「能跑」不等于「对」。如果不是我自己盯着结果、觉得「这不对吧」,这个缺陷就会一路带进作业。

从那以后我养成了两个习惯:一是 AI 生成的代码我一定逐段读一遍,能用大白话讲出每段在干嘛才放行——这也是「禁止盲抄」逼我做的;二是先跑小规模、再放大,还给核心逻辑写了单元测试,结果测试真帮我抓出了 bigram 合并被误过滤的 bug。

最大的收获不是这个工具本身,而是理解了结对编程里「人」的位置:AI 擅长快速产出、解释、甚至诚实地承认错误,但它不会替你判断「结果合不合理、方向对不对」。判断力,仍然只能由人来提供。 另外,通过逐段读代码,我也真的学会了 arXiv API 的查询方式、关键词共现网络怎么建、matplotlib 动画的原理——这些本来会是我最想跳过、却最有价值的部分。


十二、对 AI 结对伙伴的评价

  • 优点:
    • 响应快、产出稳定,代码带中文注释和 docstring,读起来省心;
    • 会主动澄清需求(比如动手前先确认选题和数据源),而不是闷头乱写;
    • 诚实——被我发现缺陷时大方承认、给出修复方案,不狡辩。
  • 不足:
    • 初版代码常漏掉「边缘情况」的容错(网络重试、终端编码这些第一遍往往想不到);
    • 容易把「能跑」当成「正确」,比如拿采样数当真实趋势,需要人来质疑;
    • 有时给结论太快、验证太少,显得「过度自信」。
  • 总体:
    一个高效、坦诚、但需要人来监督的结对伙伴。它把我从繁琐的编码里解放出来,让我把时间留给更重要的 review 和测试;但它的输出永远只是「草稿」,最终判断和把关必须由我来做。一句话:它是很好的副驾驶,但方向盘得握在自己手里。

附录

PSP 表格

时间单位:分钟。(以下为回顾性估计,非逐分钟精确记录)

PSP2.1Personal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划
· Estimate 估计这个任务需要多少时间6040
Development开发
· Analysis 需求分析(包括学习新技术)120150
· Design Spec 生成设计文档9060
· Design Review 设计复审6045
· Coding Standard 代码规范3020
· Design 具体设计120100
· Coding 具体编码360420
· Code Review 代码复审90150
· Test 测试(自我测试,修改代码,提交修改)150240
Reporting报告
· Test Report 测试报告6040
· Size Measurement 计算工作量3020
· Postmortem & Process Improvement Plan 事后总结,并提出过程改进计划6090
合计12301375

从 PSP 表格能看出,我最初对时间的预估明显偏乐观,实际耗时超出预估约 2.4 小时,主要差在三处:

  1. 「编码」实际比预估多、但并没有失控:因为有 AI 结对伙伴生成代码,真正的「敲代码」时间其实不长;多出来的时间主要花在逐段读懂 AI 生成的代码上——这是「禁止盲抄」要求的硬成本。
  2. 「代码复审」和「测试」严重超出预估(复审 +60 分钟、测试 +90 分钟):这是我最没料到的地方。一开始我以为「AI 生成的代码 + 跑一遍」就够了,实际却接连发现 4 个缺陷(网络超时、趋势平线、终端乱码、bigram 合并),每个都要复现、定位、验证修复。这让我意识到:把时间留给「验证」比留给「产出」更值钱。
  3. 「需求分析」也超出预估(+30 分钟):学 arXiv API 的查询语法、理清「采样数 vs 真实发文量」的区别,花的时间比我预想的多,但这一步没省,后面才少走了弯路。

如果重来一次,我会在计划阶段就把「代码复审 + 测试」的预估调高一倍,并预留一整块时间专门做「怀疑 AI 输出」的验证——因为结对编程里,人的价值恰恰不在打字,而在挑错。

华为云代码仓库链接

https://devcloud.cn-north-4.huaweicloud.com/codehub/project/3fbf8dadc8f14a829da1297170c4aca8/codehub/3090578/home

墨刀原型设计网页链接

https://modao.cc/ai/share/6ab51dbc75d0275ef6fda70b

代码规范(在华为云代码仓库)

img

代码commit截图

img

img

img

...全文
32 回复 打赏 收藏 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

88

社区成员

发帖
与我相关
我的任务
社区描述
计算机-软件工程
软件工程 高校 福建省·福州市
社区管理员
  • FZU_SE_LQF
  • *奈落*
  • 助教李烨
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧