102400307许雅欣——第二次作业

102400307许雅欣 2026-09-24 23:59:25
这个作业属于哪个课程2601_FZU_SE社区-CSDN社区云
这个作业要求在哪里软件工程实践第二次作业——与AI结对编程(顶会热词统计)-CSDN社区
这个作业的目标做一个能看 CVPR、ICCV、ECCV 近几年在研究什么的网站:论文能进库,前十、关系图和多年走势都能点
其他参考文献邹欣《构建之法》第3章、第4章、第8章;数据来源见 data/public/SOURCES.md

目录

项目交付、git 仓库链接、代码规范链接和 AI 工具说明

还要一起看的

交出去的是四样:能点的原型、五个功能的网站、这篇博客、学号命名的仓库。成品首页和原型播放页在这张表的前两行。克隆地址打不开论文和走势。

还要一起看的在哪
成品网站http://159.75.211.145:8080/#/hot。浏览器里要能看见论文、前十和走势
原型网站不用登录。从热门方向点进去,走势、论文、导入、说明都能跟着走。https://www.figma.com/proto/KAm85sJhaB1sxRUTYcPteR/%E9%A1%B6%E4%BC%9A%E7%83%AD%E8%AF%8D%E5%8E%9F%E5%9E%8B%E8%AE%BE%E8%AE%A1?page-id=0%3A1&starting-point-node-id=3%3A8&scaling=min-zoom&content-scaling=fixed&node-id=3-7&show-proto-sidebar=1
代码仓库,名字就是学号 102400307https://codehub-cn-south-1.devcloud.huaweicloud.com/0d6f3fb7704b4025b9c9388b55cc0980/102400307.git
代码规范仓库根目录 codestyle.md。Python 跟 PEP 8,Vue 跟 官方 Style Guide
写草稿的助手DeepSeek Hy4 preview。场景、表和页面是 DeepSeek 起的稿,五个功能怎么切、三套数字怎么算,是我定的
论文元数据data/public/SOURCES.md,主路是 Hugging Face 上的公开会议数据
课程页、作业要求文首第一张表,两条链接还空着

仓库链接、代码规范链接和 AI 工具说明

代码在华为云 CodeArts,仓库名是学号 102400307。下面这条是克隆地址,浏览器打开它看不到论文和走势。

https://codehub-cn-south-1.devcloud.huaweicloud.com/0d6f3fb7704b4025b9c9388b55cc0980/102400307.git

规范在仓库根目录 codestyle.md,没有另起一套。Python 跟 PEP 8,Vue 跟 官方 Style Guide。我额外钉了三条,是怕统计写乱:入库只走 PaperRepo.create,接口都是 code、message、data,数据库和密钥不进 Git。

论文从哪来写在 data/public/SOURCES.md,主路是 Hugging Face 上的公开会议数据。课程页和作业要求还空着,在文首第一张表。学号在仓库名里。README.md 的中文乱成问号了,发布前要重写。

过程和规范都在仓库根目录,和代码同一处:

写的是什么文件
需求NABCD.md
时间PSP.md
信息架构原型信息架构.md
原型细化原型设计细化.md
原型发布原型设计结果.md
实现设计阶段二-实现设计.md
编码步骤阶段二-编码协作.md
骨架与入库阶段二-编码-01-骨架与入库.md
论文列表阶段二-编码-02-论文列表.md
导入接口阶段二-编码-03-导入接口.md
热门方向阶段二-编码-04-热门方向.md
关键词图谱阶段二-编码-05-关键词图谱.md
热度走势阶段二-编码-06-热度走势.md
前端五页阶段二-编码-07-前端五页.md
查无则抓阶段二-编码-08-查无则抓.md
代码规范codestyle.md

AI 工具说明

只用 DeepSeek Hy4 preview。没有第二个助手。

用法就一条。我先写不许做什么,DeepSeek 出草稿,我对照五个功能留下或划掉,划掉的写进下一轮提示。五个功能的边界,以及前十用 TF-IDF、图谱用篇内共现、走势用按年篇数,这三套数字定了就不再交回去改。不把范围写进提示时,阅读清单和以后再做图谱会自己长出来。

flowchart LR
  bound[我先写不许做什么] --> draft[DeepSeek 出草稿]
  draft --> check[我对照五个功能]
  check --> keep[留下]
  check --> cut[划掉]
  cut --> bound

需求、原型、每一个功能里 DeepSeek 起草了什么、我留下什么、划掉什么,整张表在后面的结对一节。这里不先铺一遍。前端五页的取舍也在那张表里,原文在 阶段二-编码-07-前端五页.md。

PSP 表格

预估、实际、偏差原因

开工前我把整件事拆进 PSP.md。表上的合计写成 2210 分钟,大约 37 小时。那一格把「计划」和「估计」各加了 40,其实是同一件拆表的事。下面合计只加一次计划,预估是 2170 分钟。

实际按 9 月 20 日建仓库、22 日定需求、23 日画原型并起骨架、24 日把五个功能和这篇博客收尾来记,大约 35 小时。总账差不多。省下来的和超时的不在同一行。

阶段预估(分钟)实际(分钟)相差
计划4025少 15
估计这个任务要多久4025和计划是同一件事,合计不加
需求分析,含学新技术360260少 100
设计文档120150多 30
设计复审4080多 40
代码规范5020少 30
具体设计280340多 60
编码900680少 220
代码复审90160多 70
测试150100少 50
测试报告4090多 50
计算工作量2010少 10
事后总结80200多 120
合计21702115少 55

省在起稿。DeepSeek 先把六个场景、表和五页骨架写出来,我不用对着空白从零列。学 Flask 和 Vue 也没有单独坐满预估里的两小时,是边写边问,时间进了编码。代码规范是 DeepSeek 按 PEP 8 和 Vue 官方指南收成一份,我只加了三条:入库只走一个函数,接口都是 code、message、data,数据库和密钥不进 Git。测试里不少 pytest 是 DeepSeek 起草、我改断言,所以测试行比 150 分钟短。提交次数和 tag 用 git 就能数,工作量那一行也就十几分钟。

超时花在把 DeepSeek 的草稿拉回来。需求第 1 轮人写对了,功能写多了,阅读清单出现了,图谱和动图被放到以后。复审就不能只花 40 分钟,要把这两块捞回基础功能,再改一版问法。原型按墨刀估了 280 分钟,底图定了以后改到 Figma 盖热区,具体设计多出来的就是换工具;热区怎么连是 DeepSeek 列的,我对着按钮画,所以没有把五个页面重描一遍。编码表面少了 220 分钟,是因为骨架不用手打。停用词把 Deep Learning 切没、更新用 or 把空年份当成没填、大库统计要等几十秒,这些进了代码复审,那一行从 90 加到 160。每步协作记录和这篇博客比「事后总结 80 分钟」长,DeepSeek 写的摘要不能直接交,我要留下采纳了什么、划掉了什么。编码这 680 分钟是本机五个功能加上后来的浏览和演变。华为云能打开的首页还空着,上线后再点的那一遍还没记进来。

提交、分支、release

Git 这边,当前分支 108 次提交,远程 dev 109 次,main 还停在阶段一,只有 7 次。tag 有 v1.0.0 和 v1.1.1。提交说明写功能,不写工具名。Issue 和 PR 的网页编号我还没抄下来。数据库、依赖和生成的大 CSV 都在 .gitignore 里。

NABCD 需求分析

读完《构建之法》第 3 章和第 8 章,我按五个字母把平台定下来。作业让做法和对比写长,需求和好处、怎么交出去也不能空着。定稿在 NABCD.md 前半,下面就是那一版。后半是四轮对话和截图,这里不重贴,结对那一节只留把范围拉回来的那一次。

需求是痛点,不是功能清单。做法要做得成。好处是用户愿不愿意离开现在的工具。竞争里还有一种对手:用户干脆不做统计。交付是原型、网站和仓库交到别人手里,还要能指出哪三条场景走得通。

动手前我已经钉死、后面没再让 DeepSeek 推翻的有这些:五个功能都做,图谱和动图不能放到以后;Flask、Vue 3、SQLite、ECharts,部署华为云;批量以课程或公开数据为主,单篇才用 DBLP;前十用 TF-IDF 和横条,不用词云当主图;走势只数某年有多少篇,不把权重画进去;transformer、NeRF、diffusion 保持论文里的写法,不合并。了解更多、年度热词演变是附加。关于页必须有,了解更多写在关于页里。

需求

CV 三大顶会一年录取就能到几千篇。刚入门的人靠逐篇阅读摸热点几乎做不到。翻官网、用学术搜索、自己用表格数,都慢,换一批人或换一年,热门也对不上。

我把需求收成一句话:先维护一份三个顶会的论文库,再在这份库上给出热词、图谱和走势。库里有什么,统计就认什么。这不是再做一个论文检索网站。

三类用户是我定的:刚入门的本科生、实验室研究生、导师和助教。每类两条。谁、在什么情况、痛点、希望平台怎样,这个格式用了 DeepSeek 的建议。希望平台怎样只许落到五个功能和两项附加。

场景谁,卡在哪希望平台怎样
1 前沿调研,还没选方向学过机器学习和数字图像处理,还没进组。网上多是二手转述,官网标题里的缩写看不懂。分不清哪个词在变热、哪个已经过时,论文散在官网、DBLP 和下载站首页看前十,点进图谱,再点关键词列出论文。走势页看这个词近几年出现得勤不勤。要读某篇时回列表查摘要和原文链接。关于页可以写进报告
2 读指定的一篇 CVPR,术语卡住方法名一个接一个。每次只查到一个孤立的词,看不出和谁经常一起出现,也不知道近几年还有没有人做,自己不会写爬虫入口是列表:题目精确查,或编号、关键词模糊查。DeepSeek 写成在图谱上搜词,我改掉了,图谱只负责看关系。在不在前十都可以看图谱和走势。库里没有就按这句话抓取并写入,下次走库
3 开题,方向还值不值得做导师给了大致方向,自己到会议列表里数、往表格里贴,换个词就重做。缺的是趋势,不是当年有多少篇。机构名不统一只当痛点,平台不做合并走势页把多个词、多个年份、三个会画在一张动图上,纵轴是按年篇数。回首页看在不在前十,用图谱找邻近词。缺数据去导入。附加看往年谁新进、谁掉出去
4 写综述,文献来源杂PDF、自己下的、别人导出的混在一起,写到一半又多一篇。补字段耗时间,还容易重复。计算方法对不上,热度图不敢放进论文先批量导入,单篇再补。列表上改错、删重复。库里没有就按查询抓。整理完在这批数据上出前十。计算方法写进脚注
5 给定方向,给新生出题主要靠读论文的印象,印象会偏,会守着熟悉的方向。三个会几千篇没人愿意数一遍从热词点到图谱再点到论文。走势看跨年和三个会的差别。定了方向后列出带原文链接的论文。组里用同一个网址
6 维护课程论文库每届重新统计,文件留在自己电脑里,学生报告里的热词各说各的。DeepSeek 写过课程名单和防撞题,我删了导入课程数据集,缺的按查询去抓。本届前十当题目和评分参照。学生点关键词读论文,再做多年、三个会的对比
场景主要用到页面怎么走
1 本科生做调研前十、图谱、走势、列表首页到走势,需要时去说明页
2 本科生卡术语列表是入口,再用图谱和走势列表到首页,再到走势
3 研究生开题走势为主,加上前十、图谱、导入走势到首页,缺数据去导入
4 研究生写综述导入、列表、前十导入到列表,再到首页
5 导师定方向前十、图谱、走势、列表首页到走势
6 助教维护课程库五个功能都用导入到列表,到首页,再到走势

