☰
基于arXiv API与大模型的论文自动抓取筛选日报流水线实战
2026/10/15 7:53:23 网站建设 项目流程

1. 从一篇“每日论文分析报告”说起:为什么我要把 arXiv 刷成流水线

每天早上八点半,我的邮箱里会准时躺着一封自己写给自己的邮件,标题格式固定是“arXiv 每日论文分析报告 YYYY-MM-DD”。2026 年 9 月 21 日这一期,我照例在通勤地铁上翻完,顺手把三篇可能影响手头项目的论文丢进了待读清单。这个习惯我坚持了快四年,从最初手动刷列表、复制标题到 Excel,到现在全流程自动化,中间踩过的坑足够写一本小册子。

先把话说清楚:这篇东西不是教你“如何一天读完一百篇论文”,那是自欺欺人。我要分享的是一套把 arXiv 每日新增论文自动抓取、粗筛、精读、归档、生成分析报告的完整工作流。它解决的核心问题是——信息过载下的“漏读焦虑”。做研究、做工程、做投资的人都知道,arXiv 每天新增的论文量早就不是人力能覆盖的,光是 cs.CL、cs.CV、cs.LG 这几个分类,一天几百篇是常态。你不可能全读,但你怕漏掉那篇真正重要的。

这套流程适合谁?三类人。第一类是研究生和科研人员,需要持续跟踪某个细分方向的进展,又不想被无关论文淹没;第二类是算法工程师和产品经理,要判断某个技术是不是已经成熟到可以落地,得看最新的一手资料;第三类是技术投资和行业分析从业者,需要从论文密度和主题迁移里读出趋势信号。哪怕你只是“arXiv 怎样订阅”这种最基础的问题还没搞明白,看完也能直接上手。

我用的工具链很朴素:Python 做抓取和解析,arXiv 官方 API 做数据源,本地 SQLite 做存储,大模型做摘要和分类,最后用 Markdown 模板渲染成报告。整套东西跑在一台常年开机的迷你主机上,成本几乎为零。下面我把设计思路、核心细节、实操过程和踩坑记录一层层拆开讲,你可以直接抄作业,也可以按自己的需求裁剪。

2. 整体设计与思路拆解:为什么是“抓取—粗筛—精读—归档”四段式

2.1 先想清楚:你要的是“全”还是“准”

很多人一上来就想做一个“全网最全的 arXiv 监控”,结果抓了几万篇,打开一看全是噪声,最后连报告都懒得点开。我在早期也犯过这个错,第一版脚本把 cs 下所有分类全量拉下来,一天两千多篇,生成的报告有八十多页,读了三天就放弃了。后来我才想明白一个道理:日报的价值不在于覆盖多少,而在于命中率。一份每天只推 5 篇、但其中 3 篇跟你相关的报告,远比一份推 200 篇、你只扫一眼标题的报告有用。

所以整套设计的第一原则是漏斗式收敛。arXiv 官方 API 支持按分类、关键词、日期范围检索,我把它当成第一道粗筛网;然后用本地规则(关键词白名单、作者关注列表、机构过滤)做第二道;再用大模型做语义相关性打分,这是第三道;最后人工精读,这是第四道。每一道都在减少数量、提升质量,最终进入报告的通常只有 5 到 15 篇。

2.2 为什么选 arXiv 官方 API 而不是爬网页

这是被问得最多的问题。arXiv 的网页版当然可以爬,但我不推荐,原因有三个。第一,网页结构会变,今天能用的选择器明天可能就失效,维护成本高;第二,网页抓取频率高了容易被限流甚至封 IP,而官方 API 有明确的调用规范,稳定得多;第三,API 返回的是结构化数据,标题、作者、摘要、分类、提交时间都是干净的字段,省去大量解析工作。

arXiv API 的入口是http://export.arxiv.org/api/query,支持search_query、start、max_results、sortBy、sortOrder这些参数。比如要查 2026 年 9 月 21 日提交到 cs.CL 的论文,可以这样构造查询:

