ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

每日更新ArXiv CV论文:自动化抓取、过滤与推送实战

每日更新ArXiv CV论文:自动化抓取、过滤与推送实战 1. 这个每日更新项目到底在做什么每天早上八点半我习惯性地打开终端先跑一遍当天的ArXiv CV板块抓取脚本把新挂出来的论文标题、摘要、作者和PDF链接拉下来筛掉那些明显灌水的再把真正有意思的十几篇整理成一份清单发到几个小群里。这个动作我坚持了快两年中间踩过的坑、换过的方案、写废的脚本加起来能堆满一个仓库。今天就把这套“每日更新ArXiv CV Paper”的完整玩法拆开讲清楚从整体设计思路到具体代码再到每天实际跑起来会遇到什么幺蛾子全部摊开说。先说清楚这个东西是什么。ArXiv的计算机视觉板块cs.CV每天新增的论文数量大概在两百到四百篇之间浮动会议截稿前后会暴涨到六七百篇。靠人肉刷网页根本不现实所以需要一套自动化的流程定时抓取、结构化解析、关键词过滤、去重排序、格式化输出。这套流程做出来之后你每天早上花十分钟就能扫完当天所有值得看的CV论文而不是在ArXiv的列表页里翻到眼花。它解决的核心问题有三个。第一是信息过载cs.CV的new列表一天几百条没人有精力逐条看。第二是格式混乱ArXiv页面上的信息是给人看的不是给程序读的标题、作者、摘要混在一起直接复制粘贴到笔记里很难整理。第三是时效性CV领域变化太快今天挂出来的论文可能明天就被新的盖过去了手动追踪根本追不上。适合谁来参考如果你是在读研究生、算法工程师、或者任何需要持续跟进CV前沿的人这套东西能帮你省下大量时间。哪怕你只会一点点Python照着下面的步骤也能跑起来。2. 整体方案设计与技术选型思路2.1 为什么选ArXiv官方API而不是爬网页最开始我是直接爬ArXiv的列表页面的用requests加BeautifulSoup解析HTML里的条目。跑了不到一周就发现问题ArXiv的页面结构偶尔会微调class名一改整个解析逻辑就崩了。而且频繁请求列表页容易被限流有几次直接被返回403得等好一会儿才能恢复。后来换成了ArXiv官方的API接口地址是http://export.arxiv.org/api/query这个接口返回的是Atom格式的XML结构稳定得多。官方文档里写明了支持按分类、日期、关键词等条件查询还能分页。最关键的是这个接口对请求频率的容忍度比爬网页高不少只要不是疯狂并发基本不会触发限制。提示ArXiv官方建议请求间隔至少3秒实际测试下来单次抓取间隔保持在3到5秒之间比较稳妥。如果你要抓多天数据建议在循环里加time.sleep(3)。API返回的XML里每篇论文包含entry节点里面有title、summary摘要、author、published、updated、id链接、category等字段。解析起来很规整用feedparser或者直接xml.etree.ElementTree都能搞定。2.2 抓取策略按日期还是按分类ArXiv API支持两种主要的查询方式。一种是按分类查比如cat:cs.CV会返回该分类下最新的论文但默认是按提交时间倒序而且没有直接的日期过滤参数。另一种是用submittedDate字段做范围查询格式是[YYYYMMDDHHMM TO YYYYMMDDHHMM]这个更精确。我现在的做法是组合使用查询语句写成cat:cs.CV AND submittedDate:[202609210000 TO 202609212359]这样就能精确拿到某一天提交的所有cs.CV论文。注意ArXiv的提交时间是按美国东部时间算的所以如果你在北京时间早上跑拿到的其实是前一天美东时间的论文这个时差要心里有数。分页方面API用start和max_results两个参数控制。单次最多返回2000条但实际用下来一次请求拿100到200条比较稳太多了响应会变慢。如果当天论文超过200篇就分多次请求每次start递增。2.3 过滤与排序怎么从几百篇里挑出值得看的抓下来只是第一步真正的功夫在过滤。我的过滤逻辑分三层。第一层是关键词白名单和黑名单。白名单包括detection、segmentation、diffusion、transformer、3D、NeRF、SLAM、pose、tracking这些我关心的方向。黑名单则是survey、review、dataset这类我暂时不想看的类型当然这个因人而异。关键词匹配同时扫标题和摘要标题里命中的权重更高。第二层是去重。同一篇论文可能因为v1、v2版本更新而重复出现用ArXiv ID比如2409.12345做唯一键去重就行。另外有些论文会同时挂在cs.CV和cs.LG下面API查询时如果没限定分类可能会拿到重复条目所以查询语句里一定要带上cat:cs.CV。第三层是排序。我按“标题命中关键词数量 摘要命中关键词数量”算一个简单的相关性分数再结合提交时间做二级排序。这样每天早上打开清单最相关的排在最前面扫起来效率高很多。2.4 输出格式Markdown还是JSON输出格式取决于你用在哪里。如果只是自己看Markdown最舒服标题加粗、摘要缩进、链接直接可点。如果要喂给其他工具比如Notion、Obsidian或者自己的数据库那就输出JSON字段清晰后续处理方便。我现在的做法是两份都出一份Markdown发群里给人看一份JSON存本地做归档。Markdown的模板大概长这样### 2409.12345 - 论文标题 **作者**: 张三, 李四, 王五 **提交时间**: 2026-09-21 **摘要**: 这里是摘要的前200个字符... **链接**: https://arxiv.org/abs/2409.12345 **命中关键词**: detection, transformerJSON那边就是标准的字典结构id、title、authors、abstract、published、url、keywords几个字段。3. 核心代码实现与关键细节3.1 环境准备与依赖安装整套东西用Python写就行依赖不多。我习惯用虚拟环境避免污染系统Python。python3 -m venv arxiv_env source arxiv_env/bin/activate pip install requests feedparserrequests用来发HTTP请求feedparser用来解析Atom XML。如果你不想装feedparser用标准库的xml.etree.ElementTree也能做但feedparser对命名空间的处理更省心代码量少一半。注意feedparser在处理ArXiv返回的XML时偶尔会把author里的name解析成嵌套结构取作者名的时候要用entry.authors列表每个元素是字典取[name]字段。3.2 构造查询请求核心的请求函数大概是这样import requests import feedparser import time from datetime import datetime, timedelta ARXIV_API http://export.arxiv.org/api/query def fetch_papers(date_str, start0, max_results200): # date_str 格式: 20260921 query fcat:cs.CV AND submittedDate:[{date_str}0000 TO {date_str}2359] params { search_query: query, start: start, max_results: max_results, sortBy: submittedDate, sortOrder: descending } resp requests.get(ARXIV_API, paramsparams, timeout30) resp.raise_for_status() feed feedparser.parse(resp.text) return feed.entries这里有几个细节值得说。submittedDate的范围查询里时间格式是YYYYMMDDHHMM所以202609210000到202609212359覆盖了当天全天。sortBy设成submittedDatesortOrder设成descending这样最新的排前面。timeout30是必须的ArXiv偶尔响应慢不设超时程序会卡死。3.3 解析条目与字段提取拿到feed.entries之后逐条解析def parse_entry(entry): arxiv_id entry.id.split(/abs/)[-1] title entry.title.replace(\n, ).strip() abstract entry.summary.replace(\n, ).strip() authors [a[name] for a in entry.authors] published entry.published url entry.id categories [tag[term] for tag in entry.tags] return { id: arxiv_id, title: title, abstract: abstract, authors: authors, published: published, url: url, categories: categories }标题和摘要里经常有换行符ArXiv的XML里为了排版会插入\n直接存下来会很难看所以统一替换成空格。作者列表从entry.authors取每个元素是{name: ...}。分类从entry.tags取每个tag的term字段就是分类名比如cs.CV、cs.LG。3.4 关键词过滤与打分逻辑过滤函数我写得比较直白WHITELIST [detection, segmentation, diffusion, transformer, 3d, nerf, slam, pose, tracking, generation, recognition, captioning, vlm, llm] BLACKLIST [survey, review, benchmark dataset] def score_paper(paper): title_lower paper[title].lower() abstract_lower paper[abstract].lower() score 0 hits [] for kw in WHITELIST: if kw in title_lower: score 3 hits.append(kw) if kw in abstract_lower: score 1 if kw not in hits: hits.append(kw) for kw in BLACKLIST: if kw in title_lower or kw in abstract_lower: score - 5 paper[score] score paper[keywords] hits return paper标题命中一个关键词加3分摘要命中加1分黑名单命中扣5分。这个权重是我调了几次之后定下来的标题里出现的关键词通常更能代表论文的核心方向。最后按score降序排score相同的按提交时间排。3.5 去重与增量更新去重逻辑很简单维护一个已处理的ID集合import json import os SEEN_FILE seen_ids.json def load_seen(): if os.path.exists(SEEN_FILE): with open(SEEN_FILE, r) as f: return set(json.load(f)) return set() def save_seen(seen): with open(SEEN_FILE, w) as f: json.dump(list(seen), f) def deduplicate(papers, seen): new_papers [] for p in papers: base_id p[id].split(v)[0] # 去掉版本号 if base_id not in seen: new_papers.append(p) seen.add(base_id) return new_papers, seenArXiv的ID格式是2409.12345v1这样版本号在v后面。去重的时候把版本号去掉只留基础ID这样v1和v2不会重复。seen_ids.json每天更新跑久了这个文件会越来越大但几千个ID的JSON也就几百KB完全不用担心。3.6 输出生成Markdown与JSON双格式Markdown生成函数def to_markdown(papers, date_str): lines [f# ArXiv CV Paper 每日更新 - {date_str}\n] for p in papers: lines.append(f### {p[id]} - {p[title]}\n) lines.append(f**作者**: {, .join(p[authors][:5])}\n) lines.append(f**提交时间**: {p[published][:10]}\n) abstract_short p[abstract][:300] ... if len(p[abstract]) 300 else p[abstract] lines.append(f**摘要**: {abstract_short}\n) lines.append(f**链接**: {p[url]}\n) lines.append(f**命中关键词**: {, .join(p[keywords])}\n) lines.append(---\n) return \n.join(lines)JSON输出就直接json.dump(papers, f, ensure_asciiFalse, indent2)注意ensure_asciiFalse不然中文作者名会变成转义字符。4. 每日运行流程与自动化4.1 手动跑一遍的完整步骤如果你不想搞定时任务每天手动跑一遍也就几秒钟的事。完整流程是激活虚拟环境source arxiv_env/bin/activate运行主脚本python daily_arxiv.py脚本自动完成抓取、解析、过滤、去重、输出打开生成的arxiv_cv_20260921.md查看结果主脚本的入口大概长这样def main(): today datetime.now().strftime(%Y%m%d) all_papers [] start 0 while True: entries fetch_papers(today, startstart, max_results200) if not entries: break for e in entries: all_papers.append(parse_entry(e)) if len(entries) 200: break start 200 time.sleep(3) seen load_seen() scored [score_paper(p) for p in all_papers] new_papers, seen deduplicate(scored, seen) new_papers.sort(keylambda x: (-x[score], x[published])) save_seen(seen) md to_markdown(new_papers, today) with open(farxiv_cv_{today}.md, w) as f: f.write(md) with open(farxiv_cv_{today}.json, w) as f: json.dump(new_papers, f, ensure_asciiFalse, indent2) print(f共抓取 {len(all_papers)} 篇去重后新增 {len(new_papers)} 篇) if __name__ __main__: main()分页循环那里如果某次返回的条目数小于max_results说明已经到最后一页了直接break。每次请求之间sleep(3)避免触发限流。4.2 用cron做定时任务Linux和macOS下用cron最省事。打开crontab -e加一行30 8 * * * cd /path/to/arxiv_project /path/to/arxiv_env/bin/python daily_arxiv.py run.log 21这行配置的意思是每天早上8点30分执行脚本输出追加到run.log。时间点选在8点30是因为ArXiv的更新通常在美东时间凌晨到早上北京时间早上8点多正好能拿到前一天的新论文。注意cron的环境变量和你的shell环境不一样脚本里如果用到了相对路径一定要在cron命令里先cd到项目目录。另外虚拟环境的Python要用绝对路径不然cron找不到。4.3 用GitHub Actions做云端定时如果你不想本地开着机器跑GitHub Actions是个不错的选择。在仓库里建.github/workflows/daily.ymlname: Daily ArXiv CV on: schedule: - cron: 30 0 * * * # UTC 0:30对应北京时间 8:30 workflow_dispatch: jobs: fetch: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.11 - run: pip install requests feedparser - run: python daily_arxiv.py - run: | git config user.name github-actions git config user.email actionsgithub.com git add . git commit -m Update $(date %Y%m%d) || exit 0 git push这个配置会在每天UTC 0:30北京时间8:30自动跑跑完把生成的Markdown和JSON提交回仓库。workflow_dispatch那行是让你可以手动触发方便调试。4.4 输出推送到群聊或笔记工具生成的Markdown可以直接复制到微信、飞书或者Slack。如果要自动推送飞书和钉钉都有Webhook机器人发个POST请求就行。以飞书为例import requests def push_to_feishu(webhook_url, markdown_text): payload { msg_type: interactive, card: { header: {title: {tag: plain_text, content: 今日ArXiv CV更新}}, elements: [{tag: markdown, content: markdown_text[:4000]}] } } requests.post(webhook_url, jsonpayload)飞书卡片消息有长度限制太长的Markdown要截断或者分多条发。我一般只推score最高的前10篇完整清单还是存本地。5. 常见问题与排查实录5.1 抓取返回空结果怎么办最常见的原因是日期格式写错了。submittedDate的范围查询里时间必须是YYYYMMDDHHMM格式少一位或者多一位都会返回空。另外ArXiv的提交时间是美东时间如果你在北京时间早上查当天的日期可能美东那边还没到那个日期自然查不到。解决办法是往前推一天或者用datetime.now() - timedelta(hours12)来算日期。还有一种情况是查询语句里的AND和空格处理。ArXiv API对查询语句的解析比较严格cat:cs.CV AND submittedDate:[...]中间的空格不能少AND必须大写。如果写成cat:cs.CV and submittedDate会返回空结果。5.2 解析时作者名乱码或缺失ArXiv的XML里作者名有时候会包含特殊字符比如带重音的欧洲名字。feedparser默认会做Unicode处理但如果你用xml.etree.ElementTree手动解析要注意编码问题。另外有些论文的作者列表很长entry.authors可能只返回前几个这是ArXiv API的限制不是解析错误。如果作者名出现\n或者多余空格在parse_entry里统一做strip()和replace(\n, )处理。5.3 去重逻辑把新版本误杀了去重的时候我去掉了版本号只留基础ID。这会导致一个问题如果一篇论文昨天出了v1今天出了v2v2会被当成已读而跳过。对于追踪前沿来说v2通常只是小修小补跳过问题不大。但如果你特别关注某篇论文的更新可以在去重逻辑里加一个判断如果版本号比已记录的高就保留。def deduplicate_with_version(papers, seen_dict): new_papers [] for p in papers: base_id p[id].split(v)[0] version int(p[id].split(v)[-1]) if v in p[id] else 1 if base_id not in seen_dict or version seen_dict[base_id]: new_papers.append(p) seen_dict[base_id] version return new_papers, seen_dict这样seen_ids.json里存的就是{2409.12345: 2}这样的字典而不是简单的集合。5.4 关键词过滤太严或太松关键词列表需要根据你的研究方向调。我一开始白名单里放了network这个词结果几乎所有论文都命中了因为neural network太常见。后来把network去掉换成更具体的transformer、diffusion效果就好多了。黑名单也一样survey和review确实能过滤掉综述但有些标题里带benchmark的论文其实很有价值一刀切掉可惜。我的做法是黑名单只扣分不直接排除让排序来决定而不是硬性过滤。5.5 请求被限流或超时ArXiv API虽然没有严格的速率限制但短时间内大量请求还是会被暂时封禁。表现是返回403或者连接超时。解决办法就一个加sleep。每次请求之间至少间隔3秒分页请求之间间隔5秒。如果还是被封等15到30分钟再试。另外requests.get一定要设timeout不设的话遇到网络波动会一直卡着。我一般设30秒超时了就重试一次重试还失败就跳过这次请求记录到日志里。5.6 常见问题速查表问题现象可能原因解决办法返回空结果日期格式错误或时差问题检查YYYYMMDDHHMM格式日期往前推一天返回403请求频率过高增加sleep间隔等待15分钟再试作者名乱码编码问题统一用UTF-8处理strip()清理去重误杀新版本版本号被忽略改用字典记录版本号比较后保留关键词命中过多白名单词太泛去掉network等通用词换具体术语脚本卡死未设timeoutrequests.get加timeout30cron不执行环境变量或路径问题用绝对路径先cd到项目目录6. 实操心得与进阶玩法6.1 我踩过的几个坑第一个坑是时区。刚开始跑的时候我每天早上8点查当天的论文结果连续三天都是空的。后来才反应过来北京时间早上8点美东时间还是前一天晚上ArXiv那边当天的论文还没提交完。改成查前一天之后数据就正常了。第二个坑是XML命名空间。ArXiv返回的XML里entry节点带有命名空间直接用entry.title有时候取不到得用entry.find({http://www.w3.org/2005/Atom}title)。feedparser帮你处理了这层所以强烈建议用feedparser而不是手动解析。第三个坑是摘要里的LaTeX公式。ArXiv的摘要里经常有$...$包裹的数学公式直接输出到Markdown里会渲染混乱。我的做法是在parse_entry里把$替换成空字符串或者用反引号包起来。虽然损失了一些信息但可读性提升很多。6.2 怎么让过滤更精准关键词匹配是最简单的方法但不够聪明。进阶一点的做法是用句向量做语义相似度。把每篇论文的摘要用预训练模型比如Sentence-BERT编码成向量和你预先定义好的几个“兴趣向量”算余弦相似度超过阈值的才保留。这样能抓到那些标题里没出现关键词但内容相关的论文。不过这个方法需要额外装sentence-transformers和torch依赖比较重。如果你只是个人用关键词匹配加人工扫一眼完全够了。语义过滤适合论文量特别大、需要进一步压缩的场景。6.3 归档与回溯查询每天生成的JSON文件按日期命名存到一个archive/目录下。时间久了想回溯查某个关键词在哪天出现过用grep或者写个简单的Python脚本遍历所有JSON就行。import json import glob def search_archive(keyword): results [] for filepath in glob.glob(archive/*.json): with open(filepath, r) as f: papers json.load(f) for p in papers: if keyword.lower() in p[title].lower() or keyword.lower() in p[abstract].lower(): results.append((filepath, p[id], p[title])) return results这个回溯功能在写综述或者找某个方向的论文时特别有用比在ArXiv网站上搜要快得多。6.4 多分类扩展现在只抓了cs.CV如果你还关心cs.LG、cs.AI、cs.RO可以把查询语句改成cat:cs.CV OR cat:cs.LG OR cat:cs.RO然后在解析的时候把分类信息也存下来输出时按分类分组。不过这样每天的数据量会翻好几倍过滤和排序的压力也更大建议先从一个分类跑顺了再扩展。6.5 最后分享一个小技巧ArXiv的RSS订阅其实也能用地址是https://rss.arxiv.org/rss/cs.CV返回的是最近几天的论文。RSS的好处是格式更简单解析起来比API还省事。但RSS只保留最近几天没法按日期精确查询也没法分页。我的做法是API和RSS互为备份API挂了就切RSS应急虽然数据不全但至少不会断更。这套东西跑顺了之后每天早上花十分钟扫一遍清单一个月下来能筛出二三十篇真正值得精读的论文。比起在ArXiv列表页里漫无目的地翻效率提升不是一点半点。你要是也在追CV前沿建议先把基础版本跑起来再根据自己的研究方向慢慢调关键词和过滤逻辑。
返回列表