相对第一稿,我删掉的是:词云当主图、热词白话解释、引用量排序、机构分布、BibTeX 和 DOI 自动补全、个人阅读档案、课程名单、选题撞题、按学期做快照,以及把图谱和动图放到以后。导入页上后来的粘贴只是把已有材料喂进库,不是这里划掉的自动补全。

做法

页面上怎么点,DeepSeek 帮我写细了。算法和边界是我定的。先写清要分开算的三件事,再对齐到五个功能。

库如果不先固定,换一个人、换一年,热门就对不上。所以第一件事不是画图,是有一份能改、能删、能再导入的三会论文库。图都从这张库里长出来。

前十要回答的是:在当前这批论文里,哪个词更能代表方向,而不是哪句话每篇都出现。标题、摘要、关键词放在一起,小写、去掉 using、method 这种停用词。transformer、NeRF、diffusion 保持论文里的写法,不做词干合并。权重是这批论文里的 ∑(1+log tf)×log(N/df)。一个词出现的文档越多,log(N/df) 越小,泛词会被压下去。首页用横条,能数出 1 到 10。换会议或年份,就按新的这一批重算。DeepSeek 建议过词云当主图,我没留。词云看不出名次。

走势要回答的是另一件事:这个词在某一年、某一个会上,有多少篇提到。纵轴只用篇数。DeepSeek 写成篇数或权重都可以,我改成只留篇数。两套数画在一条线上,上升到底是公式变了还是录用变了,看不出来。少于两个词没有对比,不画线。ICCV 奇数年才开,ECCV 偶数年才开,某年是 0 是没开会,不是这个方向凉了。

已经知道题名的人,不该先去点云图。列表先查库:题目精确,编号或关键词模糊。库里没有,就把这句话拿去联网,摘要、关键词、原文链接同一趟写回,页面上用绿条说明这次是补录。再搜走库。导入是另一条路:手里已经有课程数据或公开数据,批量或单篇喂进去。两条路都进同一张库,不合成一页。机构名合并、DOI 自动补全,这次不做。

榜上是十个分开的词,仍然不知道该读哪篇。所以图谱的点是这十个词,同一篇里一起出现就连一条边,次数多线就粗。必做是点节点,列出题名、会议、年份、摘要和原文链接。DeepSeek 还建议点边、只看两个词同时出现的论文,我标成可选。

功能上面要解决的事页面上怎么做
1 导入没有统一的库,后面的图都画不出来导入页批量上传,或单篇先查库、没有再走 DBLP。写入摘要、关键词、原文链接,返回成功条数和失败原因
2 列表已知题名却被带到图上;库里没有,这个词进不了榜精确或模糊先查库。没有就抓,同一次把结果送回并入库,绿条提示补录。同一页改错、删重复。再搜走库
3 前十泛词每篇都有,只数篇数会把 method 送上榜当前筛选下的论文算 TF-IDF,横条取前十。悬停能看到权重和大约多少篇。换筛选就重算
4 图谱词分开了,不知道该读哪篇节点是关键词,篇内共现连边。点节点列出论文和原文链接。点边不是必做
5 走势看方向在涨还是已经见顶,三个会是否一样热多个词、年份,勾上 CVPR、ICCV、ECCV。三条线按年播放,能暂停并读出那一年各自的篇数。可以从某一年回到列表

两个附加不进这五格。说明页写三个会是什么,以及前十和走势各自怎么算、数据到哪一年。年度热词演变把每年的前十排在一起,看谁新进、谁掉出去、名次怎么变。

好处

DeepSeek 按场景把五个功能又写了一遍。我压成对谁有用,没有把引用量和个人文献库写成卖点。

谁离开现在的工具之后得到什么
本科生做调研首页就有三个会上的前十,点进图谱落到论文,走势用来避开已经过时的方向
本科生卡术语从列表搜,不在前十里也能看图谱和走势。查不到由平台去抓
研究生开题看谁在涨、谁已经见顶。前十和图谱用来看这个方向站在哪、和谁交叉
研究生写综述导入和增删改少填字段。前十是在自己这批数据上算的,图能说清数字从哪来
导师和助教定完方向能拉出带链接的论文。各届在同一份库、同一个网址上出前十
这门课库建一次还能改。两套算法分开写在说明页。不用每换一个词就重爬、重画

本科生做调研时,首页就能拿到三个会上的前十,点进图谱落到具体论文,走势页用来避开已经过时的方向。列表能查,查不到由平台去抓。读论文卡住时从列表搜术语,不在前十里也能看图谱和走势。

研究生开题主要看谁在涨、谁已经见顶。前十和图谱用来看这个方向站在哪、和谁交叉。写综述时用导入和增删改少填字段。前十是在自己这批数据上算的,图放进论文能说清数字从哪来。

导师定方向可以看图,定完能拉出带链接的论文给新生。助教导入课程数据后,各届在同一份库上出前十。同一个网址,组里看到同一批数据。

库建一次还能改、还能用。前十和走势的算法分开写在说明页上。榜上的词和库里的词对得上。不必每换一个关键词就重爬、重画。

竞争

对比对象是我选的:Google Scholar、Semantic Scholar,会议官网和 DBLP,Connected Papers、ResearchRabbit,自己用 Excel 或 Python,还有公众号、博客上的年度盘点。DeepSeek 起草了表。我删掉了用引用次数和机构排行去比的句子,数据里不一定有这些字段,这次也不做。

书里还写了一种对手:用户干脆不做统计。翻两篇官网就交作业,或者组里每人一份表格,热门永远对不齐。所以交付不能只停在本机脚本。

现成工具能做什么对这三个会的热词帮不上什么这里怎么做
Google Scholar、Semantic Scholar检索论文,提供摘要、链接和相关推荐结果是一长串命中,看不出该词在三个会上现在热不热、近几年怎么变。里面还混着预印本和其他会议功能 3 给出三个会、可选年份的前十。功能 5 把多年对比放在同一张动图上
会议官网、DBLP 列表数据全,可按年、按会浏览仍是列表。热词要自己导出再算,换一批人或换一年,算法容易对不上功能 1 先建成自己的库。功能 3、4、5 直接给出统计,不把计算丢回给读者
Connected Papers、ResearchRabbit按引用关系画论文关系图节点是论文,不是关键词。没有三个会的热度榜,也没有按年走势功能 4 是关键词共现,点词看到相关论文和原文链接。功能 3、5 补热度和时间。功能 2 搜不到会抓取入库
自己用 Excel 或 Python计算方法完全自己定要自己爬、洗、画。换词、换年就重做。结果在本机,导师、助教和学生各看各的页面上改筛选就重算。部署到华为云后,组里打开同一个地址
公众号或博客的年度热词盘点读着轻松,适合先建立印象说不清数据从哪来、怎么算、截止到哪一天。想接着读原文,还要自己再搜图都从自己的库里算。从榜点到图谱,再点到原文。说明页写清口径和年份

这五条是现成工具不容易一起给到的:

留在这个平台上的现成工具里缺的那一块
只在 CVPR、ICCV、ECCV 上算的前十,用横条,不是搜出一堆论文Scholar 给的是命中列表,盘点文章给的是结论,都不是可筛选的榜
关键词共现图,点一个词就能看到相关论文和原文链接论文关系图的节点是论文,点开仍不知道该词和谁一起出现
多年、三个会画在同一张动图上,能暂停,暂停时读得出那一年各自的篇数官网和 DBLP 是按年的名单,没有这张可暂停的对比
库里没有就按查询抓取,写入后再查还在,并带摘要、关键词、原文链接搜不到就结束的检索,不会把这篇补进后面的榜和图谱
能批量导入,能改能删,云上同一个地址,前十和走势的算法分开写清私人脚本换一台电脑就没了,盘点文章不能改库

交付

交付按作业写,不是另拟一套范围:能点的原型网页、五个功能的实现、华为云上能打开的地址、学号命名的仓库。仓库在 dev 上开发,基本功能完成后有 v1.0.0。数据以课程或公开数据集为主。DBLP 只补单篇,以及列表里搜不到的时候。爬取只用于这门课。

做完以后怎么说明它能用,我从已经定的场景里挑了三条。细的截图放在成品那一节,以项目交付那张表里的网站地址为准。

  1. 场景 1:首页前十,进图谱,进论文,原文链接能打开。换年份或会议,条形图会重算。
  2. 场景 2:列表里搜一条库里本来没有的话,出现本次补录,回首页图上有变化。同一句话再搜走库,不再抓。
  3. 场景 3 和场景 5:走势上至少两个词,三个会都勾上,能播、能停。停下能读出那一年三个会各自的篇数。

能点的原型和项目交付那张表是同一条,原型那一节开头再放一次。后面的动图是按它的分镜接出来的。

https://www.figma.com/proto/KAm85sJhaB1sxRUTYcPteR/%E9%A1%B6%E4%BC%9A%E7%83%AD%E8%AF%8D%E5%8E%9F%E5%9E%8B%E8%AE%BE%E8%AE%A1?page-id=0%3A1&starting-point-node-id=3%3A8&scaling=min-zoom&content-scaling=fixed&node-id=3-7&show-proto-sidebar=1

原型设计与原型链接

正式能点的是 Figma,不用登录。和项目交付那张表是同一条:

https://www.figma.com/proto/KAm85sJhaB1sxRUTYcPteR/%E9%A1%B6%E4%BC%9A%E7%83%AD%E8%AF%8D%E5%8E%9F%E5%9E%8B%E8%AE%BE%E8%AE%A1?page-id=0%3A1&starting-point-node-id=3%3A8&scaling=min-zoom&content-scaling=fixed&node-id=3-7&show-proto-sidebar=1

三份文档都还在:原型信息架构.md 写为什么这么分,原型设计细化.md 写每一页怎么排,原型设计结果.md 写 Figma 上点到哪。下面按这个顺序把三份都写进博客。画板和热区是我盖的。网站代码不是从原型导出来的。

信息架构:为什么分成这几页

NABCD 定稿之后先分页面,当时按墨刀来写。五个功能不能挤在一页上,也不能做成又一个检索站。首页要同时看见前十和词和词的关系,走势单独一页是因为纵轴是另一套数,列表负责改库和查无则抓,导入负责把已有材料喂进来,说明页把算法写给人看。详情不占顶栏,从题名点进去。

顶栏五个名字一开始写成首页、热度走势、论文列表、论文导入、关于。细化时改成热门方向、热度走势、论文、导入、说明,功能没变,只是和页面上的话对齐。当前页高亮。右上角小字:数据来自当前库,统计随库变化。后来细化改成直接显示库内篇数。

筛选条只放在热门方向和走势。会议是 CVPR、ICCV、ECCV,可多选,默认三个都勾。年份是起止,默认近若干年,细化定成 2022 至 2025。点更新统计之后,当前页的图按新范围重算。这两页之间沿用已经应用的会议和年份。点进论文、导入、说明,不带这套筛选,否则人会以为列表也被同一组勾限制住了。列表只用自己的查询框。从图表点进列表,才出现一条只读的附带条件,那是这一次跳转带上的词、年、会,不是全局筛选。

走势如果年份只选了一年,提示需要多年区间,不播放。少于两个词也不画对比线。空库时热门方向和走势都给出去导入,本页不写库。

flowchart TD
  open[打开热门方向] --> see[看见前十和词云,论文区还空着]
  see --> word[点一个词]
  word --> papers[论文仍出现在这一页]
  papers --> title[点题名]
  title --> detail[详情]
  see --> trend[顶栏去热度走势]
  trend --> year[读某一年]
  year --> list[论文列表带上只读条件]
  list --> miss{库里有没有这句话}
  miss -->|没有| green[顶部绿条,写入后再显示]
  miss -->|有| table[直接列出]