import urllib.parse base = "http://export.arxiv.org/api/query?" params = { "search_query": "cat:cs.CL AND submittedDate:[202609210000 TO 202609212359]", "start": 0, "max_results": 200, "sortBy": "submittedDate", "sortOrder": "descending", } url = base + urllib.parse.urlencode(params)

这里有个细节要注意:arXiv 的submittedDate用的是 UTC 时间,而且格式是YYYYMMDDHHMM,没有分隔符。如果你在北京时间早上跑,想抓“昨天”的论文,得把时区换算清楚,否则会漏掉一批。我一般统一按 UTC 处理,报告里再标注“数据截止 UTC 某时”。

2.3 大模型在流程里扮演什么角色

大模型不是用来“读论文”的,至少在我的流程里不是。它的核心作用是做语义粗筛和结构化摘要。具体来说,我把每篇论文的标题和摘要喂给模型,让它输出三样东西:一个 0 到 10 的相关性分数、一句话的核心贡献、以及三个关键词标签。这样我扫报告的时候,先看分数和标签,感兴趣再点进去看原文。

为什么不直接让模型写详细解读?因为摘要本身信息量有限,模型很容易“脑补”出原文没有的结论,这在科研场景里是致命的。我试过让模型基于摘要写 500 字解读,结果十篇里有三篇出现了事实性错误,比如把“在某个数据集上提升 2%”说成“刷新了 SOTA”。所以我的原则是:模型只做归纳和分类,不做推断和评价。真正的判断留给人。

2.4 存储和归档为什么用 SQLite 而不是云服务

有人建议用 Notion、飞书多维表格或者各种笔记软件做归档,我都试过,最后回到了 SQLite。原因很简单:数据主权和查询自由。SQLite 是一个单文件数据库,备份就是复制一个文件,迁移就是拷走,不依赖任何平台。而且我可以写任意复杂的 SQL 查询,比如“找出过去三个月里所有提到某个方法且相关性分数大于 7 的论文”,这在笔记软件里要么做不到,要么很别扭。

表结构我设计得很简单,主表papers存论文元数据,副表scores存模型打分结果,副表notes存我自己的阅读笔记。三张表用 arXiv ID 关联。这样即使模型换了、报告模板换了,历史数据依然完整可用。

3. 核心细节解析与实操要点:抓取、解析、打分、渲染

3.1 arXiv API 的调用规范和限流处理

arXiv 官方对 API 的调用有明确建议:每次请求间隔至少 3 秒,不要并发轰炸。我见过有人写多线程抓取,结果被临时封禁,得不偿失。我的做法是单线程顺序请求,每次请求后time.sleep(3),虽然慢一点,但稳定。一天几百篇论文,分页拉取也就十几次请求,几分钟就跑完了。

分页参数要特别注意。max_results单次最大可以设到 2000,但我不建议设太大,因为响应体很大,解析慢,而且一旦中途失败要重来。我一般设max_results=100,然后用start参数翻页,直到返回结果为空或者提交时间超出目标日期。

还有一个坑是查询语法。arXiv API 的search_query用的是类似 Lucene 的语法,AND、OR、ANDNOT必须大写,字段名和值之间用冒号。日期范围用方括号,TO必须大写。我早期因为把and写成小写,查出来的结果完全不对,排查了半天才发现是语法问题。

3.2 解析 Atom XML 的注意事项

arXiv API 返回的是 Atom 格式的 XML,用feedparser或者xml.etree.ElementTree都能解析。我用的是后者,因为不想引入额外依赖。解析时有几个字段容易踩坑:

  • ID 字段:返回的是完整 URL,形如http://arxiv.org/abs/2609.12345v1,需要截取出2609.12345作为主键,版本号v1单独存,因为同一篇论文可能有多个版本。
  • 作者字段:<author><name>里是作者名,但格式不统一,有的是“姓 名”,有的是“名 姓”,做作者过滤时要模糊匹配。
  • 分类字段:<arxiv:primary_category>是主分类,<category>可能有多个,做分类统计时要用主分类。
  • 摘要字段:<summary>里的文本会有换行和多余空格,需要strip()和replace('\n', ' ')清洗。

下面是我实际用的解析片段:

