1. 从一条每日更新帖说起:为什么值得盯住 ArXiv 的 CV 板块
每天早上刷一遍 ArXiv 的 cs.CV 分区,已经成了我这两年雷打不动的习惯。原因很直接:计算机视觉这个方向迭代太快了,快到什么程度?你上周刚看完的一篇目标检测论文,这周可能就有人把它的 backbone 换掉、把 loss 重写、在 COCO 上又刷高了两个点。如果隔上十天半个月不看,再回到这个领域,会有一种"我是不是走错片场了"的恍惚感。
所以当我看到"每日更新 ArXiv CV Paper"这个项目标题时,第一反应是:这是个刚需。它解决的不是"我要不要读论文"的问题,而是"我怎么在信息洪流里不被淹死"的问题。ArXiv 每天在 cs.CV 上挂出来的新论文,少则几十篇,多则上百篇,会议截稿前那几周更是夸张。你不可能每篇都精读,甚至不可能每篇都点开。你需要的是一个稳定的、每天准时出现的、经过初步筛选的入口。
这个项目适合谁?三类人。第一类是刚进组的研究生,导师让你"多看看最近的论文找找方向",但你打开 ArXiv 首页一脸懵,不知道该从哪下手。第二类是在工业界做算法落地的工程师,你不需要追最前沿的理论,但你需要知道"最近有没有什么新方法能直接用到我的检测/分割/生成任务上"。第三类是我这种"半只脚在学术、半只脚在工程"的人,既要保持对前沿的敏感度,又没时间天天泡在论文里。
这篇博文不打算给你灌鸡汤,也不打算复述某篇具体论文的摘要。我想做的是把这个"每日更新"的项目拆开,讲清楚它背后的运作逻辑、我实际跑下来的一套流程、踩过的坑,以及怎么把它改造成适合你自己的版本。核心关键词就三个:ArXiv、CV、Paper。围绕这三个词,我会把从"抓取"到"筛选"到"归档"到"复现"的整条链路讲透。
2. 内容整体设计与思路拆解
2.1 为什么是 ArXiv,而不是别的渠道
先回答一个最基础的问题:为什么盯 ArXiv,而不是盯会议、盯公众号、盯各种论文推荐流?
会议论文的问题是滞后。CVPR、ICCV、ECCV 这些顶会,从投稿到放榜到开放获取,中间隔着大半年。你看到的时候,这个方向可能已经被后续的 ArXiv 预印本迭代过两三轮了。公众号和推荐流的问题是"二手"。它们经过了编辑的筛选和解读,省事,但也意味着你看到的是别人嚼过的东西,而且推荐算法会把你困在舒适区里,你永远看不到那些"标题看起来不相关但其实很有启发"的论文。
ArXiv 的价值在于"一手"和"全量"。它是预印本平台,作者在投稿前或者投稿的同时就把论文挂上去了,时效性最强。而且它是全量的,不会因为"这篇不够热门"就不给你看。当然,全量也是它的缺点——信息过载。所以这个项目的核心设计思路,本质上就是:在保留 ArXiv 一手性和全量性的前提下,用一套自动化流程把信息过载的问题解决掉。
我试过几种不同的方案,最后沉淀下来的思路是这样的:抓取要稳,筛选要准,归档要能检索,复现要能落地。这四件事对应四个环节,缺一不可。很多人只做了第一步"抓取",结果每天收到一堆标题,看两天就烦了,因为筛选没做好。也有人筛选做得不错,但归档没做,过了一个月想找某篇论文,翻聊天记录翻半天。
2.2 方案选型的几个关键取舍
在动手之前,有几个取舍需要先想清楚,这些取舍直接决定了你后面用起来顺不顺手。
第一个取舍:全量抓取还是关键词抓取?全量抓取就是每天把 cs.CV 的新论文全部拉下来,关键词抓取就是只拉包含特定词的论文。我的建议是全量抓取,本地筛选。原因很简单:ArXiv 的 API 支持按分类拉取,全量拉取的成本并不高,一天也就几百条元数据。但如果你在抓取阶段就用关键词过滤,你会漏掉那些"标题里没有你的关键词、但方法其实相关"的论文。比如你做的是"小样本学习",但某篇论文标题写的是"few-shot"而不是"small sample",关键词过滤就可能漏掉。全量拉下来,筛选逻辑放在本地,随时可以调整,灵活得多。
第二个取舍:用官方 API 还是用页面解析?官方 API 是首选。ArXiv 提供了基于 OAI-PMH 的接口,也提供了 export 接口,返回的是结构化的 Atom XML,解析起来很干净。页面解析(爬 HTML)的问题是页面结构会变,今天能用的选择器明天可能就失效了,维护成本高。而且页面解析对服务器不友好,容易触发限流。官方 API 有明确的调用规范,按规范来,稳定得多。
第三个取舍:本地跑还是云端跑?如果你只是自己用,本地跑完全够了。一台常年开机的机器,或者你自己的开发机,写个定时任务就行。云端跑的好处是"不依赖你的机器开不开机",但配置起来麻烦,还要考虑凭证管理。我的做法是本地跑,用一个简单的 cron 或者系统计划任务,每天早上八点触发。如果你用的是 Windows,任务计划程序也能干这事。
第四个取舍:存成什么格式?我试过存 CSV、存 JSON、存 SQLite,最后稳定在 SQLite。CSV 的问题是字段里如果有逗号或者换行,解析起来很烦;JSON 的问题是查询不方便,你想按"发布日期 + 关键词"组合查,得自己写代码遍历;SQLite 是单文件、零配置、支持 SQL 查询,完美契合这个场景。论文的元数据(标题、作者、摘要、链接、分类、发布日期)存一张表,你的标注(是否已读、是否精读、标签、笔记)存另一张表,用论文 ID 关联。
3. 核心细节解析与实操要点
3.1 ArXiv API 的调用规范与参数计算
ArXiv 的 export API 地址是http://export.arxiv.org/api/query,这个接口支持几个关键参数,用好了能省很多事。
search_query:查询条件。按分类拉取用cat:cs.CV,按关键词用all:xxx,组合用AND、OR。start:起始位置,从 0 开始。max_results:单次返回的最大条数。这里有个坑:官方建议单次不要超过 2000 条,实际测试下来,单次 100 到 200 条最稳,再多容易超时或者被限流。sortBy:排序字段,用submittedDate按提交时间排。sortOrder:ascending或descending。
如果你要拉"最近一天 cs.CV 的新论文",思路是这样的:先按submittedDate降序拉一批,然后过滤出提交时间在最近 24 小时内的。为什么不直接用时间范围查询?因为 ArXiv 的 API 对时间范围查询的支持比较弱,用submittedDate:[202609080000 TO 202609090000]这种格式,实测下来经常返回空或者不全。稳妥的做法是拉最近 N 条(比如 300 条),然后在本地按时间戳过滤。
关于调用频率,官方没有给出非常明确的数字,但社区的经验是每 3 秒不超过 1 次请求。我自己的做法是每次请求之间 sleep 3 秒,一天也就请求一两次,完全够用。如果你要拉历史数据,比如补拉过去一个月的,那就更要注意节奏,别把人家服务器打挂了。
提示:ArXiv 的 API 返回的是 Atom XML 格式,不是 JSON。解析的时候用 Python 的
feedparser库最省事,它专门处理这种格式,几行代码就能把标题、作者、摘要、链接都提出来。
3.2 筛选逻辑的设计:从"标题党"到"真相关"
抓取只是第一步,真正决定这个项目好不好用的,是筛选逻辑。我见过太多人做的"每日论文"最后变成"每日标题列表",问题就出在筛选上。
我的筛选分三层。
第一层是硬过滤。比如你明确不做 3D 视觉,那就把标题或摘要里包含 "3D"、"point cloud"、"NeRF" 的过滤掉。这一层用关键词黑名单实现,简单粗暴但有效。注意黑名单要定期维护,因为领域热词会变。
第二层是关键词加权。你关注的方向,比如"目标检测"、"语义分割"、"扩散模型",给每个方向配一组关键词,标题里出现权重高,摘要里出现权重低。最后算一个总分,按分数排序。这一层的关键是关键词要覆盖同义词和变体。比如"扩散模型"要同时覆盖 "diffusion"、"DDPM"、"score-based"、"denoising";"目标检测"要覆盖 "detection"、"detector"、"object detection"、"DETR"。
第三层是人工快速扫读。前两层做完,每天剩下的可能就十几二十篇。这时候你花十分钟扫一遍标题和摘要的第一句,标记出"值得精读"的。这一层不能省,因为自动化再聪明也替代不了人的判断。有些论文标题平平无奇,但摘要里藏着一个很巧妙的思路。
这里有个经验:摘要的第一句和最后一句信息量最大。第一句通常讲"我们解决了什么问题",最后一句通常讲"我们取得了什么结果"。中间的方法描述可以快速略过,等你决定精读的时候再看。
3.3 归档与检索:让三个月后的你能找到今天的论文
归档这件事,很多人不做,觉得"我收藏了就行"。但收藏夹是会爆炸的,而且收藏夹里的东西你基本不会再打开。我的做法是建一个 SQLite 数据库,论文元数据一张表,我的标注一张表。
论文表的关键字段:arxiv_id(主键)、title、authors、abstract、categories、published_date、url、pdf_url。标注表的关键字段:arxiv_id(外键)、status(未读/已扫/精读/复现)、tags(自定义标签,逗号分隔)、notes(笔记)、added_date。
有了这个库,你可以做很多事。比如"查一下我上个月标记为'精读'但还没写笔记的论文",比如"查一下所有带'扩散模型'标签的论文按日期排",比如"统计一下我最近三个月关注的方向分布"。这些查询用 SQL 一句话就能搞定,比翻收藏夹高效太多。
注意:SQLite 是单文件数据库,备份就是复制文件。但要注意并发写入的问题,如果你同时跑多个脚本往里写,可能会锁。我的做法是写入操作串行化,读操作随便。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
先把环境搭起来。我用的是 Python,版本 3.9 以上都行。需要装的库不多:
pip install feedparser requests beautifulsoup4feedparser用来解析 ArXiv 返回的 Atom XML,requests用来发 HTTP 请求,beautifulsoup4备用(有时候需要解析一下 HTML 页面,比如你想从论文主页抓更多信息)。
数据库用 Python 内置的sqlite3,不需要额外安装。定时任务用系统的 cron(Linux/macOS)或者任务计划程序(Windows)。
4.2 抓取脚本的完整实现
先写抓取部分。核心逻辑是:构造请求 URL,发请求,解析 XML,提取字段,存库。
import feedparser import requests import sqlite3 import time from datetime import datetime, timedelta ARXIV_API = "http://export.arxiv.org/api/query" def fetch_recent_cv_papers(max_results=300): params = { "search_query": "cat:cs.CV", "start": 0, "max_results": max_results, "sortBy": "submittedDate", "sortOrder": "descending" } resp = requests.get(ARXIV_API, params=params, timeout=30) resp.raise_for_status() feed = feedparser.parse(resp.content) papers = [] cutoff = datetime.utcnow() - timedelta(hours=24) for entry in feed.entries: published = datetime.strptime(entry.published, "%Y-%m-%dT%H:%M:%SZ") if published < cutoff: continue papers.append({ "arxiv_id": entry.id.split("/")[-1], "title": entry.title.replace("\n", " ").strip(), "authors": ", ".join(a.name for a in entry.authors), "abstract": entry.summary.replace("\n", " ").strip(), "categories": ", ".join(t.term for t in entry.tags), "published_date": entry.published, "url": entry.link, "pdf_url": entry.link.replace("/abs/", "/pdf/") }) return papers这段代码有几个细节值得说。entry.id是类似http://arxiv.org/abs/2609.12345v1的格式,取最后一段就是论文 ID。entry.published是 UTC 时间,格式固定,直接strptime解析。entry.summary里可能有换行符,存库前统一替换成空格,避免后面显示的时候排版乱。
时间过滤这里用的是"最近 24 小时"。如果你希望更精确,比如"从昨天早上八点到今天早上八点",那就把cutoff改成固定时间点。但要注意时区问题,ArXiv 用的是 UTC,你本地可能是东八区,差 8 小时。我的做法是统一用 UTC 存库,显示的时候再转本地时间。
4.3 筛选与打分脚本
抓取完之后,跑筛选。核心是给每篇论文算一个相关度分数。
KEYWORD_WEIGHTS = { "detection": 3, "detector": 3, "detr": 3, "segmentation": 3, "segment": 2, "diffusion": 3, "ddpm": 3, "denoising": 2, "transformer": 2, "attention": 2, "few-shot": 3, "zero-shot": 3, "self-supervised": 2, "contrastive": 2, } def score_paper(paper): score = 0 title_lower = paper["title"].lower() abstract_lower = paper["abstract"].lower() for kw, weight in KEYWORD_WEIGHTS.items(): if kw in title_lower: score += weight * 2 if kw in abstract_lower: score += weight return score这个打分逻辑很简单,但够用。标题里出现关键词,权重翻倍,因为标题里的词通常代表论文的核心主题。摘要里出现,算基础权重。你可以根据自己的方向调整KEYWORD_WEIGHTS,比如你最近在做视频理解,就把 "video"、"temporal"、"tracking" 加进去。
打分之后按分数降序排,取前 20 篇作为"今日推荐"。剩下的存库但不推送,等你哪天想扩大范围的时候再翻。
4.4 推送与展示
推送方式看个人习惯。我试过几种:邮件、Telegram bot、本地生成 Markdown 文件。最后稳定在"本地生成 Markdown + 邮件推送摘要"。
本地生成 Markdown 的好处是,你可以用任何 Markdown 编辑器打开,加批注、加标签都方便。邮件推送的好处是,你在手机上也