页为什么单独成页这一页不许做的事
热门方向前十和共现回答的是「现在热什么、和谁一起出现」不写库,不做搜词框。点词先在本页出论文
热度走势纵轴是篇数,和权重不是同一件事少于两个词不画线。本页不补库
论文已知题名的人从这里进,查不到要能补进库批量导入不放这里。补录不用弹窗
导入手里已经有文件或一篇题名不做查无则抓。那是列表的事
说明算法和三个会要能被引用只读,没有空库态
详情读完一篇再决定要不要打开原文不占顶栏。原文链接后来改成弹窗,播放时不离开原型

DeepSeek 写过点图谱节点就跳进列表。我没留。图谱要是变成另一张检索表,功能 4 就没了。进列表只有三条路:点题名,从走势带词、年、会进去,从演变带词和年进去。演变不带会议,因为那一行是这一年的榜,不是某一个会的折线。

设计细化:画面上怎么排,为什么改

算法没改。前十仍是 TF-IDF,走势仍是按年篇数。改的是人怎么读这些数。

信息架构里前十是单独一条大横条。画出来之后,一条长条读不出第 1 名到第 10 名,也分不清三个会各占多少。细化改成十行表:方向名、一句白话、蓝色权重条、绿色约篇数条、较上年、三会分布。旁注写明横轴是权重,不是篇数。词云和共现合成一块,圆的大小是权重,细弧线是同一篇里一起出现。点一行或点一个圆,该词描边,其余变淡,右侧写这个方向在做什么、常和谁一起出现,下方列出题名、会议、年份、摘要。点两个词之间的边只列同时出现的论文,标成可选。首页改成上下长页,不再左右硬挤。

走势页上,折线区不再单独放一个播放键。鼠标在图上左右移动,或拖滑块,竖线停在某年,下面三张卡读出当年三个会各多少篇。只选一个词时用黄条,不画对比图。作业要求的动图放在页面下方的历年前十:按年播放、暂停,一次只看一年。播放用分年画板加延时,不是真的物理动画。

论文页的检索和添加在同一行。结果用文字卡片:会议色标、年份、题名、两行摘要、关键词,不加封面图。查无则抓是表上方的绿条。同一句再查改黄条,表示不再补录。添加打开空表,手填,不查库、不走 DBLP。

说明页改名之后,最上放三张会卡,这是了解更多。接着用四句人话对应五个功能,不写功能编号。计算方法分成左右两格,写明两套数不能比大小。最后写数据从哪来,以及 ICCV 只在奇数年、ECCV 只在偶数年,某年落到 0 不是这个方向凉了。

信息架构里的样子细化之后为什么改
导航叫首页、论文列表、关于热门方向、论文、说明和页面上的话一致,五页功能不变
按钮叫应用筛选更新统计语气和「库会变」一致
前十是一条大横条十行表加迷你条要能数出名次,还要看见三个会
共现是单独一块关系图和词云画在一起两块都是「词和词」,再拆会重复
原文链接新开一页弹窗里给地址播放原型时不离开这一站
浅灰底加稳蓝深色顶栏、白卡片、三会三种颜色三个会要能靠颜色认出来
补录用过弹窗的草稿绿条、再查变黄条不要挡住表格

配色留浅底白卡片。顶栏 #161b22,主按钮 #0969da。CVPR 绿、ICCV 蓝、ECCV 琥珀只给会议标签、走势线和说明页的会卡,不再加第五种颜色。删除用描边,不做成大红主按钮。页脚三列:站点一句话、五个导航、两套数字不要混比。底行写数据来自当前论文库,只用于课程。顶栏下面不再画一条蓝绿红装饰线。

专用原型工具,以及和 AI 怎么协作

设计结果:在 Figma 里点到哪

连线时我改到 Figma。各页长什么样已经在细化里定了,我按那些画面做成画板底图,锁定之后盖透明热区,比在墨刀里重描一遍快。DeepSeek 列过热区可以连到哪,位置是我对着按钮、表格行和题名画的。每个弹窗的关闭单独对,没有共用一套坐标。演变按年播放是画板载入后大约 1.1 秒跳到下一张。暂停落在 2025 那张,那张不加延迟,不然停不住。词云选中、气泡挤开、竖线跟年,都是换一张底图,不是真的动画引擎。发布之后不用登录就能点。换的是连线工具,五个功能和文案没换。

下面五段是分镜接成的动图,鼠标没有画进去。每一段先写清画面上有什么、点的是哪一个控件、点完变成什么,再放图。运行中的网站在下一节,两套画面不要看成同一张。

热门方向。一开始没点任何词:权重前十的十行和词云都在,相关论文那一块是空的,空状态写着要点上面的热词。第一下点的是前十第一行 diffusion,或词云里最大的那个圆。画面换成选中:diffusion 描边,其余变淡,论文还不一定列出。第二下点的是第二行 transformer。词云改围着 transformer,下方出现该词的论文,有题名、会议、年份、摘要。最后把 CVPR、ICCV、ECCV 三个勾都去掉,再点更新统计。数卡、榜和词云隐去,出现恢复默认范围、去导入论文。去导入连到导入页的批量。

热门方向的点击

热度走势。从顶栏点热度走势进来,或在热门方向已选词时点看近几年还热不热。第一张是 2025:竖线在最右,下面三张卡是当年三个会的篇数。接着点折线最左一段,或把滑块拖到最左,切到 2022,竖线和三张卡一起变。再点滑块偏左到 2023,偏右到 2024。然后点掉 transformer 那个胶囊,只剩一个词,出现黄条:至少再选一个,折线不画。再点回 transformer,回到两个词的 2025。最后点页面下方的按年播放,从 2022 开始大约每 1.1 秒翻到 2023、2024、2025。暂停停在 2025 那张。

走势按年切换

论文列表。顶栏点论文,或从详情点返回,进入默认表。检索框和添加在同一行。先输入一句库里没有的话,点检索。整页顶部换成绿条,写的是已按这句话补录并入库,表格里出现这条,不是弹出一个窗口。同一句再点检索,绿条换成黄条,篇数不增加。再点添加论文,弹出空表,字段是手填的,点取消或遮罩回到列表。点某一行的编辑,弹出已经填好的表,保存或取消都回列表。点删除,弹出确认,确认或取消都回列表。若是从走势点了查看当年论文进来,列表上方有一条只读条件,例如词、年、会,点取消限定之后条件消失,回到默认表。

论文列表上的操作

导入。顶栏点导入,默认是批量页签:说明每条至少要有题名、摘要、关键词、原文链接,中间是一块虚线区域。点开始导入,弹出导入完成,上面是成功篇数和失败篇数。查看论文会去列表,回热门方向会到未选词的首页,点遮罩关掉这个窗口。再点单篇补录页签,题名是必填。点查找并写入,弹出写入成功。查看该论文进详情。这一页没有绿条。绿条只在论文列表。

导入

详情和说明。在热门方向已经列出的论文里,点第一篇题名,进入详情:大标题、会议色标、年份、编号、摘要、关键词。点返回论文列表,回到默认表。点原文链接,弹出地址,点关闭或遮罩回到详情,原型里不打开外站。详情上的编辑、删除和列表是同一套弹窗。顶栏点说明:最上三张卡片是 CVPR、ICCV、ECCV,下面左右两格是两套算法,再下面是数据从哪来。这一页没有按钮会改库。

详情和原文链接

成品展示

十张以上,每张写清画面里有什么

成品网站:http://159.75.211.145:8080/#/hot。浏览器里要能看见论文、前十和走势。克隆地址不算这一条。

打开网站,顶栏从左到右是热门方向、热度走势、论文、浏览、导入、说明。左上角是「顶会热词」,下面一行小字是 CVPR、ICCV、ECCV。右边是库里现在有多少篇。第一次进来停在热门方向。地址用 hash,刷新不会丢页。下面按这个顺序看。顶栏里的浏览,以及说明页上的三个会、走势页下方的历年前十,写在目录最后的 附加。和原型不一致的地方,走到那一页再写。

热门方向,还没点词。画面上有筛选条,三个会默认勾着,年份是 2020 到 2026,按钮是更新统计。下面是库内总数、当前范围、关键词数。再下面是十行前十,每行一条权重条和大约多少篇。再往下是一块「关键词工作台」:左边是词列表,中间写这个词经常和谁一起出现,右边是词云图。最底下「相关论文」还是空的,写着先选一个关键词。鼠标只是停着,没有点。这一段证明榜来自当前库,论文要等你点词。

热门方向,还没点词

热门方向回答两件事:当前这批论文里哪十个词更能代表方向,这些词经常和谁写在同一篇里。点更新统计会先出现「正在统计…」。库大的时候要等一会儿,等的是按当前会议和年份重算,不是页面卡死。三个勾都去掉再更新,榜会空,和原型里隐去榜、出现去导入是同一件事。筛选只在这一页和走势页之间带着走,点进论文或导入不会把这三个勾带过去。

热门方向,点一个词。鼠标先停在前十里的 3d 那一行再点。地址带上这个词,下面列出相关论文,能看见题名、会议、年份、摘要。再点一篇的原文链接,新开的是论文自己的页面。这一段要看见鼠标先停在那一行上再点,证明点词不是装饰。

热门方向,点一个词

点词之后论文留在这一页,和原型一样,不会立刻跳进列表。点题名才离开。点关键词可以去浏览,这是原型上没有的入口。原型里词云和共现画在同一块;网站上前十仍是能数出 1 到 10 的表,关系画在旁边,侧栏上的邻近词也限制在这十个里,避免点进去的数字和线上的篇数不是一回事。

论文详情。这一篇是 Ultra3D,上面有 ECCV、2026、编号、摘要和关键词。鼠标点原文链接,新开的是论文自己的页面,不是站内再套一个地址弹窗。回到详情后点编辑,弹出已经填好的题名、关键词和摘要。关掉之后回到论文列表。

论文详情

详情和原型的差别在原文链接。原型为了不离开播放页,用弹窗把地址摆出来。网站上原文链接直接打开那篇论文。编辑和删除仍是弹窗,和列表同一套。

论文列表。顶栏停在论文。查询方式是综合,框里已经是 diffusion。鼠标先去掉 CVPR,再去掉 ICCV,只留 ECCV,然后点检索。列表从最近入库改成找到 8254 篇,都是 ECCV。每条有编号、会议色标、年份、题名、摘要和关键词,右边是编辑和删除。

论文列表

论文页是已知题名的入口。从走势或演变进来时,列表上方有一条只读条件,写着词、年、会,点取消就回到默认表。添加是空表,手填,不访问外面。删一篇要确认,词会跟着删,榜按剩下的库重算。

论文列表,绿条。输入一句库里没有的话,鼠标点检索。顶部先出绿条,写明这次是联网补录,摘要、关键词、原文链接出现在结果里。库内篇数增加。不要用弹窗。

论文列表绿条

论文列表,黄条。鼠标再点一次检索。绿条换成黄条,说明这句话已经抓过,篇数不增加。这一段证明不会插两次。

论文列表黄条

绿条和黄条的位置与原型一致,都在列表顶部,不挡成窗口。外面也没有时,页面明说没有,不假装补录成功。

导入,批量。顶栏停在导入,页签停在批量导入。中间是文件区,还没选文件,可以拖进去,支持 PDF、CSV、JSON、TXT、BIB,以及这些文件打成的 ZIP。下面是开始导入和下载 CSV 模板。右边写着 CSV 表头、会议只认 CVPR / ICCV / ECCV、同一题名不会重复入库。最近一次批量记录是空的,单篇补录不会出现在这里。

导入批量

