
每天早上一睁眼arXiv 上都会多出两三百篇新论文。如果你只盯着某个细分方向可能只有两三篇值得读但为了这两三篇你得翻几十个搜索页、点开一堆标题最后大概率还是漏掉了更重要的。我花了几个晚上搭了一个基于 DeepSeek 的自动筛选系统把“人肉刷 arXiv”变成“让模型帮我刷”顺手还推送到邮箱。今天把这套方案从思路到代码完整拆一遍给同样被论文淹没的科研党一个可以照抄的作业。这个系统不挑方向只要你在 prompt 里写清楚自己的研究兴趣它每天早上都会自动把当天 arXiv 上相关度高的论文挑出来附上评分和一句推荐理由发到你的邮箱。如果你刚接触编程照着代码改一改也能跑起来如果你已经写过不少脚本后面关于提示词工程和阈值调优的经验应该能帮你把筛选质量再拉高一个档次。1. 整体设计与思路拆解1.1 为什么选择DeepSeek做论文筛选传统关键字筛选用正则表达式匹配标题和摘要遇到同义词基本就废。比如你研究“多模态推理”论文标题可能写“vision-language co-reasoning”纯字符匹配根本抓不到。用 DeepSeek 这类大语言模型来做本质上是把它当成一个“读过大量论文的助教”给它看标题和摘要让它判断这篇和你研究方向有没有关系还顺带给出理由和标签。这种语义层面的筛选比关键词规则灵活得多。我把关键词过滤和 DeepSeek 筛选做过一次对比结果很直观维度关键词过滤DeepSeek 筛选同义词识别差只能字面匹配好能理解同义表述跨领域关联无法识别可以发现间接相关的论文结果可解释性只能说明命中了哪个词能输出一句自然语言理由成本免费、实时有 API 费用需要耗时稳定性稳定但漏报率高有随机性需要加规则兜底所以最后我采取的策略是两者结合先用关键词把候选集缩小再用 DeepSeek 做精排。这样省掉大量无效的模型调用同时保留语义理解的优势。DeepSeek 的 API 对中文支持很好。我的研究方向描述是用中文写的模型也能准确理解返回的推荐理由可以用中文解释读起来非常顺。同样一篇英文摘要它能用“这篇提出了一个跨模态的对比学习框架和你的多模态推理方向高度相关”来总结这不就是私人助教么。1.2 系统架构模块划分我把它分成四个模块抓取、预筛、评分、通知。每个模块尽量独立方便单独调。抓取模块通过 arXiv 的 API 拉取当天更新的论文拿到标题、摘要、作者、链接、分类号。这里用到的是官方提供的数据不需要爬网页稳定很多。拉下来的论文先存在内存里不落库也行因为每天处理完就完事了如果要长期积累可以接一个 SQLite。预筛模块做两件事一是排除明显不相关的分类比如你只做 NLP那定量生物学方向的论文就不看了二是做关键词粗筛把包含指定词组的论文捞出来。如果粗筛后数量还是太多比如遇到某个方向当天井喷可以把关键词匹配做成“必须命中标题”或“摘要中至少出现两次”。评分模块把预筛后的论文逐条发给 DeepSeek让它输出一个 0 到 100 的相关度评分结构化成 JSON。这个模块是核心后面我会专门讲提示词怎么写。通知模块把得分超过阈值的论文整理成 Markdown 格式的邮件正文发送到你自己的邮箱。不想用邮件的话也可以改成调用即时通讯工具的 webhook。1.3 技术选型与关键取舍整体实现我用 Python因为第三方库最全。arXiv 的 API 用 urllib 就能调没必要上 requests模型调用用 OpenAI 兼容的 SDKDeepSeek 提供同样的接口格式设置 base_url 和 api_key 就行。定时任务直接写进脚本里用系统的 cron 或计划任务触发不依赖后台框架。有一个让我纠结很久的点要不要把所有论文都过一遍模型从效果看全量模型处理肯定召回最高但成本也高。如果当天论文有 500 篇每篇摘要平均 1500 token一天就要处理 75 万 token长期下来不是小数目。所以最终选了“规则预筛 模型精排”的漏斗结构。实测下来预筛能过滤掉 70% 的不相关论文剩下 30% 再让模型打分成本和效果取得了不错的平衡。这里还有一个容易被忽略的细节模型评分有随机性。同一篇论文跑两次分数可能会波动 10 分左右。所以在设计时不能让模型直接决定“要不要”而是让它输出连续分数再配合下面的规则阈值来过滤。这样即使模型偶尔抽风只要不是极端情况结果基本稳定。2. 核心细节解析与实操要点2.1 从arXiv抓取论文元数据arXiv 官方 API 的入口是export.arxiv.org/api/query。它支持按时间、分类、关键词查询返回 Atom XML 格式。最常用的参数是search_query比如cat:cs.CL筛选自然语言处理all:transformer搜索标题和摘要。还有一个关键参数sortBysubmittedDate能保证拿到的是当天新增论文。实际请求时要注意官方 API 有频率限制如果抓太频繁会被拒绝访问。我的经验是每次请求之间至少间隔 3 秒并且设置一个明确的 User-Agent 标明用途。另外arXiv 的摘要里可能包含换行和特殊字符解析后要清洗一下否则喂给模型时容易让 prompt 结构错乱。用 XML 解析时要先处理命名空间。下面这段代码能提取出最基本的信息import urllib.request import xml.etree.ElementTree as ET ARXIV_API http://export.arxiv.org/api/query NS {atom: http://www.w3.org/2005/Atom, arxiv: http://arxiv.org/schemas/atom} def fetch_papers(query: str, max_results: int 50): url f{ARXIV_API}?search_query{query}sortBysubmittedDatesortOrderdescendingmax_results{max_results} req urllib.request.Request(url, headers{User-Agent: PaperFilter/0.1}) with urllib.request.urlopen(req) as resp: xml_text resp.read().decode(utf-8) root ET.fromstring(xml_text) papers [] for entry in root.findall(atom:entry, NS): papers.append({ title: entry.find(atom:title, NS).text.strip().replace(\n, ), abstract: entry.find(atom:summary, NS).text.strip(), link: entry.find(atom:id, NS).text.strip(), category: [c.attrib[term] for c in entry.findall(atom:category, NS)], }) return papers注意sortBysubmittedDate不一定百分之百只返回当天的可能混着前一天没入库的所以在后续脚本里要用解析出的发布日期再过滤一次。日期可以从arxiv:published标签里取解析成 UTC 再比较from datetime import datetime, timezone published entry.find(arxiv:published, NS).text.strip() pub_date datetime.fromisoformat(published.replace(Z, 00:00)) if pub_date.date() datetime.now(timezone.utc).date(): # 只保留今天的论文 pass另外arXiv 的 API 接口是http如果服务器环境要求更严格可以走https不过在大多数场景下都能直接访问。拉取结果后建议把标题和摘要里的多余空白压缩掉正则替换连续空格为单个空格这样模型请求时能省一点 token。2.2 提示词工程让DeepSeek明白“相关性”论文筛选的核心不是代码而是 prompt。我调试了很多版本最后稳定下来的是一个带角色设定、评分标准和 few-shot 示例的模板。先说角色设定告诉模型“你是某课题组的研究助手”比直接说“请你判断是否相关”效果更稳定。因为角色会给模型一个行为框架。然后是研究方向描述这个描述不能太短比如“自然语言处理”这种太笼统模型会给你很多泛泛的分数。要把关键词、方法、目标都写进去比如“多模态大模型中的视觉-语言对齐机制尤其关注跨模态注意力”。评分标准要明确如果论文提出新方法或新数据集且方法可复现给 80 分以上如果只是应用既有方法到新场景给 50 到 70 分如果完全不沾边给 20 分以下。把评分维度和具体尺度写在 prompt 里能显著降低模型乱打分的概率。few-shot 示例特别重要。我会在 prompt 里给两个示例一个高度相关但标题里没有关键词的论文模型应该给高分另一个看起来沾边但实际方向偏移的论文模型应该给低分。这样模型就理解了“相关性不是单纯看字面词”。最后要求模型返回严格 JSON字段包括 score、reason、tags。下面是一个我实际在用的完整 prompt 模板def build_prompt(paper: dict) - str: research_field 多模态大语言模型中的视觉-语言对齐机制尤其关注跨模态注意力、指令微调中的对齐策略、以及图文推理链 return f 你是某课题组的研究助手。你要根据下面的研究方向判断一篇论文是否值得课题组成员精读。 研究方向{research_field} 评分标准 - 90-100提出了新方法、新模型或新数据集且直接解决该方向的关键问题 - 70-89将现有方法深度应用到该方向有明确的参考价值 - 40-69仅仅部分相关例如使用了某个相似名词但没有实质关联 - 0-39基本不相关只是 keywords 有巧合 输出要求 只允许输出一个 JSON 对象不要输出其他任何文字。 格式如下 {{score: 0, reason: 不超过30字的中文理由, tags: [标签]}} tags 只能从 [new_method, new_dataset, survey, application] 中选择没有匹配的就给空列表。 示例1 论文标题Aligning Vision-Language Representations with Learnable Cross-Modal Tokens 论文摘要本文提出一种可学习的跨模态 token 对齐方法增强了视觉编码器与语言模型的交互。 正确输出{{score: 95, reason: 直接提出新的对齐方法针对性极强, tags: [new_method]}} 示例2 论文标题ImageNet Classification with Visual Features 论文摘要仅使用视觉特征进行图像分类没有涉及任何语言模型或跨模态交互。 正确输出{{score: 12, reason: 纯图像分类方向完全不符, tags: []}} 现在开始 论文标题{paper[title]} 论文摘要{paper[abstract][:2000]} 这个模板有几个细节是刻意设计的。首先输出要求里明确“只允许输出一个 JSON 对象”并且给了示例格式模型基本不会跑偏。其次标签候选只有 4 个限制范围比放开让模型自由发挥要稳定得多。最后摘要限制 2000 字符因为论文摘要的后半段往往是实验设置和结论对“相关性”判断的边际贡献不大反而会拖慢响应速度。这里有个坑要求返回 “strict JSON” 还不够模型偶尔会在前面加解释文字或者用中文标点。所以在解析时不能只json.loads要先做一层正则清理提取{...}部分。后面常见问题里会细说。2.3 评分规则与阈值设定DeepSeek 输出的 0-100 分数是“软性”的直接拿它和阈值比较之前我建议先做一个“关键词加权”的二次校正。具体做法是在 prompt 外自己再算一个关键词命中分标题命中一个关键词 10摘要命中 5命中多个关键词累加。然后最终分数 0.8 * 模型分 0.2 * 关键词加权分。之所以保留关键词加权是为了防止模型对某些过长摘要产生“注意力漂移”把核心点漏掉。阈值设定可以先无脑取 60 分然后跑一周记录每天命中数量。如果每天都很多说明阈值低了如果三天打鱼两天晒网说明阈值高了。我的经验是宁可每周多筛出两篇读也别漏掉真正重要的那篇。所以阈值不要定太高65 分是个不错的起点。另外可以给论文打标签比如“survey”“new_dataset”“SOTA”。这个标签不是筛选必需但如果推送邮件里带了标签你扫一眼就能决定要不要点开。DeepSeek 在这方面表现不错给它列几个候选标签它会照做。不过模型对 “SOTA” 这类需要严谨验证的标签有时会误判所以最后推送前我自己加了一道小检查如果摘要里出现 “state-of-the-art”“ outperforms ” 这些字样才允许打上 “SOTA” 标签。3. 实操过程与核心环节实现3.1 环境准备与依赖安装需要 Python 3.9 以上装两个库就够requests用于调用模型接口标准库里的urllib、xml、smtplib就能解决其他问题。我在虚拟环境里安装避免污染全局python -m venv venv source venv/bin/activate pip install requestsDeepSeek 的 API 是 OpenAI 兼容的所以也可以用openai这个库把base_url指向 DeepSeek 的接口。但我试下来直接用requests反而少一层依赖出错更可控。下面是调用函数import requests import json API_KEY your_deepseek_api_key BASE_URL https://api.deepseek.com/v1/chat/completions MODEL_NAME deepseek-chat def chat_json(prompt: str, temperature: float 0.3): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json, } payload { model: MODEL_NAME, messages: [{role: user, content: prompt}], temperature: temperature, } resp requests.post(BASE_URL, headersheaders, jsonpayload) data resp.json() return data[choices][0][message][content]注意这里我故意把temperature设成 0.3。论文筛选的任务需要一定的一致性温度太高会随机打乱评分设得太低又可能让模型拒绝输出。0.3 到 0.4 是性价比比较高的区间。API Key 一定要通过环境变量或者配置文件读取不要写死在代码里。我见过太多人把 key 发到公开博客然后被拿去刷爆。用os.getenv(DEEPSEEK_API_KEY)取既安全又方便换。3.2 核心代码采集、评分、推送整体流程函数写成一个pipeline()每天跑一次。采集部分用前面的fetch_papers预筛部分简单判断目标关键词评分部分组装 prompt 后调用chat_json推送部分用smtplib发送邮件。下面是一个可运行的骨架我删掉了日志和异常处理的细节只保留主线import os import re import json import smtplib from datetime import datetime, timezone from email.mime.text import MIMEText RESEARCH_FIELD 多模态大语言模型中的视觉-语言对齐机制 KEYWORDS [vision-language, multimodal, cross-modal, alignment] THRESHOLD 65 def pre_filter(papers): result [] for p in papers: text (p[title] p[abstract]).lower() if any(k.lower() in text for k in KEYWORDS): result.append(p) return result def parse_model_output(content: str) - dict: # 提取第一个 { 到最后一个 } 之间的内容 start content.find({) end content.rfind(}) if start -1 or end -1: raise ValueError(no json found) data json.loads(content[start:end1]) data[score] int(data[score]) return data def score_paper(paper: dict) - dict: prompt build_prompt(paper) # 复用 2.2 节的模板 content chat_json(prompt) parsed parse_model_output(content) # 关键词加权 text (paper[title] paper[abstract]).lower() kw_score 0 for k in KEYWORDS: if k.lower() in paper[title].lower(): kw_score 10 elif k.lower() in text: kw_score 5 final_score int(0.8 * parsed[score] 0.2 * kw_score) parsed[final_score] final_score return parsed def send_mail(papers_with_meta: list): lines [# 今日论文推荐\n] for item in papers_with_meta: lines.append(f## {item[title]}) lines.append(f相关度{item[final_score]} 分) lines.append(f理由{item[reason]}) lines.append(f链接{item[link]}) if item.get(tags): lines.append(f标签{, .join(item[tags])}) lines.append() body \n.join(lines) msg MIMEText(body, markdown, utf-8) msg[Subject] f每日论文筛选 {datetime.now().strftime(%Y-%m-%d)} msg[From] os.getenv(MAIL_FROM) msg[To] os.getenv(MAIL_TO) with smtplib.SMTP_SSL(os.getenv(SMTP_HOST), int(os.getenv(SMTP_PORT))) as server: server.login(os.getenv(SMTP_USER), os.getenv(SMTP_PASS)) server.send_message(msg)运行主流程时我通常会把不合格的论文也存到一个 debug 文件里方便回头检查是不是漏掉了好论文。这里有个细节parse_model_output里的int(data[score])必须加因为模型偶尔返回字符串类型你不强转后续 THRESHOLD时就会 TypeError。3.3 定时运行与结果分析脚本写好后最省事的方式是配置 cron。比如每天早上八点跑一次0 8 * * * cd /path/to/project /path/to/venv/bin/python pipeline.py run.log 21记住要用虚拟环境的解释器不能用直接python否则可能用到错误版本。跑一周之后我建议做一个简单的结果分析把每天推送论文的标题存下来手动抽查 20 篇看哪些被漏掉、哪些是误报。漏掉多的就降低阈值或增加关键词误报多就反过来调整 prompt 或提高阈值。这一步才是整个系统最有价值的部分。因为模型判断永远有瑕疵人工闭环调整才是提升精度的关键。我自己的系统跑了两周之后误报率从刚开始的 40% 降到了 15% 左右。我每周会做一个表格记录效果日期预筛论文数模型调用数最终推送数手动抽查判断周一868698 篇值得读周二102102129 篇值得读周三737376 篇值得读当你连续记录半个月就能看出阈值和关键词设置是否合理了。如果某天预筛论文数异常高很可能是某篇热门论文霸占了多个分类去重逻辑没生效。如果连续两天推送数为 0先别急着降阈值大概率是 arXiv API 没抓到新数据而不是真的没有好论文。4. 常见问题与排查技巧实录4.1 请求被限流或超时arXiv 的 API 不会给明确的 429但会出现连接超时或者返回一段比较少见的 XML。解决办法是添加重试和退避逻辑。我写了一个简单的retry装饰器三次重试每次等待时间递增。另外把max_results拆小比如一次拉 20 篇分批次拉避免一次拉几百篇被服务器拒绝。还有一次我遇到urlopen error [Errno 110] Connection timed out排查后发现是网络代理环境太复杂。后来在服务器上直接清空代理变量才恢复正常。如果你也用了代理记得在脚本里显式清除HTTP_PROXY和HTTPS_PROXY不然很隐蔽。DeepSeek API 偶尔也会返回 503 或者 429这类状态码意味着服务端过载。我的处理逻辑是遇到 5xx 就退避重试遇到 429 就等待更长的时间再试一次。不要在重试逻辑里直接 sleep 60那样会把整个 pipeline 卡住最好给每次重试抛出一个异常让主流程跳过这篇论文最后把失败列表打印出来。4.2 模型输出不稳定与JSON解析失败这是最常遇到的问题。DeepSeek 返回的内容偶尔会在 JSON 前后加一句废话比如“好的为你生成结果”导致json.loads直接抛异常。我的处理方法是先找第一个{和最后一个}截取中间部分再解析如果还是失败就重新调用一次。因为温度不高重试后输出基本会正常。还有一个很隐蔽的问题模型给出的 score 可能是字符串75而不是数字 75。所以解析后要用int()强制转换否则后面比较时会报错。解析代码可以封装成一个小函数并且支持容错def safe_parse_json(content: str): try: return json.loads(content) except json.JSONDecodeError: start content.find({) end content.rfind(}) if start -1 or end -1: return None try: return json.loads(content[start:end1]) except json.JSONDecodeError: return None如果safe_parse_json返回 None我会把这篇文章标记为“待处理”不去重试也是可以的。因为提示词里的示例已经把输出格式规定得很严偶尔一次失败不影响整体效果。但你一定要在日志里保留原文方便事后分析是不是某些特殊论文触发了模型异常。4.3 筛选结果漂移与阈值调优有时候同一个研究方向上周跑得很好这周突然推了一堆不太相关的论文。原因可能是 arXiv 这周的新论文里恰好有大批擦边球或者模型在长上下文中的注意力被稀释。我的经验是把研究方向描述拆分得更细加上“不需要泛泛的综述性工作”这样的负面约束。同时阈值不要固定死可以根据近三天推送数量做动态调整比如平均超过 10 篇就上调 5 分少于 3 篇就下调 5 分。还有一种情况研究方向本身在演进。比如你从“多模态对齐”细化到“细粒度视觉定位”那么方向描述里要同步加一句“当前最后关注细粒度区域定位与语言引导的交互”。我经常忘了更新 prompt导致系统还在推旧方向的内容那时候就会觉得“这系统怎么越跑越偏”。其实不是系统变笨了而是你的研究方向变了。4.4 成本控制与性能优化如果预筛后每天还有 200 篇论文意味着要调用 200 次模型 API每次等待 1-3 秒整体跑下来要 10 分钟以上。性能瓶颈主要在串行等待。解决办法是用线程池并发调用把并发数控制在 5 左右能提速 3-4 倍且不容易触发限流。另外可以把论文抽象出前 2000 字符因为后面基本都是 related work对判断相关性帮助不大。使用 ThreadPoolExecutor 的时候要保证每个线程都有独立的 prompt没有共享状态from concurrent.futures import ThreadPoolExecutor, as_completed def batch_score(papers: list, max_workers: int 5): results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map {executor.submit(score_paper, p): p for p in papers} for future in as_completed(future_map): paper future_map[future] try: parsed future.result() parsed[link] paper[link] parsed[title] paper[title] results.append(parsed) except Exception as e: print(ffailed: {paper[title]} - {e}) return results成本方面可以用一个简单的统计函数定期记录每天调用 token 数。一个月下来你心里就有底了。我的实际体验是预筛加并发之后每天大概消耗 30 万 token不是一个大数字但如果不做预筛会多出三四倍。4.5 安全与隐私提醒这个系统本身不涉及敏感数据但有几点容易忽视。第一API Key 和邮箱密码必须用环境变量管理不要把配置文件丢到公开仓库。第二论文摘要属于公开数据但如果你后续要对推送结果做二次处理比如训练自己的模型要留意 arXiv 的使用条款。第三定时任务跑出来的日志可能包含论文标题和个人邮箱不要在公开空间随意粘贴完整日志。还有一点如果你把系统交给别人维护一定不要把研究方向描述和个人偏好写得过于私人化。这个文件可能会流传出去尽量保持学术化的表达。5. 扩展玩法让系统更像你的私人学术助理5.1 接入即时通知邮件虽然稳定但不是所有人都习惯每天看邮箱。可以把推送结果转成 markdown 发到即时通讯工具或者生成一个简单的 HTML 页面放到本地。我目前用邮件因为设置简单、不受设备限制。如果你想要更及时的通知可以用即时通讯工具的 webhook调用方式也就是一次 POST 请求比邮件还容易。下面是一个极其简单的 webhook 发送函数import requests def send_webhook(webhook_url: str, text: str): resp requests.post(webhook_url, json{text: text}, timeout10) resp.raise_for_status()这个方法唯一的隐患是 webhook 地址泄露后容易被别人盗刷所以不要把 webhook 地址放在公开仓库里。如果平台支持关键字过滤也可以先做一层脱敏再发送。5.2 增量更新与去重arXiv 上同一篇论文可能在多个分类里出现抓取时会造成重复。我的做法是用论文 ID 的前几位去掉版本号作为去重键在内存里维护一个集合。这样既不会重复推送也能追踪论文是否有更新版本。seen set() def is_duplicate(paper: dict) - bool: # 例如 arxiv.org/abs/2401.00123v2 - 2401.00123 paper_id paper[link].split(/)[-1].split(v)[0] if paper_id in seen: return True seen.add(paper_id) return False这个去重逻辑对“同 ID 不同版本”也有效。我之前没做去重时一天收到过两封完全重复的邮件排查了半天才发现是不同分类入口抓到了同一篇论文。5.3 多主题并行筛选如果你的研究方向有两个不想每天看两份邮件可以让一个脚本同时跑多个 prompt把不同主题的结果合并到同一封邮件里。这里要注意各主题的分数不能直接比较因为 prompt 的评分标准可能不一样所以邮件里要明确标注主题名。实现上不用把文件拆成多份只要把RESEARCH_FIELD、KEYWORDS和THRESHOLD做成参数循环调用即可。我遇到过一个小问题两个主题的关键词有交叉导致同一条论文被两个主题都推了一次。解决办法是在同一个去重集合里增加“主题名 论文ID”的复合键或者按主题的优先级去重。这些扩展都不复杂核心还是抓取、评分、通知这个链路。系统真正跑起来以后你就会发现它省下来的不只是刷论文的时间还有你注意力切换的隐性成本。每天花 3 分钟扫一眼邮件比在 arXiv 上漫无目的地刷一小时要高效得多。我个人在实际操作中最深的一点体会是这类自动筛选系统效果好坏 80% 取决于那一段研究方向描述怎么写代码反而是最不费脑的地方。你不需要把方向写得多学术要把你自己看论文时的直觉描述出来比如“我关心方法不是任务”“我更在意可复现性”模型才能真正理解你的偏好。另外不要追求一个永远不变的参数论文领域的热点在变你的研究兴趣也会微调每两三个星期回头看看筛选结果顺手改几句 prompt、动一下阈值系统才能一直好用。最后再分享一个小技巧如果你打算长期用这套系统记得把每天推送的论文链接和评分存成一个历史文件。哪天想找一篇之前读过但忘了收藏的论文一份包含摘要和评分的本地索引比 arXiv 站内搜索好用太多。这就是“论文筛选”之外意外收获的额外价值。