import xml.etree.ElementTree as ET ns = { "atom": "http://www.w3.org/2005/Atom", "arxiv": "http://arxiv.org/schemas/atom", } def parse_entry(entry): raw_id = entry.find("atom:id", ns).text arxiv_id = raw_id.split("/abs/")[-1].split("v")[0] title = entry.find("atom:title", ns).text.strip().replace("\n", " ") summary = entry.find("atom:summary", ns).text.strip().replace("\n", " ") published = entry.find("atom:published", ns).text primary = entry.find("arxiv:primary_category", ns).attrib.get("term", "") authors = [a.find("atom:name", ns).text for a in entry.findall("atom:author", ns)] return { "arxiv_id": arxiv_id, "title": title, "summary": summary, "published": published, "primary_category": primary, "authors": authors, }

3.3 相关性打分的提示词设计

这是整套流程里最需要反复调优的部分。我前后改了十几版提示词,最终稳定下来的版本大概是这样的:

你是一个科研文献筛选助手。下面是一篇论文的标题和摘要。请判断它与“大语言模型推理效率优化”这个研究方向的关联程度,输出一个 0 到 10 的整数分数,10 表示高度相关,0 表示完全无关。同时用一句话概括论文的核心贡献,并给出三个关键词标签。输出格式为 JSON:{"score": 整数, "contribution": "一句话", "tags": ["标签1", "标签2", "标签3"]}。只输出 JSON,不要有其他内容。

这里的关键是研究方向要具体。如果你写“人工智能”,那所有论文都相关;如果你写“大语言模型推理效率优化”,模型才能做出有区分度的判断。我建议每个人根据自己的方向定制这句话,而且可以准备多个方向,让同一篇论文对多个方向分别打分。

还有一个技巧是给模型几个示例。我在提示词里放了两三个“高分样例”和“低分样例”,模型的打分稳定性明显提升。这其实就是少样本学习,虽然现在模型能力很强,但在打分这种需要一致性的任务上,示例依然有效。

3.4 报告模板的设计原则

报告是给人看的,所以可读性第一。我的模板结构是这样的:顶部是日期和统计概览(今日抓取总数、各分类分布、进入报告的数量),然后是分方向的论文列表,每篇论文包含标题、作者、arXiv 链接、相关性分数、一句话贡献、关键词标签,最后是我自己的阅读笔记区域(留空,手填)。

Markdown 模板用 Jinja2 渲染,这样格式调整很方便。我特意把每篇论文的区块做成可折叠的(用<details>标签),这样报告长了也不会太乱。虽然有些 Markdown 渲染器不支持折叠,但在主流编辑器里都能正常显示。

提示:报告里一定要放 arXiv 原文链接,而且要用https://arxiv.org/abs/的格式,不要用 API 返回的http://链接,前者是规范的论文页面,后者是接口地址。

4. 实操过程与核心环节实现:从零搭一套日报流水线

4.1 环境准备和依赖安装

整套流程对硬件要求极低,一台能跑 Python 的机器就行。我用的是迷你主机,4 核 8G 内存,跑这套东西绰绰有余。依赖也很少:

pip install requests feedparser jinja2 openai

requests用来调 API 和模型接口,feedparser其实可以不用(我用的是标准库),jinja2做模板渲染,openai是模型 SDK(如果你用其他模型,换成对应的 SDK 即可)。数据库用 Python 内置的sqlite3,不需要额外安装。

如果你想让流程每天自动跑,Linux 下用cron,Windows 下用“任务计划程序”。我是在 Linux 上跑的,crontab 配置大概是这样的:

30 1 * * * cd /home/user/arxiv-daily && /usr/bin/python3 main.py >> run.log 2>&1

这里30 1是 UTC 时间凌晨 1 点半,对应北京时间早上 9 点半,正好在我上班前跑完。注意 cron 的环境变量和交互式 shell 不一样,Python 路径最好写绝对路径,否则可能找不到。

4.2 抓取模块的完整实现