导入,单篇。鼠标点单篇,填一个题名,点查找并写入。弹出写入成功。若再切到粘贴,把一条 BibTeX 贴进去写入,也进同一张库。粘贴是网站多出来的入口。

导入单篇

导入页不做查无则抓。手里已经有材料才走这里。列表上那句库里没有的话,才走绿条。两条路写进同一个库。

说明。顶栏停在说明。最上三张卡片是 CVPR、ICCV、ECCV,绿、蓝、琥珀。CVPR 每年都办,ICCV 只在奇数年,ECCV 只在偶数年。下面是按你现在的任务选页,再往下是统计口径:抽词、热门方向的 TF-IDF、走势按年篇数、图谱篇内共现。鼠标继续往下,能看见数据从哪里来,以及附加的历年 Top 10 演变和开题浏览。这一页没有会改库的按钮。

说明

说明页写给第一次看的人:ICCV 只在奇数年开,ECCV 只在偶数年开,折线掉到 0 是这年没开会,不是这个方向凉了。走势纵轴是篇数,不是权重,也不是引用。浏览页在说明里被写成附加,挂了也不影响另外五页。

热度走势。顶栏停在热度走势。统计范围是 CVPR、ICCV、ECCV,年份从 2020 到 2026。参与对比的方向还是空的,旁边写着至少选择两个。逐年收录走势只有图例和空坐标,下面三张卡都是 0。鼠标在筛选附近移动,没有点播放,三条线没有画出来。

热度走势

这是功能 5 要的动,必须是运行中的网站,不能用上一节的原型翻页代替。原型靠换四张底图表示年份,网站是同一张图上的线在画,播放时在已经拿到的点上移动年份。接口一次把全部年份和三个会都返回,没有届次的年份是 0。

浏览。顶栏点浏览。输入一个方向,停大约 0.2 秒,下面出现建议。点一个建议,能看见简介和相关论文。没有匹配时是中文,不是英文报错。这一页是原型没有的。

【动图】

浏览用来开题时把方向收成一页。它不替代绿条,不替代前十的算法,也不替代走势那条正在画的线。

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

作业这一节要看见的不是最后一版,而是来回:关键提示、DeepSeek 回了什么、我为什么留下、为什么划掉、改的时候动了哪一句。同一件事要有第二轮,不能只贴终稿。下面按作业来排。先写协作怎么进行。再把每一份记录里的轮次摊开,这是提示迭代。然后单写调试,也就是 DeepSeek 写错、我指出来并改掉的地方。最后是案例。作业点名三则:需求与原型、编码、调试。编码里来回长的步骤各自再写一则,所以不止三则。原文和截图在对应 md,这里把判断写全。

人机协作怎么进行

工具是 DeepSeek,版本 Hy4 preview。每一处都是同一套,不是一次生成交上去。

我先把已经钉死的边界写进提示:五个功能都做,图谱和动图不能放到以后;Flask、Vue 3、SQLite、ECharts,部署华为云;前十用 TF-IDF,走势用按年篇数;查无则抓在列表页,用提示条,不用弹窗。DeepSeek 出草稿。我对照作业和已经定稿的文档看,留下能对上的,划掉加戏和会算错的。下一轮提示只改划掉的那几条,不让整篇重写。本机再跑一遍,对上了才进仓库。

flowchart LR
  bound[我先写边界] --> ask[这一轮只问一件事]
  ask --> draft[DeepSeek 出草稿]
  draft --> check[我对照五个功能]
  check --> keep[留下]
  check --> cut[划掉并写原因]
  cut --> ask
  keep --> run[本机跑通再入库]

分工可以讲清。场景结构、五页怎么分、配色和按钮文案、表和接口清单、前端目录、走势折线怎么逐年露出来,这些草稿来自 DeepSeek。作者标签怎么躲开停用词、更新字段不用 or 回退、图谱侧栏只留当前前十、空库也返回三个会的线、词云不用那版图、测试在创建应用之前关掉抓取,这些是我改的。草稿对不上五个功能,或者我讲不清这行会让榜、线、绿条错在哪,就不进仓库。

Prompt 一轮轮怎么收

作业要的迭代是问法变过,结果跟着变。不是换标题重贴终稿。需求从四个字母拆开问。信息架构先出五页,再只改四条规则,最后才问配色和文案。参考图七轮,每一轮只改上一版被我指出的问题。编码则是先出一截能跑的,再拿去审查,错的改代码,口径类只写清不改。每一则都写在后面的案例里:我问什么,DeepSeek 给了什么,发生在哪一份文档,我留下什么,我划掉什么,为什么,怎么改。

调试:改掉写错的地方

调试分要看见的是:DeepSeek 的代码或结论有错,我指出来,改完再验。不是换个说法重写一遍。下面这些都进过仓库或测试,每一条都写了错在哪、我怎么改。

发生在哪DeepSeek 当时那样错在哪我怎么改怎么知道改对了
骨架,阶段二-编码-01作者关键词和摘要走同一套停用词deep、learning 把 Deep Learning 切没,导入成功但榜是空的标签整段留下,能切开的单词再计一份样例里 Vision Transformer 既能看见短语,也能用 transformer 搜到
同一处更新data.get("year") or old["year"]空字符串被当成没填,旧年份被吞掉键不在请求里才用旧值;撞上唯一键报重复把年份改成空再读,旧值还在
同一处 seed空文件、缺年份直接往下跑导入崩,不是给出失败行先查空文件和题名、会议、年份;CVPR 2024、2024-06 也能收空 CSV 不崩,返回 0
前十,阶段二-编码-04某年的文档数被当成全库文档数来算 idf较上年和洞察全空idf 用当前筛选的全集合,再乘各年的 tf各年权重加总等于总权重
前十较上年用全库年份这个词没出现的年份也参加环比只取这个词自己有权重的年份;没有上一年就空单年的词,较上年是空,表上是横线
图谱,阶段二-编码-05侧栏列出前十以外的词点了侧栏,图上没有对应的圆邻近词用当前前十的节点过滤侧栏词都能在图上找到
走势,阶段二-编码-06空库时序列是空数组有数据时永远三条线,空库变成零条,前端要写特殊分支空库也返回 CVPR、ICCV、ECCV 三条,值为 0,并给出无数据提示空库和有库的序列条数一样
五页,阶段二-编码-07三万篇库上一次把好几条接口绑在一起,没有等待页面像卡死改成按顺序请求,并写出正在统计大库上能看见等待,不是白屏
查无则抓,阶段二-编码-08缺会议时默认写成 CVPR别的会的论文被算进 CVPR会议必须是 CVPR、ICCV、ECCV 之一,否则跳过缺会议的行不进库
同一处测试测试没在创建应用前关掉抓取用例互相污染,绿条是假的HOTWORDS_CRAWL=none 写在建应用之前,不写进业务代码抓取相关测试 16 条通过

截图留在对应 md。骨架那一轮和查无则抓的审查,下面案例 C 里贴了。

案例

作业点名的三则都在这一节。A 是需求和原型,含头脑风暴、信息架构、文案。B 是编码,含入库、前十、图谱、热度动图和五页。C 是调试。每一则都写提示、回复摘要、留下、划掉、为什么、怎么改。

案例 A:需求头脑风暴

发生在 NABCD.md。我已经定了三类用户、技术栈、功能 1 到 5 都做。不确定的是每人卡在哪一步、和 Scholar、DBLP、Connected Papers 比差在哪。所以让 DeepSeek 先写场景,由我按作业收。

第 1 轮。我的提示是:固定平台、技术、数据、部署,三种角色各写 2 个场景,每条按谁、在什么情况、痛点、希望平台怎样。

DeepSeek 回了六条场景,角色也对。希望平台怎样写得很满:词云、白话解释、引用量、机构分布、BibTeX、DOI、阅读档案、课程名单。文末建议先做搜索和趋势,图谱和跨年对比以后再说。

第 1 轮我怎么问

第 1 轮 DeepSeek 多写了什么

留下:三类人、每类两条、四个要素;本科生要先看全貌,研究生要看趋势,助教要一份能共用的数据。

划掉:引用量、机构、阅读档案这些加戏。数据里不一定有这些字段。尤其划掉把功能 4、功能 5 放到以后。这两项各占基础分。

为什么这样改:不限定范围,DeepSeek 会按理想产品写。作业要的是能做完、能讲清的五个功能。

怎么改:没有在第 1 轮的稿上补句子,而是改了第 2 轮的问法。

第 2 轮。我把五个功能原文贴进去,写明前十用 TF-IDF 和条形图,数据以课程或公开数据集为主,并列出不要写进期望的内容。只改六条里的希望平台怎样,加一张场景、功能、页面表。不写做法和对比,不写代码。

DeepSeek 把六条希望收到功能 1 到 5 上,附加单独标了。

留下:这张对应表。

我自己改了三处,没有再让 DeepSeek 重写:场景 2 的入口改到列表页;清洗只当痛点,不单独做一页;导入和查不到再抓分成两页。

第 3 轮只写做法和竞争。提示写明场景已定,不要再加功能。做法按问题、功能、怎么点来写。竞争至少四个,只能用已定功能比较。

DeepSeek 把入库、查不到就抓、前十、图谱、动图分开写了,竞品表里没有再写引用量和 BibTeX。

留下这个骨架。必须我拍板的三处:动图只用按年词频;方法名不做词干合并;点边看两词同时出现的论文标成可选。不拍板,后面实现会对不上。

第 4 轮只写好处和交付,对应原型链接、实现、华为云、仓库。验收从已定场景里挑。

留下好处和三条验收。我补进定稿的两处:场景 2 仍从列表搜,不从图谱搜;关于页必做,了解更多才是附加。交付里的逐步点击改成实现后的自检,避免需求文档写成测试手册。

轮次DeepSeek 草稿我交上去的
1功能可以以后再做,并加上阅读清单五个功能都在这一版
2六条希望已经对上功能入口改到列表,导入和抓取分两页
3做法和竞品表可用动图只用篇数,词不合并
4好处和交付可用关于页必做,了解更多才是附加

案例 A:信息架构

发生在 原型信息架构.md。对应原型评分里和 AI 一起定页面、以及跳转、弹窗、查询联动、数据怎么流。五页、图谱不做搜词框、导入和列表分开,这些我已经定了。不确定的是每页怎么排、筛选会不会串到列表、列表新增和导入单篇会不会画成同一个表。

第 1 轮。提示只给已定功能和五页,按页写区块、按钮文案、空状态,再给跳转、弹窗、数据流转三张表。不许加功能,不许写代码。后来补了一句:两个附加都要画进对应页,并标附加。

DeepSeek 把五页都写了。筛选只放首页和走势;图谱无搜词框;写库只有导入和列表里的查无则抓。三张表齐全。

留下:五页划分;首页点词出论文;查无则抓放列表;两种算法分开写在关于页。

还不能定稿的原因:筛选写成切页都沿用,会把年份套到列表上;走势空状态写成至少选 1 个词,对不上两个词才有对比;列表新增和导入单篇可能画成一个表单。所以进入第 2 轮,不重写五页。

第 2 轮只改四点:走势至少两个词;筛选只在首页和走势之间沿用;列表新增手填,导入单篇才走 DBLP;附加演变允许点词进列表;补录用表内提示条,不要弹窗挡住表格。

DeepSeek 四条都改了。未选词和只选 1 个词分开写了提示。演变点某年某词只带词和年。补录改为表上方提示条。

四条全部留下,写进定稿。

我补了两处,没有再要一版:点图谱节点必须仍在首页论文面板里出论文。DeepSeek 把节点和边点进列表写进例外,这和功能 4 打架,所以没采纳。进列表只走点题名、点走势数据、点演变里的词。另一处是附加两块要画出来,不要只用灰色未实现。

