每日ArXiv CV论文追踪:自动化抓取与邮件推送实战 1. 为什么我要做这个每日ArXiv CV论文追踪项目做计算机视觉方向的研究或者工程落地最怕的一件事不是代码写不出来而是你辛辛苦苦调了三个月的模型某天刷到一篇三个月前的论文发现人家早就把你想解决的问题用更优雅的方式做完了。这种“信息滞后”带来的重复劳动在CV这个迭代速度极快的领域里几乎是每个从业者都踩过的坑。我从几年前开始养成一个习惯每天早上到工位的第一件事不是打开IDE而是先过一遍ArXiv上cs.CV分类当天的新论文。但说实话手动刷ArXiv这件事体验非常糟糕——页面加载慢、列表信息密度低、标题和摘要混在一起、想快速筛选出跟自己方向相关的论文全靠肉眼扫。尤其是赶上投稿截止日期前后那几天一天能挂出来两三百篇光翻列表就要花掉大半个小时。所以我决定把这个流程自动化掉。这个项目的核心目标很简单每天定时抓取ArXiv cs.CV分类下的最新论文提取标题、作者、摘要、链接、发布时间等关键信息做初步的分类和关键词过滤然后以结构化的形式推送出来。它解决的不是什么高深的算法问题而是一个纯粹的“信息获取效率”问题。适合的人群也很明确CV方向的研究生、算法工程师、技术负责人以及任何需要持续跟踪视觉领域前沿动态的人。哪怕你只是刚入门的小白用这个工具也能快速建立起对领域动态的感知不至于在组会上被问到“最近有什么新工作”时一脸茫然。这篇文章我会把这个项目的完整设计思路、核心实现细节、实操步骤、踩过的坑全部摊开讲。不是那种“教你写个爬虫”的入门教程而是真正能每天稳定跑起来、经得起时间考验的工程方案。2. 整体架构设计与技术选型考量2.1 为什么选择ArXiv作为唯一数据源信息源的选择是这个项目第一个要回答的问题。CV领域的论文发布渠道其实不少除了ArXiv还有各大顶会的开放获取平台、各种学术社交网络、以及一些第三方的论文聚合站点。我最终只选了ArXiv原因有三。第一覆盖率高。现在CV领域几乎所有重要工作都会先在ArXiv上挂预印本顶会中稿的论文里绝大多数都能在ArXiv上找到对应版本。只盯ArXiv基本不会漏掉真正重要的东西。第二接口稳定且免费。ArXiv提供了官方的API查询接口支持按分类、日期、关键词等维度检索返回Atom格式的结构化数据。不需要登录不需要付费没有反爬虫的复杂机制对于个人项目来说非常友好。第三信息结构统一。每篇论文的元数据格式一致标题、作者、摘要、分类标签、提交时间都是标准字段解析起来没有歧义。相比之下有些聚合站点虽然界面好看但数据来源不透明字段缺失严重反而不如直接用原始数据自己处理。注意ArXiv的API有请求频率限制官方建议每次请求间隔至少3秒。这个限制在后面实现定时抓取的时候必须考虑进去否则容易被临时封禁。2.2 抓取方案API还是页面解析确定了数据源之后接下来要选抓取方式。ArXiv有两条路可以走一是调用官方API二是直接解析网页HTML。我一开始试的是页面解析因为觉得API返回的Atom格式处理起来麻烦。但很快发现页面解析有几个致命问题ArXiv的页面结构偶尔会调整每次改版都要重新适配解析规则列表页只显示标题和作者摘要需要点进详情页才能拿到这意味着每篇论文要多发一次请求一天两三百篇就是两三百次额外请求效率极低且容易触发限流。换成API之后这些问题全部消失。一次请求就能拿到一批论文的完整元数据包括摘要。返回格式虽然是XML但结构极其规整用现成的XML解析库几行代码就能提取出所有需要的字段。而且API支持分页可以通过调整每页数量来控制单次请求的数据量。最终我采用的是API为主、页面为辅的策略日常抓取全部走API只有在需要补充某些API不返回的字段比如论文的评论信息、期刊引用等时才针对性地解析单篇论文的页面。2.3 数据存储为什么不用数据库很多人做这类项目第一反应是上数据库MySQL或者MongoDB。我一开始也这么想过但仔细评估之后放弃了。原因很简单这个项目的数据量和查询需求根本用不上数据库。每天新增的CV论文大概在100到300篇之间一年下来也就几万篇。这个量级用JSON文件或者CSV文件存储完全够用读取速度也不慢。而且这个项目的数据使用场景主要是“按日期查询”和“按关键词搜索”前者用文件目录按日期组织就能解决后者用简单的文本匹配或者轻量级的全文检索库就能搞定。用文件存储还有一个好处可移植性强。整个项目的数据就是一个文件夹拷贝到任何地方都能直接用不依赖任何数据库服务。对于个人项目来说少一个依赖就少一个出问题的环节。我最终采用的存储结构是按日期分目录每天一个JSON文件文件名就是日期。每个JSON文件里是一个数组每篇论文是一个对象包含标题、作者列表、摘要、ArXiv ID、PDF链接、分类标签、提交时间等字段。这种结构简单直观想查哪天的数据直接打开对应的文件就行。2.4 推送方式为什么选邮件而不是即时通讯数据抓下来之后要推送给用户推送渠道的选择也很关键。常见的方案有邮件、即时通讯机器人、RSS订阅等。即时通讯机器人看起来最方便但实际用起来有几个问题一是消息长度限制一篇论文的摘要动辄两三百字一天几十篇论文根本发不完二是信息容易被聊天记录淹没过两天想回头找某篇论文的推送翻聊天记录能翻到崩溃三是对网络环境有要求不是所有场景下都能稳定收发消息。RSS订阅倒是没有长度限制但现在用RSS的人越来越少了而且RSS阅读器的体验参差不齐对新手不太友好。邮件是我最终选的方案。优势很明显没有长度限制可以把当天所有论文整理成一封结构清晰的邮件天然可归档邮件客户端自带搜索和分类功能想找历史推送随时能翻出来格式丰富支持HTML格式可以做成带目录、带分类、带关键词高亮的精美排版。而且邮件推送对网络环境的要求最低几乎在任何场景下都能正常接收。2.5 整体流程串联把上面的选型串起来整个项目的流程是这样的每天固定时间触发任务我用的是系统自带的定时任务工具调用ArXiv API拉取过去24小时内cs.CV分类下的新论文解析返回的XML数据提取每篇论文的元信息对论文做初步分类按子方向和关键词过滤按用户配置的关注词将处理后的数据存入按日期组织的JSON文件生成HTML格式的邮件内容发送到指定邮箱记录本次运行的日志方便排查问题整个流程没有任何复杂的依赖一台最基础的云服务器就能跑起来每个月的成本几乎可以忽略不计。3. 核心实现细节与关键代码解析3.1 ArXiv API的调用与参数配置ArXiv API的基础地址是http://export.arxiv.org/api/query支持GET请求核心参数有以下几个参数名作用常用取值search_query查询条件cat:cs.CV 表示CV分类start起始位置从0开始max_results单次返回数量建议不超过100sortBy排序字段submittedDatesortOrder排序方向descending我实际使用的查询语句是cat:cs.CVANDsubmittedDate:[开始时间TO结束时间]这样可以精确控制只拉取指定时间窗口内的论文。时间格式必须严格遵循YYYYMMDDHHMM的格式否则API会返回错误。这里有一个容易踩的坑ArXiv的提交时间用的是美国东部时间不是UTC也不是北京时间。如果你直接用本地时间拼接查询条件很可能会漏掉或者多拉一些论文。我的做法是统一用UTC时间做查询然后在后续处理时再转换成需要的时区。import urllib.request import urllib.parse from datetime import datetime, timedelta def build_arxiv_query(start_time, end_time): base_url http://export.arxiv.org/api/query search_query fcat:cs.CV AND submittedDate:[{start_time} TO {end_time}] params { search_query: search_query, start: 0, max_results: 100, sortBy: submittedDate, sortOrder: descending } return base_url ? urllib.parse.urlencode(params)3.2 XML解析与字段提取API返回的是Atom格式的XML结构大致如下根节点是feed里面包含多个entry节点每个entry对应一篇论文。需要提取的字段包括title论文标题注意要去掉换行符和多余空格author下的name作者列表可能有多个summary摘要同样需要清理格式idArXiv ID包含完整URLpublished首次提交时间updated最后更新时间category的term属性分类标签可能有多个link中titlepdf的hrefPDF链接解析的时候用Python标准库里的xml.etree.ElementTree就够了不需要额外装第三方库。命名空间是http://www.w3.org/2005/Atom解析时需要在标签名前加上命名空间前缀。import xml.etree.ElementTree as ET def parse_arxiv_response(xml_content): ns {atom: http://www.w3.org/2005/Atom} root ET.fromstring(xml_content) papers [] for entry in root.findall(atom:entry, ns): paper { title: entry.find(atom:title, ns).text.strip().replace(\n, ), authors: [a.find(atom:name, ns).text for a in entry.findall(atom:author, ns)], summary: entry.find(atom:summary, ns).text.strip().replace(\n, ), arxiv_id: entry.find(atom:id, ns).text.split(/abs/)[-1], published: entry.find(atom:published, ns).text, updated: entry.find(atom:updated, ns).text, categories: [c.get(term) for c in entry.findall(atom:category, ns)], pdf_link: } for link in entry.findall(atom:link, ns): if link.get(title) pdf: paper[pdf_link] link.get(href) papers.append(paper) return papers3.3 论文分类与关键词过滤逻辑抓下来的论文如果不做任何处理直接推送用户看到的就是一个几百篇论文的大列表跟直接刷ArXiv没有本质区别。所以必须做分类和过滤。分类这块我采用的是基于ArXiv分类标签的粗分类 基于标题关键词的细分类两层策略。ArXiv本身会给每篇论文打上多个分类标签比如cs.CV、cs.LG、cs.AI等但这些标签太粗了不足以区分CV内部的不同方向。所以我在粗分类的基础上再用标题中的关键词做一次细分。细分类的规则是我自己维护的一个关键词映射表大致长这样子方向关键词目标检测detection, detector, yolo, r-cnn, detr图像分割segmentation, mask, sam, segment图像生成diffusion, gan, generation, synthesis三维视觉3d, nerf, point cloud, depth, reconstruction视频理解video, temporal, tracking, action多模态clip, vision-language, multimodal, vlm这个表不是固定的你可以根据自己的研究方向随时增删。比如你主要做医学图像就可以加一个“医学影像”的分类关键词放medical、clinical、diagnosis等。关键词过滤则是另一套逻辑。分类是把论文归到不同的桶里过滤是从所有论文中挑出你特别关心的那几篇。我配置了一个“高优先级关键词”列表比如diffusion、transformer、zero-shot这些只要标题或摘要里出现了这些词这篇论文就会被标记为高优先级在邮件里用不同的颜色高亮显示。实操心得关键词匹配建议同时匹配标题和摘要但标题的权重应该更高。我的做法是标题命中算2分摘要命中算1分总分超过阈值的才标记为高优先级。这样可以避免一些摘要里随便提了一嘴关键词但实际不相关的论文被误标。3.4 邮件内容的HTML模板设计邮件是最终呈现给用户的东西排版质量直接决定了这个工具好不好用。我用的是内联CSS的HTML模板因为大多数邮件客户端对外部CSS和style标签的支持都不太好内联样式是最稳妥的方案。模板的整体结构是顶部是日期和统计信息今天共多少篇论文其中高优先级多少篇然后是按子方向分组的论文列表每组内部按高优先级排序。每篇论文显示标题带链接、作者只显示前三位后面用“等”代替、摘要截取前200字、以及分类标签。div stylefont-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; h2 stylecolor: #333; border-bottom: 2px solid #4a90d9; padding-bottom: 8px; 每日CV论文速递 - {date} /h2 p stylecolor: #666;今日共收录 strong{total}/strong 篇论文其中高优先级 strong{high_priority_count}/strong 篇。/p h3 stylecolor: #4a90d9; margin-top: 24px;{category_name}/h3 div styleborder-left: 3px solid #4a90d9; padding-left: 12px; margin-bottom: 16px; p stylefont-size: 16px; font-weight: bold; margin: 0; a href{pdf_link} stylecolor: #1a0dab; text-decoration: none;{title}/a /p p stylecolor: #888; font-size: 13px; margin: 4px 0;{authors}/p p stylecolor: #444; font-size: 14px; line-height: 1.6;{summary_short}/p /div /div摘要截取这里有个细节不能简单地按字符数截断否则可能截到一半单词或者半个句子。我的做法是先按句号分割然后逐句累加直到接近200字为止这样截出来的摘要至少是完整的句子。3.5 定时任务的配置与容错处理定时任务我用的是Linux系统自带的crontab配置一行就够了0 8 * * * /usr/bin/python3 /path/to/arxiv_daily.py /path/to/logs/cron.log 21这行配置的意思是每天早上8点执行一次脚本标准输出和错误输出都追加到日志文件里。但光有定时任务还不够必须考虑容错。我遇到过的情况包括API临时不可用返回502、网络超时导致请求中断、邮件发送失败、磁盘空间不足导致文件写入失败。这些情况如果不处理轻则当天没有推送重则脚本崩溃后后续几天都不执行。我的容错策略是三层保护第一层是请求重试。API调用失败时自动重试最多重试3次每次间隔递增3秒、9秒、27秒。如果3次都失败记录错误日志并跳过当天的抓取但不影响脚本正常退出。第二层是数据校验。抓取完成后检查论文数量如果为0或者明显异常比如超过1000篇说明可能出了问题不发送邮件只记录警告日志。第三层是邮件发送确认。邮件发送后检查返回状态如果发送失败将邮件内容保存为本地HTML文件作为备份同时记录错误日志。import time import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart def send_email_with_retry(subject, html_content, to_addr, max_retries3): for attempt in range(max_retries): try: msg MIMEMultipart(alternative) msg[Subject] subject msg[From] your_emailexample.com msg[To] to_addr msg.attach(MIMEText(html_content, html, utf-8)) with smtplib.SMTP_SSL(smtp.example.com, 465) as server: server.login(your_emailexample.com, your_password) server.sendmail(your_emailexample.com, to_addr, msg.as_string()) return True except Exception as e: print(f邮件发送失败第{attempt1}次{e}) time.sleep(3 ** (attempt 1)) return False4. 完整实操流程与部署指南4.1 环境准备与依赖安装这个项目对运行环境的要求极低一台1核1G的云服务器就绰绰有余。操作系统推荐Ubuntu 20.04或更高版本Python版本3.8以上即可。需要安装的Python第三方库只有两个requests用于发送HTTP请求beautifulsoup4用于在需要时解析HTML页面。其他功能全部用Python标准库实现。pip install requests beautifulsoup4如果你打算把数据存到数据库而不是文件那还需要装对应的数据库驱动。但我前面已经分析过了文件存储完全够用没必要引入额外依赖。4.2 配置文件的设计与参数说明我把所有可配置的参数都抽到了一个单独的配置文件里这样修改配置不需要动代码。配置文件用JSON格式结构如下{ arxiv: { category: cs.CV, max_results_per_request: 100, request_interval_seconds: 3, timezone_offset_hours: -4 }, filter: { high_priority_keywords: [diffusion, transformer, zero-shot, foundation model], title_weight: 2, summary_weight: 1, priority_threshold: 2 }, email: { smtp_server: smtp.example.com, smtp_port: 465, sender: your_emailexample.com, password: your_auth_code, recipients: [recipientexample.com] }, storage: { data_dir: ./data, log_dir: ./logs } }几个关键参数需要解释一下。timezone_offset_hours是ArXiv提交时间与你本地时间的时差美国东部时间在夏令时是UTC-4冬令时是UTC-5需要根据当前季节调整。priority_threshold是高优先级的判定阈值标题命中关键词得2分摘要命中得1分总分达到或超过这个值就标记为高优先级。注意邮箱密码这里填的不是你的登录密码而是邮箱服务商提供的授权码。大多数主流邮箱服务都支持生成专门的授权码用于第三方客户端登录比直接填密码安全得多。4.3 主流程脚本的编写与调试主流程脚本的逻辑很直白就是前面说的七步走。我把它写成了一个单独的Python文件结构上分成几个函数每个函数负责一个环节。def main(): config load_config(config.json) today datetime.now().strftime(%Y-%m-%d) # 第一步计算查询时间窗口 start_time, end_time calculate_time_window(config[arxiv][timezone_offset_hours]) # 第二步调用API抓取论文 papers fetch_papers(config, start_time, end_time) if not papers: log_warning(今日未抓取到任何论文跳过推送) return # 第三步分类和过滤 categorized categorize_papers(papers, config[filter]) # 第四步存储数据 save_to_json(categorized, config[storage][data_dir], today) # 第五步生成邮件内容 html_content generate_email_html(categorized, today) # 第六步发送邮件 success send_email_with_retry( f每日CV论文速递 - {today}, html_content, config[email][recipients][0] ) # 第七步记录日志 log_result(today, len(papers), success)调试的时候建议先把邮件发送那一步注释掉改成把HTML内容保存到本地文件用浏览器打开检查排版效果。确认没问题之后再开启邮件发送。4.4 部署上线与日常维护脚本调试通过之后部署到服务器上就三步把代码和配置文件上传到服务器、安装依赖、配置crontab定时任务。日常维护主要关注两件事一是检查日志看看有没有报错或者异常二是定期更新关键词列表随着你研究方向的调整关注的关键词也需要跟着变。我一般每周花五分钟看一眼日志确认过去一周每天都正常推送了。如果发现某天没有推送就去日志里找原因。最常见的问题是API临时不可用这种情况重跑一次脚本就好了。另外建议定期备份数据目录。虽然ArXiv上的论文不会消失但你自己的分类结果和过滤记录是有价值的万一服务器出问题不至于全部丢失。我用的是一个很简单的方案每周把数据目录打包压缩存到另一个地方。5. 常见问题排查与避坑经验实录5.1 抓取不到论文的几种原因这是最常见的问题表现是脚本正常运行但论文数量为0。可能的原因和排查方法如下现象可能原因排查方法返回结果为空时间窗口设置错误检查时区偏移量是否正确返回结果为空查询语句格式错误打印完整URL在浏览器中测试返回结果很少请求被限流检查请求间隔是否小于3秒返回结果重复时间窗口重叠确保每次查询的起止时间不重叠解析报错XML格式异常打印原始返回内容检查时区问题是我踩过最大的坑。有段时间我发现每天抓到的论文数量忽多忽少有时候甚至为0。排查了半天才发现是夏令时切换导致的——美国东部时间从UTC-5变成UTC-4但我的配置里还写着-5导致查询时间窗口偏移了一个小时正好错过了大部分论文的提交时间。5.2 邮件发送失败的排查思路邮件发送失败的原因通常比较明确错误信息里一般会直接告诉你问题出在哪。常见的几种情况认证失败检查邮箱地址和授权码是否正确注意授权码不是登录密码连接超时检查SMTP服务器地址和端口是否正确SSL和普通连接用的端口不一样被判定为垃圾邮件检查邮件内容是否包含敏感词或者发送频率是否过高收件人拒收检查收件地址是否正确有些邮箱会拦截来自陌生发件人的邮件实操心得如果你用的是主流邮箱服务建议在邮箱设置里把发件地址加入白名单这样可以大幅降低被判定为垃圾邮件的概率。另外邮件主题不要用太夸张的词汇朴实一点反而更稳定。5.3 关键词过滤的误判与调优关键词过滤最大的问题是误判。比如你设置了detection作为关键词结果一篇讲“边缘检测”的论文也被归到了目标检测分类里但实际上它跟目标检测没有半点关系。解决这个问题的办法有两个。一是使用更精确的关键词比如把detection改成object detection或者detector减少歧义。二是引入排除词比如设置了detection作为关键词的同时把edge detection、anomaly detection、change detection加入排除列表命中排除词的论文不参与该分类。我自己的关键词列表经过大半年的迭代已经从最初的十几个词精简到了现在的八个核心词。数量少了但准确率反而高了。与其追求大而全不如把最核心的几个方向盯紧。5.4 数据存储的容量规划与清理策略虽然每天的数据量不大但日积月累下来也需要考虑存储问题。我算过一笔账平均每篇论文的JSON数据大约2KB每天200篇就是400KB一年365天大约是146MB。这个量级对于任何服务器来说都微不足道。但如果你打算长期运行比如三五年那数据量就会到GB级别。这时候可以考虑定期归档把超过一年的数据打包压缩需要的时候再解压查看。或者只保留最近半年的详细数据更早的数据只保留标题和链接摘要等大字段删掉。我目前的策略是全部保留不做清理。因为数据量确实不大而且历史数据偶尔还会用到——比如想查某个方向半年前有没有类似的工作直接搜历史数据比重新去ArXiv上翻要快得多。5.5 提升推送质量的几个实用技巧最后分享几个让推送更有价值的小技巧都是实际用下来觉得效果不错的。第一个是加“相关论文推荐”。在每篇论文下面根据标题关键词匹配历史数据中相似度高的论文附上链接。这样你在看新论文的时候能顺便回顾一下之前的相关工作建立知识关联。第二个是加“趋势统计”。在邮件顶部放一个简单的统计比如“本周diffusion相关论文数量环比增长20%”让你对领域热度的变化有个直观感知。第三个是加“一键收藏”功能。在每篇论文旁边放一个链接点击后把这篇论文的ArXiv ID追加到一个收藏文件里。后续可以基于这个收藏列表做更深入的分析比如导出到文献管理工具。这些功能实现起来都不复杂但能让一个简单的论文推送工具变得真正好用。工具的价值不在于技术多先进而在于它能不能帮你节省时间、提升效率。这个项目从最初的几十行脚本迭代到现在核心逻辑没有变过变的是对细节的不断打磨。