抓取模块的核心逻辑是:根据目标日期构造查询,分页拉取,解析,去重,入库。去重很关键,因为 arXiv 的论文可能因为时区原因出现在两天的结果里,用arxiv_id做唯一索引就能解决。

import sqlite3 import time import requests import xml.etree.ElementTree as ET def fetch_papers(target_date): base = "http://export.arxiv.org/api/query?" query = f"cat:cs.CL AND submittedDate:[{target_date}0000 TO {target_date}2359]" start = 0 all_papers = [] while True: params = { "search_query": query, "start": start, "max_results": 100, "sortBy": "submittedDate", "sortOrder": "descending", } resp = requests.get(base, params=params, timeout=30) root = ET.fromstring(resp.content) entries = root.findall("atom:entry", ns) if not entries: break for entry in entries: all_papers.append(parse_entry(entry)) start += 100 time.sleep(3) return all_papers

入库时用INSERT OR IGNORE,配合arxiv_id的唯一索引,重复的自动跳过。这样即使脚本重跑,也不会产生脏数据。

4.3 打分模块的批处理和成本控制

模型调用是唯一有成本的部分,所以要精打细算。我的做法是先本地粗筛,再模型精筛。本地粗筛用关键词白名单,比如我的方向是推理效率,白名单里有“inference”“latency”“throughput”“quantization”“distillation”“KV cache”这些词,标题或摘要命中任意一个才进入模型打分环节。这一步能把候选从几百篇降到几十篇,模型调用成本直接降一个数量级。

模型调用时,我把多篇论文的标题和摘要拼成一个批次,一次请求处理 5 到 10 篇,这样比逐篇调用省很多 token。但要注意,批次太大模型容易“串味”,把不同论文的信息混在一起。我实测 5 篇一批比较稳,再多就开始出错了。

def score_batch(papers, direction): prompt = build_prompt(papers, direction) resp = client.chat.completions.create( model="your-model", messages=[{"role": "user", "content": prompt}], temperature=0, ) return parse_json_response(resp.choices[0].message.content)

temperature=0是为了保证打分一致性,同样的输入每次输出应该一样。如果模型支持 JSON 模式,一定要开启,能省去大量解析容错代码。

4.4 报告渲染和分发

渲染用 Jinja2,模板文件单独放,方便改。渲染完的 Markdown 文件按日期命名,存到reports/目录。分发我用的是邮件,Python 的smtplib就能搞定,把 Markdown 转成 HTML 塞进邮件正文。如果你不想用邮件,也可以推到自己的笔记软件或者生成静态网页。

这里有个小技巧:报告里加一个“与昨日对比”的区块,显示今天新增的论文里有哪些主题是昨天没出现的。这个信号很有价值,往往意味着某个方向突然热起来了。实现方式就是查数据库,对比两天的关键词标签分布。

5. 常见问题与排查技巧实录:那些我踩过的坑

5.1 抓不到论文?先查时区和查询语法

最常见的故障是“今天怎么一篇都没抓到”。排查顺序是这样的:第一,确认目标日期是不是 UTC 日期,如果你在北京时间 9 月 21 日早上跑,想抓的其实是 UTC 9 月 20 日的数据;第二,检查search_query的语法,AND和TO必须大写,日期格式必须是YYYYMMDDHHMM;第三,手动在浏览器里访问构造好的 URL,看看返回的 XML 里有没有entry节点。这三步走完,九成问题都能定位。

5.2 模型打分不稳定怎么办

如果你发现同一篇论文今天打 8 分明天打 3 分,说明提示词不够明确。解决办法有三个:一是把研究方向描述得更具体,加上“只考虑方法层面的创新,不考虑应用层面的改进”这类限定;二是加少样本示例,给模型看几个明确的打分案例;三是把temperature设为 0,并且固定模型版本,不要用会自动更新的接口。

还有一个隐蔽的坑是摘要截断。有些论文摘要很长,超过模型的上下文窗口,如果不做处理直接截断,模型可能只看到前半部分,打分自然不准。我的做法是摘要超过 1500 字符就截断,并在末尾加“……”提示模型这是不完整的。

5.3 报告越来越长,读不动了