第 3 轮只问布局、配色、文案。架构不许改。不要改导航五个名字。

DeepSeek 给了四色:底、白卡片、主色、强调色。首页论文面板放图谱右侧。列表把新增一篇放在查询行最右。

留下这套排法。划掉或改写的:有些句子太像套话,改成按当前库、当前筛选,用 TF-IDF 取前十。不增加第五种颜色。删除键用橙色描边,不当填充主按钮。窄屏把面板挪到下面只当备注,正式画板按宽屏。

项目DeepSeek 第一稿我定稿
筛选切页都带着走只在热门方向和走势之间
走势选词写过至少一个至少两个才画,只选一个要提示
点节点可以进列表仍在本页出论文
查无则抓可以用弹窗表上方提示条

案例 A:文案和各页参考

发生在 原型设计细化.md。信息架构已经定了规则,这一份是把每页的样子收紧,一共七轮。每一轮都是我指出上一版哪里不对,DeepSeek 改一版,我再决定留还是划。

轮次我问什么DeepSeek 给了什么我留下我划掉为什么、怎么改
1按作业和已定文档出各页参考五页齐套的初版这套底,后面只在上面改不另起一套页面有底才能对,重画会把已经定的规则画丢
2初版太挤,导航对不上导航收成同一套五个名字和顶栏一致和作业无关的页面名助教按五个功能找页,多出来的名字对不上
3按架构重做,论文用文字卡片深色顶栏、白卡片论文只留文字装饰图标和假缩略图数据里没有图,原型假装有图,后面实现会对不上
4说明页往前,走势跟年,词云要醒目逐年读数、三会卡片、页脚词云和共现放在一起看没有单独再开一页讲关系功能 4 留在热门方向本页
5数字用图,词云和关系合成一块权重前十表、气泡、纵向长页前十仍是能数出 1 到 10 的表不用词云代替前十前十要能复核名次
6按原型评分通查数卡、洞察、列表提示条的改法三会颜色用在标签和折线上列表上啰嗦的状态行改成表上方一条提示,和查无则抓同一位置
7这些参考怎么进原型工具正式原型用专用工具,参考图只当底热区自己连把参考图链接当成正式原型作业要的是能点的原型网页,不是一张效果图

按钮文案我改了一处:信息架构里叫应用筛选,页面上改成更新统计,和后来网站上的按钮一致。

案例 A:原型网页怎么连

发生在 原型设计结果.md。底图定了以后,正式可点的原型做在 Figma。这一轮问的是注册好之后怎么铺、怎么连,不是再改需求。

留下的连法:五页顶栏;筛选只在热门方向和走势之间;点权重前十或词上的圆,论文留在本页,点题名才进详情;列表绿条、再查黄条,不用弹窗;走势至少两个词才画线;演变按年翻,大约 1.1 秒一张,暂停那张不加延迟,否则停不下来;导入批量以及单篇各有结果弹窗;说明页只读。

划掉的是用原型工具导出代码。作业禁止用原型工具生成实现。热区是我对着按钮连的,DeepSeek 只列出从哪一块连到哪一块。

写代码之前

发生在 阶段二-实现设计.md。提示写明只要设计,不要整站代码。

留下:先库后页面;四张表和唯一键;接口按五个功能拆;查无则抓的状态放在列表响应里;部署放在功能能在本机跑通之后。

划掉一上来就上云。本机对不齐的接口,放到云上只是把同一处错误放到远处。筛选、绿条、两套数字这些边界,设计稿里写死,不让编码那一步再发明一套。

案例 B:骨架和入库

发生在 阶段二-编码协作.md 第 1 步,详文在 阶段二-编码-01-骨架与入库.md。这一则同时是编码和后面案例 C 的起点。

我已经定了四张表、写库只走 PaperRepo.create、两套数不混、库文件不进仓库。提示只要第 1 步:表结构、应用入口、建库、样例导入、健康检查。不要整站代码。去重用题名、会议、年份。样例和以后的导入页走同一条写库函数。

DeepSeek 回了四张表和索引,统一的返回形状,命中唯一键不覆盖,健康检查返回篇数。并说在沙盒里导入 12 条,再导一次跳过 12 条。

留下:目录拆开、四张表、唯一键、健康检查、写库只走 create。本机按这个顺序跑:建库、导入、再导入一次。第一次新增 12,第二次新增 0、跳过 12。

划掉并改了五处,原因都是草稿会让统计变空、导入崩掉,或者仓库缺作业要的文件。

  1. 作者关键词和摘要走同一套停用词。Deep Learning、Neural Network 会变成空。导入成功只说明行在,不说明词在。我改成标签整段保留,单词能留下的再拆一份。Vision Transformer 既能看见短语,也能按 transformer 搜到。摘要里的 using、method 仍然不进榜。
  2. 更新写成 or 回退。空字符串会把旧年份吞掉。改成键不在请求里才用旧值。
  3. 空文件、缺年份会崩。先检查再写。会议写成 CVPR 2024、年份写成 2024-06 也能收。
  4. 没有可提交的样例。我补了 12 条,关键词用分号分隔。
  5. 根目录要有代码规范。这不是草稿里的,我补了 codestyle.md。

我指出标签被切掉

案例 B:列表和导入

发生在第 2 步、第 3 步,详文在 阶段二-编码-02-论文列表.md、阶段二-编码-03-导入接口.md。做法是 DeepSeek 出一版,我先反问,本机跑测试,再决定进不进仓库。

列表。留下五个论文路由,以及库里没有、刚抓到、已经抓过、外面也没有这几种状态。划掉缺会议时默认 CVPR。抓到的行一旦进库就会进前十和走势,默认会议会把别的会算成 CVPR。也划掉用容易撞车的历史键,同一句话和另一句话可能被当成同一次抓取,黄条会错。第 3 轮把键改成能分开两次查询。

导入。留下批量、单篇、批次记录这三条;批次记录只记批量;样例导入和批量导入共用同一套写入。审查列出的空文件要拒绝、成功篇数要带回编号、测试互相隔离,这些我按清单改了代码再请复验。嵌套的抓取状态被展平,避免页面读到两层不一致的结果。

为什么不原样提交:第 1 轮里的默认会议和历史键若直接粘贴,后面前十的三会分布和列表黄条都会错。我讲得清后果,所以拒绝原样进仓库。

案例 B:前十

发生在 阶段二-编码-04-热门方向.md。我先按设计写了概览和前十两个接口,再把文件交给 DeepSeek 对照功能 3 审查,不是让 DeepSeek 从零写算法。

审查指出两处算错,我都改了代码。某年的文档数被当成全库文档数,较上年和洞察是空的。改成 idf 来自当前筛选的全集合。较上年用了全库年份,这个词没出现的年也参加比较。改成只看这个词自己的年份。

作者标签里的 the、of 会进榜。整段标签若是停用词就丢掉。滤完以后一篇里没有词,就回退到题名和摘要再切,否则有的样例会从榜上消失。

有一条没改代码:短语整段保留的同时再拆词,审查担心共现把 image 这类词放大。我暂不改切词。切词一改,列表搜索会跟着变,范围过大。图谱用当前前十限制节点,放在下一步处理。

测试里洞察用例原先绑着样例年份,换一份数据结论就漂。改成测试自带一份趋势数据。这样通过与否不靠审查附件和我本机是不是同一份库。

案例 B:图谱

发生在 阶段二-编码-05-关键词图谱.md。提示写明对照功能 4:篇内共现、节点用当前前十、点词出论文、不要写页面。

DeepSeek 审查出两处,我都改了。侧栏出现不在前十里的词,点下去图上没有圆。邻近词改成只用这十个节点。另一处是约篇数和下面列表的命中数混用。不在前十时,权重和约篇数应为空,列表条数仍可以大于 0。两个数字不是同一件事。

性能上再缓存一层,我暂缓。先保证点进去的词和图上的圆是同一批。

案例 B:热度动图

发生在 阶段二-编码-06-热度走势.md,页面动画在 阶段二-编码-07-前端五页.md。作业要的动是运行中的网站上线在画,不是换几张底图。

后端。提示是对照设计里的走势一节审查。留下按年、按会数篇数,一次把全部年份返回,没有届次的年是 0。少于两个词不画对比。

改了两处。空库时序列是空的,有数据时却永远是三个会。空库也返回三条,值为 0。空库还要有无数据的提示,不能和词数不够混成没有提示。

有一条没改接口:筛选到一个没有论文的遥远年份,横轴还在、值是 0。这和库是空的不是同一种情况。我在取年份的地方写清,页面用提示或合计为 0 来分支,不为这一种再改返回形状。

前端。折线用已经拿到的点,按年份逐段露出来,播放时不再请求。播放大约 0.8 秒一步。这一段我整段留下,因为和第 6 步的接口是同一套。少于两个词时页面自己提示,不画线。

我问过较上一年用权重还是篇数。走势纵轴用篇数。较上一年留在前十那张表上,用的是权重变化,不画进折线。

案例 B:五页

发生在 阶段二-编码-07-前端五页.md。DeepSeek 出了前端草稿:目录、请求、走势动画、绿条和黄条。我在本机三万篇的库上对过再改。

五页草稿我的提示

五页草稿的回复

留下:按资源拆的目录、地址用 hash、开发时代理、筛选状态单独放、弹窗不进路由、走势只请求一次、列表按抓取状态出条、上传和普通请求分开设置、顶栏篇数来自健康检查。

划掉的每一条都有页面上的后果。

草稿我交上去的为什么
词的关系用现成图组件自己画圆和线要能点、能只强调一个词,现成那版对不上本页交互
热门方向一次请求多条,没有等待按顺序请求,并写正在统计库大时像卡死
两张卡都用当前范围的篇数库内总数、当前范围、关键词数分开三套数不能看成同一个数
进论文页就先拉一整表有条件才拉,摘要收起从别的页带条件进来时,不该先闪一版默认表
筛选年份一开始为空,再靠健康检查填默认一段年份第一次进来就要能看见榜
把压缩包原样盖住仓库只合并对过的文件一份草稿覆盖会把已经改过的后端冲掉

删除确认草稿里用浏览器自带的确认。我留下弹窗方案,和列表、详情同一套,不另做一种。

案例 C:查无则抓,以及测试互相污染

发生在 阶段二-编码-08-查无则抓.md。列表上的绿条、黄条要真的走通。DeepSeek 做了审查,不是再写一套抓取。

审查把问题分成必须改和只需写清的。我留下并改了这些:建应用之前把抓取设为不访问外面,避免上一个测试的开关漏到下一个;外面抛错时列表仍要正常返回,不能变成服务器错误;补一条真正关掉抓取时的测试,证明不会假绿。演示用的临时数据和怎么清掉,写进说明,不装成生产数据。

划掉的是把测试开关写进正式的启动过程。测试归测试,业务代码不该认识测试框架。缺会议时默认 CVPR 也在这一步再次拒绝,和第 2 步是同一条理由。

查无则抓的审查

改完之后,抓取相关测试 16 条通过。通过的意思是四种状态分得开,不是页面截了一张绿条就算完。

附加:浏览

发生在编码协作记录第 9 步。这不是五个基础功能的替代。提示是:关键词要有短简介,优先外面的百科,失败就用本地词条;热门页要能按前缀搜库里的词。

留下:失败就降级、搜索防抖、浏览页和热门方向共用筛选、短清单最多 20 条而且只在浏览器里。

划掉英文的没有匹配。我改成中文,并留一组热门词当兜底。会议名之间的分隔曾经是乱码,改成正常的标点。新页面加在地址里,不替换原来的五页。

设计实现过程

