1. 从“学术速递”这个动作说起:为什么信息筛选比信息获取更难
每天早上刷一遍学术平台的新论文列表,这件事我坚持了快三年。最开始是手动刷,后来用订阅工具,再后来自己写脚本抓取。折腾一圈下来发现,真正的痛点从来不是“拿不到论文”,而是“拿到太多论文”。一个主流预印本平台单日新增投稿量在几百到上千篇之间浮动,就算只盯着自己关心的三五个方向,一天也能刷出几十条更新。这个量级下,逐条点开看摘要是不现实的,但完全不看又怕漏掉真正重要的东西。
“人工智能学术速递[2.25]”这个标题,本质上描述的就是一个信息筛选与分发的动作——把某一天(2月25日)人工智能领域的新论文、新动态,经过一轮筛选和整理,以速递的形式呈现出来。它解决的核心问题是:在信息过载的环境下,如何用最低的时间成本获取最高价值密度的学术动态。适合谁来参考?我觉得三类人最需要:一是刚进入AI方向的研究生,需要快速建立对领域动态的感知;二是工业界做算法落地的工程师,需要跟踪与自己业务相关的最新方法;三是跨方向的研究者,想看看别的子领域在发生什么但没时间深挖。
这篇博文我不打算只讲“怎么订阅”,而是把整个速递系统的搭建逻辑拆开——从信息源的选取、筛选规则的制定、摘要的生成方式,到最终呈现格式的设计。这套东西我前后迭代了四五个版本,踩过的坑不少,有些看起来合理的方案实际跑起来完全不是那么回事。下面按模块展开。
2. 速递系统的整体设计思路与方案选型
2.1 为什么不做“全量推送”而做“精选速递”
最开始我尝试过全量推送:把某个方向当天所有新论文的标题和摘要直接汇总发出来。结果自己都不想看——信息密度太低,大量论文标题一看就跟自己的研究无关。后来改成按关键词过滤,又出现另一个问题:关键词匹配太死,很多相关但用词不同的论文被漏掉。比如我关注“扩散模型”,但有些论文写的是“denoising diffusion probabilistic models”,有些写的是“score-based generative models”,纯关键词匹配覆盖不全。
最终确定的方案是“粗筛+精排”两层结构。粗筛用较宽的关键词集合和分类标签做召回,保证不漏;精排用轻量级的相关性打分做排序,保证靠前的内容质量。这个思路跟推荐系统里的召回-排序架构是一样的,只不过规模小得多,单机跑就够。
注意:粗筛阶段宁可多召回也不要漏,精排阶段再收紧。很多人反过来做,粗筛用很严的条件,结果精排没有素材可用。
2.2 信息源的选取:不只盯着一个平台
学术速递的信息源不能只依赖单一平台。我的做法是至少覆盖三类来源:预印本平台(更新最快,但未经同行评审)、顶会顶刊的录用列表(质量有保证,但有滞后)、以及领域内几个高质量实验室的官方发布渠道(往往有代码和详细说明)。
预印本平台的优势是时效性,一篇论文从投稿到上线可能就一两天,适合追踪最新方法。缺点是质量参差不齐,需要自己有判断力。顶会顶刊的录用列表通常滞后三到六个月,但经过评审筛选,可信度更高。实验室官方渠道的好处是经常附带代码链接和实验细节,对复现特别有帮助。
三类来源的权重我一般这样分配:预印本占60%,会议期刊占25%,实验室渠道占15%。这个比例不是固定的,赶在顶会截稿前后,预印本的比例会更高;顶会放榜之后,会议期刊的比例会临时上调。
2.3 筛选规则的制定:关键词集合的动态维护
关键词集合不是一成不变的。我维护了一个主关键词列表和一个辅助关键词列表。主关键词是核心研究方向,比如“目标检测”“语义分割”“多模态对齐”这类。辅助关键词是方法层面的,比如“注意力机制”“对比学习”“知识蒸馏”。两者取交集或并集,根据当天召回量动态调整。
具体规则是这样的:如果当天召回论文少于20篇,就用主关键词的并集,放宽条件;如果在20到80篇之间,用主关键词交集辅助关键词;如果超过80篇,只保留主关键词交集,并且加上时间衰减因子——越新的论文权重越高。
这个动态调整的逻辑是手动设定的阈值,跑了一段时间发现比固定规则好用很多。因为学术动态有明显的周期性,截稿前投稿量暴增,放榜后讨论量暴增,固定规则要么漏要么溢。
2.4 呈现格式的设计:让人三秒内决定要不要点开
速递的呈现格式直接决定了阅读效率。我试过纯标题列表、标题加摘要、标题加一句话总结三种形式。纯标题列表信息量太少,很多标题看不出所以然;标题加摘要太长,刷一遍要十几分钟;标题加一句话总结是折中方案,一句话控制在30字以内,说清楚“做了什么”和“为什么值得看”。
一句话总结的生成方式,早期是人工写,后来尝试用抽取式摘要自动生成,效果不太理想——抽取出来的句子经常缺少上下文。现在用的是“模板+关键信息填充”的方式:从摘要里提取方法名称、任务类型、核心贡献三个要素,套进固定句式。比如“提出XX方法,用于XX任务,在XX数据集上取得XX效果”。这种方式生成的总结虽然不够灵活,但胜在稳定,不会出现语义不通的情况。
3. 核心细节解析与实操要点
3.1 信息抓取:接口调用与页面解析的取舍
抓取预印本平台的数据有两条路:走官方接口或者解析页面。官方接口的好处是数据结构化、稳定、有明确的字段定义;缺点是更新有延迟,有时候论文上线几个小时了接口还没同步。页面解析的好处是实时性强,论文一上线就能抓到;缺点是页面结构一变解析就失效,维护成本高。
我的做法是两者结合:日常用接口做批量抓取,每隔几小时跑一次;对时效性要求高的方向,额外加一个页面解析的通道做补充。接口返回的数据里,标题、作者、摘要、分类标签这些字段都有,直接入库就行。页面解析主要抓接口里没有的字段,比如某些平台会显示论文的讨论热度或者引用情况。
代码层面,抓取部分用Python写,核心就是requests加解析库。接口调用注意控制频率,一般平台都有速率限制,超了会被临时封禁。我的经验是每次请求间隔至少1秒,批量抓取时用队列控制并发数,不要超过3个并发。
import time import requests from queue import Queue from threading import Thread def fetch_paper(paper_id, result_queue): url = f"https://example-api.org/paper/{paper_id}" try: resp = requests.get(url, timeout=10) if resp.status_code == 200: result_queue.put(resp.json()) except Exception as e: print(f"fetch failed: {paper_id}, {e}") time.sleep(1) # 控制频率 def batch_fetch(paper_ids): q = Queue() threads = [] for pid in paper_ids: t = Thread(target=fetch_paper, args=(pid, q)) t.start() threads.append(t) if len(threads) >= 3: # 最多3个并发 for t in threads: t.join() threads = [] for t in threads: t.join() results = [] while not q.empty(): results.append(q.get()) return results提示:抓取频率控制是长期稳定运行的关键。我见过有人为了追速度把并发开到几十,结果IP被限了整整一周,得不偿失。
3.2 相关性打分:轻量级方案比复杂模型更实用
精排阶段需要一个相关性打分函数。我试过用预训练语言模型做语义相似度计算,效果确实好,但速度太慢——单篇论文打分要几百毫秒,一天几百篇就是几分钟,加上模型加载和内存占用,在个人设备上跑不划算。后来换成TF-IDF加余弦相似度的方案,速度快了两个数量级,效果差距在可接受范围内。
具体做法是:把用户的研究兴趣描述(比如一段话说明自己在做什么方向)和论文摘要分别向量化,计算余弦相似度。TF-IDF的优点是无需训练、可解释性强、对关键词敏感;缺点是无法捕捉语义层面的相似。为了弥补这个不足,我在关键词层面做了扩展——维护一个同义词表,把“扩散模型”和“去噪扩散概率模型”这类同义表达映射到同一个词条上。
打分公式大概是这样的:最终得分 = 0.6 × TF-IDF相似度 + 0.3 × 关键词命中率 + 0.1 × 时效性因子。时效性因子按论文上线时间算,24小时内为1.0,一周内线性衰减到0.5,超过一周的固定0.3。这个权重分配是试出来的,不同方向可以微调。
3.3 一句话总结的生成:模板法的稳定性优势
前面提到一句话总结用模板法生成。具体实现是从摘要中抽取三个要素:方法名称、任务类型、核心结果。方法名称的抽取靠模式匹配,常见模式有“we propose”“we present”“this paper introduces”后面跟的名词短语。任务类型从论文的分类标签和摘要中的任务关键词提取。核心结果从摘要中的数值描述提取,比如“achieves XX% on YY dataset”。
模板句式我准备了五六个变体,根据要素完整度选择。三个要素都齐的用完整句式,缺一个的用简化句式,缺两个的就只保留标题信息。这样做的目的是保证每条总结都有实际内容,不会出现“本文提出了一种新方法”这种废话。
实测下来,模板法的准确率大概在85%左右,主要错误出现在方法名称抽取上——有些论文的摘要写法比较特殊,模式匹配抓不到。遇到这种情况就回退到用标题做总结,虽然信息量少一些,但不会出错。
3.4 去重与关联:同一工作的不同版本要合并
预印本平台上经常出现同一篇论文的多个版本,或者同一工作被不同作者分别上传的情况。如果不做去重,速递里会出现重复条目,影响阅读体验。去重的逻辑是:标题相似度超过90%且作者列表有重叠的,判定为同一工作,只保留最新版本。
还有一种情况是关联——某篇论文是对之前工作的改进或扩展。这种不做合并,但在速递里加一个标记,提示读者这篇论文与之前某篇有关联。关联的判定靠引用关系和标题相似度,阈值设得比去重低一些,大概70%左右。
4. 实操过程与核心环节实现
4.1 环境准备与依赖安装
整套系统在个人电脑上就能跑,不需要GPU。操作系统不限,Linux、macOS、Windows都行。Python版本建议3.8以上,主要依赖以下几个库:
pip install requests beautifulsoup4 scikit-learn numpy pandas schedulerequests负责网络请求,beautifulsoup4做页面解析,scikit-learn提供TF-IDF和余弦相似度的实现,numpy做数值计算,pandas做数据整理,schedule做定时任务。这些都是成熟库,安装不会有什么问题。
数据库方面,我用的是SQLite,单文件、零配置、够用。数据量大了之后可以换PostgreSQL,但个人使用场景下SQLite完全撑得住。建表语句大概是这样:
CREATE TABLE papers ( id TEXT PRIMARY KEY, title TEXT, abstract TEXT, authors TEXT, categories TEXT, published_date TEXT, url TEXT, summary TEXT, relevance_score REAL, fetched_at TEXT );4.2 抓取任务的定时调度
抓取任务用schedule库做定时,每天早上6点、中午12点、晚上6点各跑一次。为什么选这三个时间点?因为预印本平台的更新高峰通常在凌晨和上午,早上6点抓能覆盖 overnight 的更新;中午12点补充上午的新增;晚上6点做当天最后一次同步。
import schedule import time def daily_job(): paper_ids = get_new_paper_ids() results = batch_fetch(paper_ids) save_to_db(results) generate_digest() schedule.every().day.at("06:00").do(daily_job) schedule.every().day.at("12:00").do(daily_job) schedule.every().day.at("18:00").do(daily_job) while True: schedule.run_pending() time.sleep(60)这个调度逻辑很简单,但有个细节要注意:如果某次抓取失败了,下一次跑的时候要能补上之前漏掉的数据。我的做法是记录每次抓取的起始和结束时间,下次抓取时从上次结束时间开始拉取,保证不漏。
4.3 相关性打分的参数调优
TF-IDF的向量化参数有几个关键设置。max_features控制词汇表大小,设太大容易过拟合,设太小会丢失信息,我一般设在5000到10000之间。ngram_range设成(1,2),也就是同时考虑单个词和双词组合,这样能捕捉到“目标检测”这类固定搭配。stop_words用英文停用词表,去掉the、a、of这类无意义词。
余弦相似度的计算没什么参数可调,但用户兴趣描述的写法很关键。我的经验是:兴趣描述不要写得太短,也不要写得太长。太短比如“计算机视觉”,匹配范围太宽;太长比如一整段论文摘要,匹配范围太窄。比较合适的长度是两三句话,说清楚方向、方法和关注点。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity def compute_relevance(user_profile, papers): corpus = [user_profile] + [p['abstract'] for p in papers] vectorizer = TfidfVectorizer( max_features=8000, ngram_range=(1, 2), stop_words='english' ) tfidf_matrix = vectorizer.fit_transform(corpus) user_vec = tfidf_matrix[0] paper_vecs = tfidf_matrix[1:] scores = cosine_similarity(user_vec, paper_vecs)[0] return scores4.4 速递内容的组装与输出
最后一步是把筛选和排序后的论文组装成速递内容。输出格式我用的是Markdown,方便在各种平台上阅读和转发。每条论文包含:标题(带链接)、作者(只列前三位,后面加et al.)、一句话总结、相关性评分(用星级表示,最高五星)。
组装逻辑不复杂,但有个细节值得说:排序方式。我试过按相关性排、按时间排、按综合分排。最后选的是综合分排序,但把时间因素单独标出来——如果一篇论文是24小时内上线的,在标题前面加一个“新”字标记。这样读者既能按相关性浏览,又不会错过最新内容。
def format_digest(papers): lines = [] for p in papers: star = '★' * int(p['relevance_score'] * 5) new_flag = '[新] ' if is_recent(p['published_date']) else '' line = f"- {new_flag}**{p['title']}** ({star})\n" line += f" {p['summary']}\n" line += f" 作者:{', '.join(p['authors'][:3])}\n" lines.append(line) return '\n'.join(lines)5. 常见问题与排查技巧实录
5.1 抓取失败:从状态码到网络层的逐层排查
抓取失败是最常见的问题,表现是请求返回非200状态码或者超时。排查顺序是这样的:先看状态码,403通常是频率超限,404是论文ID不存在,500是服务端问题。403的话降低频率等一段时间再试;404直接跳过;500可以重试两三次。
如果状态码正常但拿不到数据,检查返回内容的格式。有时候接口会返回一个空列表或者错误信息,但状态码还是200。这种情况需要解析返回体里的错误字段。再往下就是网络层的问题,比如DNS解析失败、连接超时,这些通常跟本地网络环境有关,换个时间再试一般能解决。
注意:不要一遇到失败就疯狂重试。我见过有人写了个无限重试的循环,结果把账号搞进了临时黑名单。合理的做法是失败后等待指数级增长的时间再重试,最多重试三次。
5.2 相关性打分不准:从关键词表和兴趣描述两头找原因
打分不准的表现是:明显相关的论文得分低,不相关的论文得分高。原因通常出在两头。一头是关键词表覆盖不全,比如用户关注“三维重建”,但关键词表里只有“3D reconstruction”,没有“structure from motion”和“multi-view stereo”这些同义表达。另一头是兴趣描述写得太模糊,比如“我对AI感兴趣”,这种描述跟任何论文的相似度都差不多,区分度为零。
解决办法:关键词表定期review,从漏掉的论文里反向补充新词;兴趣描述改成具体的任务加方法组合,比如“关注基于Transformer的医学图像分割,特别是小样本场景下的方法”。
5.3 一句话总结出现语义错误:回退机制很重要
模板法生成的总结偶尔会出现语义错误,比如把方法名称和任务类型搞混,或者数值提取错误。这种情况没法完全避免,但可以加回退机制:如果生成的总结里包含明显的异常模式(比如方法名超过20个字、数值超出合理范围),就自动回退到用标题做总结。
另外,总结生成后可以加一个简单的人工抽检环节。我一般每天抽5条看一眼,发现错误就调整模板或抽取规则。这个习惯坚持下来,总结的准确率会逐步提升。
5.4 速递内容太长:分层呈现比一刀切更好
当召回论文数量多的时候,速递内容会变得很长,刷一遍要很久。我的解决办法是分层呈现:把相关性最高的10篇放在最前面,用完整格式(标题+总结+作者);10到30名的用简化格式(标题+一句话);30名之后的只列标题。这样读者可以先看最重要的,有兴趣再往下翻。
分层阈值可以根据个人习惯调整。我试过5/20/50和10/30/100两种分法,最后选了后者,因为10篇精选基本能覆盖当天最重要的内容,30篇简化格式也不会太占篇幅。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 抓取返回403 | 请求频率超限 | 检查请求间隔和并发数 | 降低频率,等待后重试 |
| 相关性打分区分度低 | 兴趣描述太模糊 | 检查描述文本长度和具体性 | 改成任务+方法的组合描述 |
| 总结语义不通 | 模板匹配失败 | 查看抽取的方法名和任务类型 | 加回退机制,人工抽检 |
| 速递内容过长 | 召回量过大 | 统计当天召回论文数 | 启用分层呈现,收紧精排阈值 |
| 重复条目 | 去重逻辑未覆盖 | 检查标题相似度阈值 | 降低阈值,增加作者重叠判断 |
6. 几个容易被忽略的实操心得
6.1 数据备份比想象中重要
SQLite数据库虽然简单,但也要定期备份。我有一次硬盘故障,攒了半年的论文数据全丢了,重新抓取虽然可行,但历史评分和总结都没了。现在我的做法是每天自动备份一次数据库文件到另一个目录,每周手动导出一次CSV存档。备份文件按日期命名,保留最近30天的。
6.2 关键词表要跟着领域走
AI领域的术语更新很快,去年还叫“大语言模型”,今年可能就流行“基础模型”了。关键词表如果半年不更新,召回率会明显下降。我的做法是每个月花半小时review一次,从最近读的论文里提取新出现的术语,补充到关键词表里。同时把过时的术语标记为低权重,而不是直接删除,因为旧论文里可能还在用。
6.3 速递的阅读节奏可以自己控制
速递内容生成之后,不一定要一次性读完。我的习惯是早上花五分钟扫一遍精选部分,把感兴趣的论文链接存到待读列表;中午或晚上再花时间细读。这样把“筛选”和“精读”两个动作分开,效率更高。速递的价值在于帮你完成筛选,而不是替代精读。
6.4 不要追求完美的自动化
我见过有人想把整个流程做成全自动——自动抓取、自动总结、自动分类、自动推送。结果每个环节都出问题,维护成本极高。我的建议是:抓取和去重可以全自动,相关性打分可以半自动(定期人工校准),总结生成保留人工抽检环节。留一两个人工介入的点,整体稳定性会好很多。
6.5 输出格式要考虑阅读场景
速递最终是给人看的,输出格式要匹配阅读场景。如果是在电脑上看,Markdown格式最方便,链接可点、结构清晰。如果是在手机上看,纯文本更合适,避免格式错乱。我的做法是同时生成Markdown和纯文本两个版本,根据使用场景选择。另外,标题长度控制在50字以内,太长的标题在手机上会被截断。
这套速递系统跑到现在,每天花在维护上的时间大概十分钟,主要是抽检总结质量和更新关键词表。抓取和打分都是自动的,基本不用管。最大的收获不是省了多少时间,而是养成了每天跟踪领域动态的习惯——这个习惯带来的信息敏感度提升,比任何工具都值钱。