这是“幸福的烦恼”,说明你的筛选还不够严。我的经验是,进入报告的论文数量应该控制在 15 篇以内,超过这个数,阅读就变成负担了。如果发现报告变长,先看是不是相关性阈值设低了,把分数门槛从 6 分提到 7 分试试;如果还是多,说明你的研究方向太宽,需要拆成更细的子方向,每天轮换着看。

下面这张表是我整理的常见问题速查表,遇到故障可以直接对照:

现象可能原因排查方法解决方式
抓取结果为空时区错误或语法错误手动访问构造的 URL修正日期或大写关键字
论文重复入库缺少唯一索引查arxiv_id是否重复加UNIQUE约束
模型打分波动大提示词模糊或温度过高同一输入跑三次对比加示例、设temperature=0
报告渲染报错模板变量缺失看报错行号补默认值或改模板
邮件发送失败SMTP 配置或频率限制看异常信息换端口或加延时

5.4 关于“arXiv 怎样订阅”这件事

经常有人问订阅的问题。arXiv 本身提供邮件订阅功能,在分类页面上可以订阅每日更新,但那个是全量推送,一个分类一天几十上百封,基本没法看。我的建议是:如果你只是想“知道有这个功能”,用官方订阅就够了;但如果你真的想高效跟踪,还是得自己搭一套筛选流程,哪怕是最简单的关键词过滤,也比全量推送强得多。

另外,arXiv 有官方的 RSS 源,格式是https://arxiv.org/rss/cs.CL,用任意 RSS 阅读器都能订阅。RSS 的好处是不用写代码,坏处是同样没有筛选,而且更新有延迟。我早期用过一段时间 RSS,后来还是回到了 API 方案,因为可控性完全不是一个级别。

6. 让这套流程真正跑起来的几个心得

6.1 从最小可用版本开始,别一上来就追求完美

我见过太多人想做一个“终极版论文助手”,结果卡在架构设计上,一个月都没跑起来。我的建议是:第一天就写一个 20 行的脚本,能抓取、能打印标题就行。然后第二天加数据库,第三天加模型打分,第四天加报告渲染。每加一个功能,你都能立刻看到效果,这种正反馈才能让你坚持下去。我那套系统现在看起来很完整,但第一版就是一个requests.get加print,简陋得不行。

6.2 相关性方向要定期回顾和调整

研究方向不是一成不变的。我每季度会回顾一次过去三个月的报告,看看哪些论文我实际读了、哪些直接跳过了。如果某个关键词标签下的论文我从来没点开过,说明这个方向要么不重要,要么已经饱和了,就该从提示词里去掉。反过来,如果某个新词频繁出现且我每次都点开,就该把它加进白名单。这套流程是活的,需要跟着你的研究兴趣一起进化。

6.3 数据备份比什么都重要

SQLite 虽然简单,但也是单文件,硬盘坏了就全没了。我的做法是每天报告生成后,自动把数据库文件复制一份到另一个目录,按日期命名,保留最近 90 天。同时每周把数据库同步到一块移动硬盘上。这些操作用一个简单的 shell 脚本就能完成,成本几乎为零,但关键时刻能救命。我有个朋友就是因为没备份,换电脑时丢了两年的阅读记录,重新积累的代价太大了。

6.4 别让工具绑架你的阅读

最后说一个心态问题。这套流程的目的是帮你省时间,不是让你花更多时间在工具上。我有一段时间沉迷于优化脚本,今天加个新功能,明天调个新模型,结果真正读论文的时间反而少了。后来我给自己定了个规矩:工具每周最多维护一次,每次不超过一小时。其余时间,报告推什么就读什么,读完就关掉。工具是手段,读论文才是目的,这个主次关系不能颠倒。

2026 年 9 月 21 日这一期报告里,有一篇关于推理缓存压缩的论文我印象很深,相关性打了 9 分,读完发现思路确实巧妙,已经在我们一个内部项目里做了验证。这种“从日报里挖到宝”的时刻,就是这套流程最大的价值。它不会让你变成全知全能,但能让你在信息洪流里,稳稳地抓住那几根真正重要的稻草。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询