这一节写每个功能当初怎么想、数据从哪来、页面上点一下之后发生什么。记录文件里有审查过程,这里不把读者再送去翻文件。

功能分析、系统设计、架构图

五个功能对应五种动作,不是五个互相独立的小网站。

功能用户当时要做的事页面不做什么
1 入库手里已经有一批或一篇材料,放进库导入不在这一页搜库里没有的题
2 列表找一篇、改错、删重复;没有就按这句话去外面抓论文、详情不在这里重算前十
3 前十当前这批论文里,哪十个词更能代表方向热门方向上半不用篇数当名次
4 图谱这十个词里,谁经常写在同一篇热门方向下半不把前十以外的词画进来
5 走势这几个词这几年、三个会各有多少篇热度走势不把权重画进折线

顺序是先有库,再列表,再导入接口,再三种统计,然后五页接上。绿条补在列表查询上,不单独做第六个功能。部署放在本机能点通之后。DeepSeek 建议过更早上云,我没听。环境会把对功能的时间吃掉,云上对不齐的接口只是同一处错误放到远处。

flowchart TB
  subgraph pages [页面]
    hot[热门方向]
    trend[热度走势]
    papers[论文和详情]
    imp[导入]
    about[说明]
  end
  hot --> api[Flask 接口]
  trend --> api
  papers --> api
  imp --> api
  about --> api
  api --> create[唯一的写入]
  create --> db[(SQLite)]
  api --> tfidf[前十:TF-IDF]
  api --> co[图谱:篇内共现]
  api --> cnt[走势:按年篇数]
  tfidf --> db
  co --> db
  cnt --> db

一篇论文进库之后,三条统计都只读库,不在浏览器里现切词。

flowchart LR
  file[文件或单篇或粘贴] --> check[校验题名会议年份]
  check --> create[同一条写入]
  create --> papersT[论文表]
  create --> words[词表]
  words --> top10[前十]
  words --> graph[共现]
  papersT --> trendN[按年篇数]

数据主路是公开元数据:CVPR 每年,ICCV 奇数年,ECCV 偶数年,大约 2020 到 2026。原文优先开放获取页面。大表不提交进仓库。单篇在库里找不到,才用 DBLP。2026-09-24 那天批量访问 DBLP 被挡成验证页面,本机用 12 条样例把接口跑通。前端联调时用过更大的本地库,那不是现在公网上的数字。这些材料只用于这门课。

清洗先做题名、会议、年份,再抽词,再入库。会议写成 CVPR 2024 收成 CVPR,年份写成 2024-06 收成 2024。同一题名、同一会议、同一年再来一次,不变成两行。

三套数为什么分开

热门页上能看见权重、约篇数、较上一年,走势页上又能看见篇数。这三套数回答的不是同一个问题,放在一条轴上会把人带偏。说明页把这件事写给第一次看的人。

数问的是用在哪不用在哪
TF-IDF 权重这个词有多能代表当前这批论文前十的名次和横条走势纵轴
约篇数这个词出现在多少篇里前十表里的一列不拿它排序
按年篇数这一年、这个会,有多少篇提到这些词走势折线不拿它和权重比高低

前十用 TF-IDF,是为了压住每篇都有的泛词。图用篇内共现,是为了看哪些词经常一起出现。走势数篇,是为了看这一年还有多少篇。换筛选,三套数一起按当前会议和年份重算。刚抓进来或删掉的论文,不能还显示旧榜,所以缓存带着篇数和更新时间。

功能 1:把论文放进库

想法:手里已经有材料,才走导入。课程或公开数据是一大批,不能一篇篇手打。单篇是补一条已经知道的记录。后来加的粘贴,是把一条 BibTeX 喂进同一张库。需求定稿时我拒绝过自动补 DOI、阅读清单那种文献工具。粘贴只是多一个入口,写库仍然一条路。

这一页不做查无则抓。库里没有、要按查询语句去外面抓,那是论文列表上的绿条。两条路如果都做在导入页,用户会分不清自己是在交材料,还是在搜。

flowchart TD
  start[打开导入] --> tab{哪一个页签}
  tab -->|批量| file[选文件]
  tab -->|单篇| one[填题名]
  tab -->|粘贴| bib[贴一条 BibTeX]
  file --> empty{文件是空的吗}
  empty -->|是| reject[先拒绝,不入库]
  empty -->|否| rows[拆成一行行]
  one --> rows
  bib --> rows
  rows --> valid[题名必须有,会议和年份收成标准写法]
  valid --> create[同一条写入]
  create --> dup{题名加会议加年份已经有了吗}
  dup -->|有| skip[跳过,不算新的一篇]
  dup -->|没有| ok[写入论文和词]
  ok --> done[弹出成功篇数和失败篇数]
  skip --> done

摘要、关键词、原文链接都进论文表。没有摘要时,词从题名来。作者写的关键词整段留下,里面还能切开的单词再计一份,避免 Deep Learning 这种标签被停用词切没。空文件先拒绝,不让导入直接崩。上传体积没有先写一个固定上限,留到和服务器的实际限制一起看。

功能 2:列表上的增删改和查找

想法:论文页是已知题名的入口,也是改错和删重复的地方。添加是空表,手填,不访问外面。编辑时,请求里没带的字段保持旧值,不能因为空字符串把年份吞掉。删一篇要确认,词跟着删,前十和走势按剩下的库重算。

查找分两种。题名精确:大小写和标点不同,仍算同一篇,比的是整理过的题名。编号、关键词模糊:对得上就出列表。从走势或演变进来时,地址上带着词、年、会,列表上方一条只读条件,点取消就回到默认表。这条条件是跳转带来的,不是热门页那三个勾跟着过来。

库里没有这句话,才按查询语句去外面抓。抓取只补摘要、关键词、原文链接,结果就在列表顶部,不用弹窗。

flowchart TD
  q[点检索] --> db{库里有吗}
  db -->|有| hit[直接列出,不提示补录]
  db -->|没有| crawl{允许到外面抓吗}
  crawl -->|关掉| none[页面明说没有]
  crawl -->|开着| net{外面有吗}
  net -->|没有| none
  net -->|有,而且这句话没抓过| green[绿条:这次联网补录,并写入]
  net -->|这句话已经抓过| yellow[黄条:不再插第二次]
  green --> list[摘要、关键词、原文链接出现在结果里]
  yellow --> list

四种状态由接口说明,页面只显示人话,不把后端的英文句子贴出来。模拟、真的访问 DBLP、完全关闭,用环境变量切换。测试必须在创建应用之前关掉抓取,否则上一个用例的开关会让下一个用例假绿。

功能 3:前十

想法:用户打开首页,要先回答当前这批论文里哪十个方向更突出。范围只认已经入库、并且落在所选会议和年份里的论文。三个勾都去掉再更新,榜就空,和没有数据时去导入是同一件事。换年份或换会议,榜应该变,这是故意的。

名次用 TF-IDF,不用篇数。一篇里到处都有的词,篇数很高,但不代表方向。权重高的排前面。排完丢掉 which、image 这种不能当方向的词,再取十个。表上仍能看见大约多少篇、较上一年、三个会各占多少,这些是辅助,不是排序依据。横条的长度是权重。

点某一行,就是选中这个词,页面滚到下面的工作台,并准备列出相关论文。点更新统计时先出现正在统计。库大的时候要等一会儿,等的是按当前范围重算,不是页面卡死。

较上一年比的是这个词自己的权重,只看它出现过的相邻年份。这个词只有一年,较上一年就是空,表上画横线,不拿全库的日历硬比。

功能 4:词和词怎么连

想法:前十回答谁更突出,图谱回答他们经常和谁写在同一篇。两件事都留在热门方向这一页。点了词立刻跳进列表,图谱就变成另一张检索表,功能 4 的点击就没了。

节点就是当前这十个,不多画。每一篇论文里,落在这十个里的词两两搭配,一起出现一次,线就粗一点。所以线的粗细是篇数,不是引用,也不是权重。

flowchart LR
  top[当前前十] --> nodes[只画这十个点]
  paper[每一篇论文] --> pair[篇内的词两两搭配]
  pair --> edge[同时落在这十个里才连线]
  click[点一个词] --> stay[仍在这一页]
  stay --> side[侧栏只显示这十个里的邻居]
  stay --> rel[下面列出相关论文]
  title[点题名] --> leave[才离开,进详情]

侧栏上的邻近词也限制在这十个里。如果侧栏冒出前十以外的词,点下去图上没有对应的圆,线上的数字和表上的篇数就不是一回事。前十仍是能数出 1 到 10 的表。关系画在旁边,不用词云代替名次。

功能 5:多年,三个会,线要动

想法:看一个方向这几年还在不在做,看的是篇数随年份怎么走,并且 CVPR、ICCV、ECCV 要能放在一起比。少于两个词没有对比,不画线,只提示再选一个。

接口一次把全部年份和三个会都返回。播放时只是在已经拿到的点上移动年份,不再请求。没有届次的年份是 0。ICCV 只在奇数年开,ECCV 只在偶数年开,折线掉到 0 是这年没开会,不是这个方向凉了。

数的是论文篇数,同一篇在同一年同一个会只数一次,按年和会议分组。三个会都会出现在结果里。空库也返回三条线,值为 0,并说明库是空的。这样页面不用猜某个会是没返回,还是篇数为 0。

flowchart TD
  words{至少两个词吗}
  words -->|否| hint[提示,不画对比]
  words -->|是| api[一次取回全部年份和三个会]
  api --> draw[折线大约 0.7 秒自己画完]
  draw --> play[点播放,年份往前走]
  play --> pause[点暂停,读出这一年三个会各自的篇数]
  pause --> jump[查看当年论文,带到列表,上方出现只读条件]

动有两层,都必须是运行中的网站。一层是线自己画出来。一层是播放时年份往前走,大约 0.8 秒一年。博客里的录屏放在成品那一节,这里不重复贴两份不一样的文件。原型靠换底图表示年份,不能代替这一条。

五页怎么接上

五页只是把上面五条路放进导航,不另做一套算法。地址用 hash,刷新不会丢页。筛选的三个勾和年份只在热门方向和走势之间带着走。点进论文或导入,不把这三个勾带过去。

页面打开时读什么点一下之后
热门方向篇数、前十、共现点词留在本页;点题名去详情
热度走势至少两个词的按年篇数播放只移动年份;查看当年论文去列表
论文库里的表,或一次抓取的结果绿条或黄条在表上方;编辑和删除是弹窗
导入不先查库写入成功后,前十和走势按新库重算
说明三个会、两套算法、数据从哪来没有会改库的按钮

详情不占顶栏一项。从论文点进去时,顶栏论文仍是当前页。原文链接直接打开那篇论文的地址。原型里用弹窗把地址摆出来,是为了播放时不离开原型,网站上不需要再套一层。

部署到腾讯云

代码已经在 腾讯云。运行时由 Flask 托管前端打包结果,数据库放在服务器的数据目录,不进仓库。克隆地址打开看不到论文和走势。

能打开的首页和项目交付那张表、成品那一节是同一行:

成品网站:http://159.75.211.145:8080/#/hot。

上线后要再点一遍:导入、列表和绿条、前十、点词、走势播放。本机的样例和测试不能代替这一遍。

代码说明

关键代码和思路

上一节是数据怎么走。这一节把十份记录里的关键代码按功能放在一起。阶段二-实现设计.md 先定了表、接口和页面,里面没有可运行的函数,所以不贴代码。阶段二-编码协作.md 是总目录,第 1 步到第 8 步对应下面各段,第 9 步浏览放在附加。能讲清的才贴。草稿里后来被改掉的写法,贴的是改完进仓库的那一版。

共用的库:同一篇不能变成两行

实现的是后面五个功能共用的底,记在 阶段二-编码-01-骨架与入库.md。同一题名、同一会议、同一年再导一次,不该变成两行。词挂在论文上,论文删了,词一起走。

CREATE UNIQUE INDEX IF NOT EXISTS ux_papers_dedup
    ON papers (title_norm, venue, year);

CREATE TABLE IF NOT EXISTS paper_keywords (
    paper_id INTEGER NOT NULL REFERENCES papers (id) ON DELETE CASCADE,
    keyword  TEXT    NOT NULL,
    tf       INTEGER NOT NULL DEFAULT 1,
    PRIMARY KEY (paper_id, keyword)
);

导入、手填、抓取都进 create。插入论文和重写关键词在同一个事务里。撞上唯一键时,返回已经有的那篇,写成功后清掉统计缓存,新论文才会出现在前十里。

def create(data: dict) -> tuple[int, bool]:
    title_norm = normalize_title(title)
    try:
        with conn:
            cur = conn.execute(sql, params)
            paper_id = cur.lastrowid
            PaperRepo._replace_keywords(conn, paper_id, title, abstract, keywords)
    except sqlite3.IntegrityError:
        row = conn.execute(
            "SELECT id FROM papers WHERE title_norm = ? AND venue = ? AND year = ?",
            (title_norm, venue, year),
        ).fetchone()
        if row is None:
            raise
        return int(row["id"]), False
    invalidate_stats_cache()
    return paper_id, True

作者给了关键词,就整段留下,再把切得开的单词补一份。字段是空的,才去切题名和摘要。这是我改过的。草稿把关键词和摘要走同一套停用词,Deep Learning 会被切空。

def keyword_source(title: str, abstract: str, raw_keywords: str) -> dict[str, int]:
    tags = split_keywords(raw_keywords)
    if tags:
        merged: Counter = Counter()
        for tag in tags:
            merged[tag] += 1
            for token, tf in tokenize(tag).items():
                if token != tag:
                    merged[token] += tf
        if merged:
            return dict(merged)
    return keywords_of(title, abstract)

更新不能用 or。请求里没带的字段才沿用旧值,空字符串和 0 都算用户填过。

def _pick(data: dict, key: str, fallback):
    if key not in data or data[key] is None:
        return fallback
    return data[key]

会议写成 CVPR 2024、年份写成 2024-06,先收成 CVPR 和四位年。空文件在写入前就返回 0 条,不往下崩。

def validate_venue(venue: str) -> str:
    text = (venue or "").strip().upper()
    for name in VENUES:
        if name in text:
            return name
    raise ValueError(f"会议必须是 {', '.join(VENUES)} 之一,收到:{venue!r}")

def seed(path: Path, source: str = "batch_import") -> dict:
    rows = read_rows(path)
    if not rows:
        return {"total": 0, "success": 0, "duplicated": 0, "failed": 0, "failures": []}

功能 1:导入

实现的是把已经有的材料写进库,记在 阶段二-编码-03-导入接口.md。批量和样例导入数的是新增还是重复,都只调用上面的 create,不另写一套插入。

paper_id, created = PaperRepo.create(normalized_row)
if created:
    success += 1
else:
    duplicated += 1

单篇如果填了会议和年份,就当手填直接写入。没填齐,才走抓取。导入页不把黄条那句话原样拿来用:这句话已经抓过,在单篇上改报成没有新的一篇,避免导入页冒出列表上的黄条。

_SINGLE_FETCH_MAP = {
    STATUS_CRAWLED: "crawled",
    STATUS_NOT_FOUND: "not_found",
    STATUS_ALREADY_CRAWLED: "not_found",
}

def resolve_single_import(data: dict) -> dict:
    if _has_venue_year(data):
        paper_id, created = PaperRepo.create(payload)
        ...
    return _run_fetch_path(data, title)

批量接口只负责拆文件、调用写入、记一批日志。空批次不记日志。

功能 2:列表查找和查无则抓

实现的是列表上的查找、增删改,以及库里没有就按这句话去外面抓。查找和四种状态记在 阶段二-编码-02-论文列表.md,抓取开关和入库校验记在 阶段二-编码-08-查无则抓.md。

精确查找比的是整理过的题名。大小写和标点不同,仍算同一篇。模糊查找走题名、编号或关键词。从走势进来的词、年、会是地址上的只读条件,取消之后才回到默认表。

库里已经有结果,状态是命中,不去外面。没有结果时,先看这句话抓过没有。抓过就是黄条。没抓过、并且外面的源关着,就明说没有。开着才去抓,抓到再查一次库,让刚写入的行出现在同一次响应里。

def resolve_fetch(query: str, mode: str, has_result: bool) -> dict:
    if not query or has_result:
        return {"status": STATUS_HIT, "inserted": 0, "skipped": [], "message": ""}
    key = history_key(query, mode)
    if CrawlHistoryRepo.exists(key):
        return {"status": STATUS_ALREADY_CRAWLED, "inserted": 0, "skipped": [],
                "message": "这个查询之前已经抓过,本次不再补录"}
    if not has_enabled_provider():
        return {"status": STATUS_NOT_FOUND, "inserted": 0, "skipped": [],
                "message": "库内没有找到相关论文,外部数据源尚未接入,本次未补录"}
    return _fetch_and_store(query, mode, key)

同一次请求里,刚抓到要立刻再搜一遍,否则绿条说补录了,表还是空的。

items, total = _search(**search_kw)
fetch = resolve_fetch(query, mode, bool(total))
if fetch["status"] == STATUS_CRAWLED:
    items, total = _search(**search_kw)

两次查询不能因为空格或大小写被当成同一次。键用检索方式加整理过的句子,中间用分隔符隔开。

def history_key(query: str, mode: str) -> str:
    normalized_mode = (mode or "fuzzy").strip().lower()
    return f"{normalized_mode}{UNIT_SEP}{_loose_query(query)}"

抓回来的一条,会议必须是 CVPR、ICCV、ECCV,年份必须能转成整数。缺了就丢掉,不默认写成 CVPR。默认会议会把别的会的论文算进 CVPR,前十和走势都会偏。

def _normalize_item(item: dict) -> tuple[dict | None, str]:
    title = (data.get("title") or "").strip()
    if not title:
        return None, "题名为空"
    venue = (data.get("venue") or "").strip().upper()
    if venue not in VENUES:
        return None, f"会议缺失或非法:{venue or '空'}"

每一条通过校验的才调用 create。插进几篇,这次就是绿条。环境变量决定这次是模拟、去 DBLP,还是关闭抓取。测试要在创建应用之前设成关闭,不然用例互相补录,看起来像通过了。

STATUS_HIT = "hit"
STATUS_CRAWLED = "crawled"
STATUS_ALREADY_CRAWLED = "already_crawled"
STATUS_NOT_FOUND = "not_found"

功能 3:前十

实现的是热门方向的名次,记在 阶段二-编码-04-热门方向.md。N 是当前筛选里的论文数,df 是出现过这个词的论文数。换会议、换年份,N 变了,榜就变。各年的权重用同一个 idf,加总应当等于这个词的总权重。草稿曾把某一年的文档数当成全库文档数,较上年和洞察会全空。

df = len({int(r["paper_id"]) for r in kw_rows})
idf = math.log(n_papers / df) if df else 0.0
weight = sum((1.0 + math.log(max(int(r["tf"]), 1))) * idf for r in kw_rows)
for year, year_rows in by_year.items():
    year_weights[year] = sum(
        (1.0 + math.log(max(int(r["tf"]), 1))) * idf for r in year_rows
    )

较上一年只看这个词自己出现过的年份。没有上一年,或上一年权重是 0,就返回空,表上画横线。

def _yoy_rate(year_weights: dict[int, float], years: list[int]) -> float | None:
    ref = max(years)
    prev = ref - 1
    cur_w = year_weights.get(ref)
    prev_w = year_weights.get(prev)
    if cur_w is None or prev_w is None or prev_w <= 0:
        return None
    return (cur_w - prev_w) / prev_w

排序先比权重,权重相同再按词的字母序,名次才能复现。取前十。which、image 这种不能当方向的词,在这一步丢掉,不让它们占名额。

功能 4:图谱

实现的是关键词之间的关系,记在 阶段二-编码-05-关键词图谱.md。节点先取当前这十个词,不另算一套榜。每一篇论文里,落在这十个里的词两两搭配,一起出现一次,边加一。一篇里同一对词只贡献一次。

def build_cooccurrence(paper_filter: PaperFilter) -> dict:
    top_items = build_top10(paper_filter)
    node_ids = {item["keyword"] for item in top_items}
    by_paper = _by_paper_keywords(paper_filter, allowed=node_ids)
for keywords in by_paper.values():
    for a, b in combinations(sorted(keywords), 2):
        link_counts[(a, b)] += 1

侧栏上的邻近词也必须在这十个里。点到前十以外的词,图上没有圆,线上的数字和表上的篇数就不是一回事。不在前十时,约篇数为空,下面列表的命中数仍可以大于 0。

def _co_neighbors(..., node_ids: set[str], ...):
    for other in keywords:
        if other == target or other not in node_ids:
            continue
        counts[other] += 1

点词之后的论文,复用列表那套按词、会议、年份的查询,不另写一条搜索。

功能 5:走势,以及五页怎么把线画出来

实现的是多年、三个会的篇数对比,后端记在 阶段二-编码-06-热度走势.md,线上的动画记在 阶段二-编码-07-前端五页.md。

数的是论文篇数。同一篇在同一年、同一个会只数一次。三个会都按年分组,没有届次就是 0,页面不用猜某个会是没返回还是篇数为 0。

sql = (
    "SELECT p.year, p.venue, COUNT(DISTINCT p.id) AS cnt"
    " FROM papers p"
    " INNER JOIN paper_keywords pk ON pk.paper_id = p.id"
    f"{where}"
    " GROUP BY p.year, p.venue"
)

少于两个词,不画对比,给出再选一个的提示。库是空的,仍返回三个会的空序列,并说明没有数据。这两种提示不能合成一种。

if not years:
    hint = HINT_NEED_KEYWORDS if len(normalized) < 2 else HINT_NO_DATA
    return {"years": [], "series": _series_shell(), "keywords": normalized, "hint": hint}
if len(normalized) < 2:
    return {"years": years, "series": _empty_series(years), "hint": HINT_NEED_KEYWORDS}

接口一次把全部年份返回。多个词用重复的参数传,和列表的关键词参数同一风格。

def parse_keywords(request: Request) -> list[str]:
    words: list[str] = []
    for item in request.args.getlist("keyword"):
        for part in item.split(","):
            word = part.strip().lower()
            if word and word not in words:
                words.append(word)
    return words

@bp.get("")
def trend():
    return ok(build_trend(PaperFilter(**parse_filter(request)), parse_keywords(request)))

页面上的动不请求新数据。折线大约 0.7 秒自己画完。播放大约 0.8 秒走一年,只是把已经拿到的点逐段露出来。

.draw-line {
  stroke-dasharray: 1200;
  stroke-dashoffset: 1200;
  animation: draw 0.7s ease forwards;
}

五页接到这些接口上时,地址用 hash,刷新和静态托管不会丢页。三个勾和年份只在热门方向和走势之间带着走。进论文、导入、说明不带这套筛选。从走势跳到论文,用地址上的查询带词和年。详情不占顶栏一项,论文仍高亮。顶栏篇数来自健康检查,导入、删除、补录之后再读一次。

表、唯一写入、一次返回全部年份,设计稿里就有。标签怎么留、更新不用 or、纵轴用篇数、侧栏只留前十、空库也返回三条线,是对照记录改过的。讲不清的文件没有贴在这里。

心路历程、收获

有三件我记得很具体。

直连 DBLP 的那天,返回的页面在问我是不是机器人。我盯了一会才确定不是证书坏了。如果把这次失败写成库已经满了,后面的前十就是假的。所以主数据改走公开数据集,本机先用 12 条把健康检查跑通。

另一件更阴。导入显示 12 篇,榜却是空的。我先怀疑 CSV 被逗号切坏,对过停用词才发现 deep 和 learning 自己把标签吃了。从那以后我先看词表里有没有行,再相信榜。

第三件是原型。信息架构按墨刀写完,连线时我改去 Figma,等于页面要再摆一次。摆完才发现热区可以盖在已经定稿的底图上。工具换了,五个功能没换。

构建之法第 4 章把结对写成一个开、一个看。这里多数时候我在开。DeepSeek 写得快,也会在同一段里加上书单。人很少这么稳定地加戏,但人可以指着屏幕问绿条在哪。没有同学坐在旁边,我就把拒绝写进 md。不然一周后仓库里只剩最终代码,看起来像我一开始就写对了。

相同的地方是:都要有人看另一份产出,都要把没看懂的留下。不同的地方是速度和加戏。好处是表和工厂一轮就有,需求可以按字母拆开问。坏处是 DeepSeek 会把基础功能写成以后再做。DBLP 被挡是网站当时的行为,我没把它算成模型过期。密钥和数据库没有进仓库,没看懂的抓取也不会直接开到公网。

对 AI 结对伙伴的评价

贡献和局限

结对伙伴只有 DeepSeek,版本是 Hy4 preview。DeepSeek 适合起稿:三个角色、五页怎么分、表和接口、工厂和目录,一轮就能铺开。我留得最多的是结构。走势一次把全部年份和三个会拿回来,播放只在已经拿到的点上移动,这个结构我留了。入库走唯一的写入,撞上同一题名、同一会议、同一年就跳过,不覆盖,这个我也留了。列表上库里没有就抓、再查不再插第二次,绿条和黄条的位置我留了。

DeepSeek 定不了哪一套数算分。前十是 TF-IDF,图谱是当前这十个词的篇内共现,走势纵轴是按年篇数。这三套如果混成一个权重,横条、线和点就不是一回事。热区画在哪、绿条放在列表而不是弹窗、BibTeX 粘贴算交材料不算推荐,这些是我定的。DeepSeek 写得出函数,写不出这一刀该切在哪。

加戏是稳定的。阅读清单、把图谱写成以后再做、把权重当成走势纵轴、作者标签和摘要共用同一套停用词,这几件如果原样留下,网站会缺基础功能,或者图的意思是错的,或者榜是空的。切词那种错更麻烦:样例还是 12 篇,接口像成功,词却被切没了。所以我只提交检索对过、词还在的版本。测试要在创建应用之前关掉抓取,不然上一个用例的开关漏过来,绿条是假的。缺会议时不要默认 CVPR。更新字段不能用 or 把空值填回旧的。这些都是草稿里看起来能跑、对上页面就错的地方。

局限我也碰到了。DeepSeek 会把基础功能写成以后再做,会在同一段里加上书单。人很少这么稳定地加戏,但人可以指着屏幕问绿条在哪。知识不是实时的:2026-09-24 那天批量访问 DBLP 被挡成验证页面,这是网站当时的行为,我没把它算成模型过期。DeepSeek 也不知道我这台机器上的库有多少篇,本地联调时的篇数不能写成公网上的数字。密钥和数据库没有交给 DeepSeek,也没有进仓库。没看懂的抓取不会直接开到公网。

用法就固定成两轮。第一轮让 DeepSeek 起草,第二轮写明不许加功能、不许换口径,再对页面和测试。拒绝写进对应的 md,博客从那些 md 里摘,不从记忆里重写。没有同学坐在旁边的时候,这些 md 就是那个看的人。DeepSeek 写得快,收得慢的是我。快的部分我用了,讲不清会让榜、线或绿条错在哪的部分,不进仓库。

附加

五个基础功能走通之后才做这三块。它们挂在说明页、走势页下方和顶栏的浏览上,不替换入库、列表、前十、图谱和走势折线。挂了也不影响另外五页。成品里浏览那一段动图在 成品展示,历年前十要单独录,不要和三个会的折线放进同一个文件。

了解更多

打开说明。最上是三张卡片,颜色和走势图例一致:CVPR 绿,ICCV 蓝,ECCV 琥珀。卡片上写开会的年份。CVPR 每年都开。ICCV 只在奇数年,近年是 2021、2023、2025。ECCV 只在偶数年,近年是 2020、2022、2024、2026。点卡片去官网,本站不改库。

再往下是按任务选页:还没定方向去热门方向,想看近几年还做不做去热度走势,手里有题名去论文,已经有文件去导入。左右两格把两套算法分开写:前十是 TF-IDF 权重,走势纵轴是当年篇数,两者不混看。再下面写数据从公开元数据来,单篇找不到才用 DBLP,只用于这门课。

设计上,了解更多不单独做第六个导航。三个会的背景和统计口径以说明页为准,点一个词时的短简介放在热门方向和浏览页。前十是当前筛选下这批库里的权重,不是整个领域的结论。样例库上的名次不能写成计算机视觉这两年的定论。

实现几乎是静态页。奇数年、偶数年写在卡片文案里,和走势接口里「没有届次就是 0」是同一句话,避免说明页说没开会、折线却空着不解释。点词简介不在这一页请求。

年度热词演变

热度走势页上方仍是功能 5:至少两个词,三条线,播放时年份在已经拿到的点上走。页面向下另有一块历年前十。一次只看一年,榜上是这一年的权重前十。点按年播放,大约 1.1 秒换一年,名次重排。每一行能看出相对上一年是上升、下降还是没动,页上有一句人话:新进了哪些词,退出了哪些词。最早的一年没有上一年,句子写成对照起点,不假装有升降。

设计和折线分开。折线回答选定的几个词各有多少篇。演变回答这一年的前十换了谁。两套接口,两套播放。折线大约 0.8 秒走一年,演变大约 1.1 秒换一帧。筛选仍是走势页上的会议和年份,换筛选两块一起重算。

flowchart TD
  filter[当前会议和年份] --> once[一次取出这些年的词]
  once --> year[按年切开]
  year --> rank[每一年单独算 TF-IDF 前十]
  rank --> diff[和上一年的名单比:新进、退出、升降]
  diff --> frame[收成一帧]
  frame --> play[播放只切换帧,不再请求]

名次用的仍是前十那套权重,不是篇数,也不是折线上的数。具体哪个词从第几到第几,要在当前库上看,这里不写一个没对着页面核对过的名次。

后端一次把筛选里的行取回来,在内存里按年算,避免每一年重扫全库。算完放进缓存,键里带着筛选。

def build_evolution(paper_filter: PaperFilter) -> dict:
    years = _resolve_years(paper_filter)
    all_rows = _fetch_rows(paper_filter) if years else []
    by_year = {}
    for row in all_rows:
        by_year.setdefault(int(row["year"]), []).append(row)
    prev_top = []
    for year in years:
        _, stats = _aggregate(by_year.get(year, []))
        items = _top10_from_stats(stats)
        top = [item["keyword"] for item in items]
        entered = [word for word in top if word not in prev_top] if prev_top else []
        left = [word for word in prev_top if word not in top] if prev_top else []
        prev_top = top

接口是 GET /api/trend/evolution,和 GET /api/trend 不是同一个。页面播放只改当前帧的下标。

function playEvo() {
  evoPlaying.value = true
  evoTimer = setInterval(() => {
    evoIdx.value = (evoIdx.value + 1) % evoFrames.value.length
  }, 1100)
}

其他扩展:浏览页

顶栏点浏览,标题是开题浏览。这一页把四件事收在一起:当前范围有多大规模,哪些词在升温或降温,选中的方向是什么意思,相关论文能不能先圈几篇。右边是短清单。页头写明名次是 TF-IDF,值不值得做要去走势页看当年篇数。

输入一个方向,手离开大约 0.2 秒,下面出现库里的建议,每条带相关篇数。点一条建议,中间出现简介和相关论文。简介优先是英文维基的短摘要,并带来源。失败、断网或测试关掉外部请求时,用本地词条,不编一段看起来像维基的话。没有匹配时是中文,旁边仍留着一组热门词,不是英文报错。

从热门方向点开题浏览,地址会带上当前这个词,浏览页直接打开它。短清单点加入才出现,最多 20 篇,只存在这台浏览器里,刷新还在,不写回数据库。同一篇再点,提示已经在清单里。

flowchart TD
  type[输入一个词] --> wait[停大约 0.2 秒]
  wait --> sug[按当前会议和年份在词表里找]
  sug --> pick[点一条建议]
  pick --> wiki{维基摘要拿到了吗}
  wiki -->|拿到| show[显示摘要和链接]
  wiki -->|失败或关掉| local[显示本地词条]
  show --> papers[下面是库里的相关论文]
  local --> papers
  papers --> save[加入短清单,只写在浏览器]

建议接口按当前筛选查词表。一个字母只匹配前缀,更长的词再做包含匹配,完全相等的排在前面,然后按篇数。

@bp.get("/suggest")
def suggest():
    filt = parse_filter(request)
    query = (request.args.get("q") or "").strip()
    items = PaperRepo.suggest_keywords(
        query,
        venue=filt["venue"],
        year_from=filt["year_from"],
        year_to=filt["year_to"],
    )
    return ok({"items": items, "q": query, "filter": filt})

简介先查别名,例如 diffusion 去对扩散模型,再请求英文维基。摘要截到大约 420 字。任何网络错误都退回本地说明。测试用环境变量跳过外部请求。

def fetch_keyword_intro(keyword: str) -> dict:
    word = (keyword or "").strip()
    if os.environ.get("HOTWORDS_SKIP_WIKI") == "1":
        return _local(word)
    query = ALIASES.get(word.lower(), f"{word} computer vision")
    try:
        search = _get("https://en.wikipedia.org/w/api.php?" + urllib.parse.urlencode({
            "action": "query", "list": "search", "srsearch": query, "srlimit": 1, "format": "json",
        }))
        hits = (search.get("query") or {}).get("search") or []
        if not hits:
            return _local(word)
        title = hits[0]["title"]
        summary = _get(
            "https://en.wikipedia.org/api/rest_v1/page/summary/" + urllib.parse.quote(title)
        )
        extract = (summary.get("extract") or "")[:420]
        page_url = ((summary.get("content_urls") or {}).get("desktop") or {}).get("page") or ""
        if not extract:
            return _local(word)
        return {"keyword": word, "extract": extract, "url": page_url, "source": "wikipedia"}
    except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError, OSError):
        return _local(word)

前端停 200 毫秒再请求,避免每敲一个字母打一次库。短清单用浏览器本地存储,超过 20 条就丢掉最旧的。

const MAX = 20
function add(paper, keyword = '') {
  if (!paper?.id || has(paper.id)) return false
  items.value = [{ id: paper.id, title: paper.title, venue: paper.venue, year: paper.year, keyword }, ...items.value].slice(0, MAX)
  localStorage.setItem('cv-shortlist-v1', JSON.stringify(items.value))
  return true
}

DeepSeek 草稿里没有匹配是英文,引用行的分隔还乱过码。我改成中文提示,分隔改成正常标点,并留热门词当兜底。浏览不替代绿条,不替代前十的算法,也不替代走势那条正在画的线。

浏览页挂了,热门方向、走势、论文、导入、说明仍从顶栏进去。它不替代绿条,不替代前十的算法,也不替代热度走势那条线。

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

88

社区成员

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

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