- 人工智能
- AI 应用
- RAG
- 科研
【免费下载链接】zotero-arxiv-daily
Recommend new arxiv papers of your interest daily according to your Zotero libarary.
本文以仓库 CLAUDE.md 为骨架,深度解析 zotero-arxiv-daily 的整体架构:它如何以你的 Zotero 文献库为"兴趣画像",通过嵌入相似度对 arXiv/bioRxiv/medRxiv/chemRxiv 每日新论文做相关性重排,再用 LLM 生成 TLDR 与机构信息并通过邮件送达。读完本文,你将掌握这条流水线每一阶段的实现原理、Hydra + OmegaConf 的组合配置体系、Retriever/Reranker 插件扩展机制、测试策略与本地运行方式,可直接在仓库源码中逐行验证。
项目定位与核心思想
Zotero-arXiv-Daily 的核心目标非常朴素:"你 Zotero 里存了什么,就代表你对什么感兴趣,把每天新发布的论文里和你库中论文最相关的挑出来,发到你的邮箱。"它不是为了泛泛抓取 arXiv 每日更新,而是基于用户自身的文献积累做个性化推荐。
从 CLAUDE.md 的项目概述看,它覆盖四个论文来源(arXiv / bioRxiv / medRxiv / chemRxiv),计算新论文与用户现有库的嵌入相似度,通过 LLM 生成 TLDR(太长不看版摘要),最终以 HTML 邮件形式投递,且被设计为零成本运行在 GitHub Actions 工作流上。整个应用由Executor编排成一条线性流水线,没有任何复杂的调度框架,这让它极易被理解、扩展和二次开发。
上图为项目 README 中展示的最终邮件呈现效果:论文按相关性排序,附带 AI 生成的 TLDR、作者机构与 PDF/代码链接。
六阶段线性流水线:从 Zotero 到邮箱
应用的全部业务逻辑集中在 executor.py,由Executor类驱动。流水线共六个阶段,代码路径Executor.run()(见 executor.py#L93-L124):
- Fetch Zotero corpus(拉取 Zotero 语料):通过
pyzoteroAPI 拉取用户文献库,只保留conferencePaper || journalArticle || preprint三种条目类型且含摘要(abstractNote != '')的论文,同时递归解析每个条目的 Collection 路径(get_collection_path沿parentCollection逐级向上拼接出如2026/survey的层级路径,见 executor.py#L49-L56)。 - Filter corpus(过滤语料):按
include_path/ignore_path两组 glob 模式筛选相关 Collection,这决定了"你的兴趣画像"具体由库中哪些论文构成(详见下文"Collection 路径过滤"小节)。 - Retrieve new papers(抓取新论文):从配置指定的来源抓取新论文。arXiv 走 RSS feed,bioRxiv/medRxiv 走 REST API,chemRxiv 经 Crossref REST API。
- Rerank(重排打分):用嵌入模型计算候选论文与语料的相似度,并按时间衰减权重加权——越晚加入 Zotero 的论文权重越高,代表"最近的研究兴趣"。
- Generate TLDRs + affiliations(生成摘要与机构):通过 OpenAI 兼容的 LLM API,为每篇候选论文生成一句话 TLDR,并尽量解析出作者机构列表。
- Render + send email(渲染并发送邮件):将结果渲染为 HTML 邮件,经 SMTP 发送到收件箱。
流水线中的关键防御逻辑值得一提:若过滤后语料为空(len(corpus) == 0),run()会记录错误日志并直接返回(executor.py#L96-L98);若当天无任何新论文,除非配置了send_empty: true,否则不发送空邮件(executor.py#L118-L120)。
Collection 路径过滤:精准圈定"兴趣画像"范围
默认情况下你的全部 Zotero 文献都会参与相似度计算,但通过 config/base.yaml 中的两个配置项可以精准圈定范围:
zotero: include_path: null # 只保留匹配的 Collection,例:["2026/survey/**", "2026/reading-group/**"] ignore_path: null # 排除匹配的 Collection,例:["2026/ignore/**", "archive/**"]其实现逻辑在Executor.filter_corpus()(executor.py#L65-L90):
include_path存在时,仅保留任一 Collection 路径匹配任一模式的论文;ignore_path存在时,剔除所有匹配的论文;- 两者可同时配置(先 include 后 ignore);
- 过滤后若两者任一启用,会从结果中随机采样至多 5 篇论文打印标题与路径,便于在日志中确认"兴趣画像"构成。
参数校验由normalize_path_patterns()(executor.py#L16-L29)完成:配置值必须是字符串列表或null,不支持单个字符串(例如"2026/survey/**"会被拒绝并抛出TypeError,提示应写成["2026/survey/**"])。glob 匹配的具体实现位于 utils.py 的glob_match,并有对应的单元测试可参考。
插件系统:Retriever 与 Reranker 的注册-发现机制
CLAUDE.md 明确指出本项目的两套插件体系,其设计高度对称:
Retriever(论文源插件)
- 注册:在类上使用
@register_retriever("arxiv")装饰器(如 arxiv_retriever.py#L210),装饰器把类挂到registered_retrievers字典并写入cls.name(retriever/base.py#L39-L46); - 发现:
get_retriever_cls(name)按名称查表,未注册的名称抛出ValueError(retriever/base.py#L48-L51); - 契约:每个 Retriever 继承
BaseRetriever,实现两个抽象方法——_retrieve_raw_papers()抓取原始数据、convert_to_paper()将原始数据转换为统一的Paper对象(retriever/base.py#L16-L22); - 批量处理:
retrieve_papers()模板方法逐条转换,失败的单条论文被跳过并告警,每条之间sleep(1)限速(retriever/base.py#L24-L37)。
以 ArxivRetriever 为例:它通过feedparser解析https://rss.arxiv.org/atom/{category}组合出的 RSS 地址(category 用+连接),带 5 次重试与 HTTP 状态/解析器状态校验,失败 5 次才抛出RuntimeError。include_cross_list: false时只保留arxiv_announce_type == "new"的条目;debug 模式下只取前 10 条(arxiv_retriever.py#L259-L265)。转换阶段还会按 tar 源码包 → HTML → PDF 的优先级提取全文,PDF/TeX 提取均在子进程中执行并设有硬超时(PDF 180 秒、TAR 180 秒),超时或失败自动降级(见_run_with_hard_timeout,arxiv_retriever.py#L57-L90)。
Reranker(重排插件)
- 注册/发现机制与 Retriever 完全同构:
@register_reranker+get_reranker_cls()(reranker/base.py#L26-L36); - 两个内置实现:
local(local.py,sentence-transformers 本地嵌入模型)与api(api.py,OpenAI 兼容嵌入接口); - 打分核心在
BaseReranker.rerank()(reranker/base.py#L10-L20):语料先按加入日期降序排列,时间衰减权重为1 / (1 + log10(序号 + 1))并归一化,最终得分为(相似度矩阵 × 时间权重).sum(axis=1) × 10,再按得分降序输出。
这个时间衰减公式是整个推荐算法的灵魂:Zotero 中第 1 篇论文权重最高,第 100 篇的权重约为第 1 篇的1 / (1 + log10(101)) ≈ 1/3,即近期加入的文献对"兴趣画像"的贡献显著大于早期文献——因为研究者当前关注的方向往往与最近阅读的文献一致。
配置体系:Hydra + OmegaConf 的组合式配置
项目采用 Hydra + OmegaConf 管理配置,这是 CLAUDE.md 明确点出的技术选型。
配置组合方式
入口 main.py 通过@hydra.main(version_base=None, config_path="../../config", config_name="default")(main.py#L12)启动。default.yaml仅做两件事——组合两个配置组(default.yaml):
defaults: - base # 全量配置模板,`???` 为必填占位 - custom # 用户覆盖层,用环境变量插值填充base.yaml定义了全部配置项的默认值与注释说明(???表示必须填写),custom.yaml则以${oc.env:VAR_NAME,default}语法从环境变量读取实际值,例如:
zotero: user_id: ${oc.env:ZOTERO_ID} api_key: ${oc.env:ZOTERO_KEY} include_path: null email: sender: ${oc.env:SENDER} receiver: ${oc.env:RECEIVER} smtp_server: smtp.qq.com smtp_port: 465 sender_password: ${oc.env:SENDER_PASSWORD} llm: api: key: ${oc.env:OPENAI_API_KEY} base_url: ${oc.env:OPENAI_API_BASE} api_mode: chat_completion generation_kwargs: model: gpt-4o-mini source: arxiv: category: ["cs.AI","cs.CV","cs.LG","cs.CL"] executor: debug: ${oc.env:DEBUG,null} source: ['arxiv']在 GitHub Actions 部署场景中,这份custom.yaml的内容会被完整粘贴到名为CUSTOM_CONFIG的仓库变量里,作为运行时覆盖层写入,从而实现"仓库代码 + 用户秘密/变量"的完全解耦。${oc.env:XXX,yyy}语义为:取环境变量XXX的值,未设置则回退到默认值yyy。
配置项全景
下表汇总 config/base.yaml 中全部配置参数(???为必填):
| 配置路径 | 默认值 | 说明 |
|---|---|---|
zotero.user_id | ??? | Zotero 账户的 User ID(数字串,非用户名) |
zotero.api_key | ??? | 具有读权限的 Zotero API Key |
zotero.include_path | null | 参与推荐的 Collection glob 列表,如["2026/survey/**"] |
zotero.ignore_path | null | 排除的 Collection glob 列表 |
source.arxiv.category | null | arXiv 订阅分类缩写,如["cs.AI","cs.CV","cs.LG","cs.CL"] |
source.arxiv.include_cross_list | false | 是否纳入 cross-list 条目 |
source.biorxiv.category | null | bioRxiv 分类(按站点分类名填写) |
source.medrxiv.category | null | medRxiv 分类 |
source.chemrxiv.include_new_versions | false | 是否纳入已发布预印本的修订版;chemRxiv 无分类过滤,每天新预印本(约几十篇)全量抓取交由重排器筛选 |
email.sender/receiver | ??? | 发件邮箱 / 收件邮箱 |
email.smtp_server/smtp_port | ???/??? | SMTP 服务器与端口(如smtp.qq.com:465) |
email.sender_password | ??? | SMTP 授权码(不一定是邮箱登录密码) |
llm.api.key/base_url | ??? | LLM API Key 与 Base URL |
llm.api_mode | chat_completion | chat_completion或response(Responses API) |
llm.generation_kwargs.max_tokens | 16384 | 生成最大 token 数 |
llm.generation_kwargs.model | ??? | 使用的模型名,如gpt-4o-mini |
llm.language | English | TLDR 输出语言 |
reranker.local.model | jinaai/jina-embeddings-v5-text-nano-retrieval | 本地嵌入模型名 |
reranker.local.encode_kwargs | {task: retrieval, prompt_name: document} | 传给SentenceTransformer.encode的参数 |
reranker.api.key/base_url/model/batch_size | null | API 型嵌入模型的配置 |
executor.debug | false | 调试模式(启用更详细日志、缩小数据量) |
executor.send_empty | false | 无新论文时是否仍发送(空)邮件 |
executor.max_paper_num | 100 | 邮件中最多呈现的论文数 |
executor.source | ??? | 论文来源列表,如['arxiv','biorxiv','medrxiv','chemrxiv'] |
executor.reranker | local | 使用的重排器,local或api |
api_mode与generation_kwargs的语义可在 protocol.py#L12-L36 的_request_llm()中验证:chat_completion模式调用openai_client.chat.completions.create(messages=...),response模式则调用openai_client.responses.create(input=...)并把max_tokens自动映射为max_output_tokens,两者都透传剩余的 generation 参数。
核心数据结构:Paper 与 CorpusPaper
两类数据类定义在 protocol.py#L39-L139,是贯穿全流水线的类型契约:
Paper(候选新论文):携带source、title、authors、abstract、url、pdf_url、可选的full_text、tldr、affiliations与score。它的两个 LLM 增强方法直接在数据类上实现:
generate_tldr():构造系统提示("你是一位完美总结科学论文的助手")与用户提示(含标题、摘要、全文预览),先用 gpt-4o 分词器把 prompt 截断到 4000 token,再调用 LLM,语言由llm.language控制(protocol.py#L52-L96)。异常时降级为返回原始摘要;generate_affiliations():仅在有full_text时执行,要求 LLM 输出按作者顺序排列的 Python 列表、只保留顶层机构、去重;随后用正则\[.*?\]提取列表并json.loads解析,异常时置为None(protocol.py#L98-L133)。
CorpusPaper(Zotero 语料论文):只包含title、abstract、added_date(加入日期,用于时间衰减加权)和paths(所属 Collection 路径列表,用于 glob 过滤)。
在流水线中,Executor构造时即创建各来源 Retriever、Reranker 和 OpenAI 客户端(OpenAI(api_key=..., base_url=...),executor.py#L37-L41);重排后按max_paper_num截断,逐个generate_tldr+generate_affiliations,最后由 construct_email.py 的render_email()渲染、utils.py 的send_email()发送(executor.py#L109-L124)。
测试策略:默认跳过慢测试,纯 Python stub 即可运行
CLAUDE.md 用专节说明了测试策略,这是项目工程质量的关键设计:
- 慢测试标记:标注
@pytest.mark.slow的测试依赖重型依赖(典型如 sentence-transformers 模型下载),默认被跳过; - 默认排除机制:
pyproject.toml中配置addopts = "-m 'not slow'",因此本地uv run pytest默认只跑非慢测试; - 零 Docker 依赖:除慢测试外,其余测试使用纯 Python stub(如 mock Zotero / mock OpenAI 服务器)即可运行,无需任何容器。测试基建见 tests/utils/mock_openai/(含 Dockerfile 与
openai_server.py)与 tests/utils/mock_zotero/。
仓库内已具备覆盖各模块的测试:test_executor.py、test_protocol.py、test_utils.py(含TestGlobMatch)、test_construct_email.py、test_main.py,以及 retriever 与 reranker 各自的测试目录(tests/retriever/、tests/reranker/),其中 tests/retriever/arxiv_rss_example.xml 提供了可离线复用的 RSS 样例数据。
常用命令:运行、测试与依赖管理
CLAUDE.md 给出的命令体系如下(项目由 uv,依赖声明见 pyproject.toml):
# 运行应用(默认从 config/ 组合 default.yaml 配置) uv run src/zotero_arxiv_daily/main.py # 运行测试(默认排除慢测试) uv run pytest # 运行全部测试(包括慢测试) uv run pytest -m "" # 运行单个测试 uv run pytest tests/test_utils.py::TestGlobMatch -v # 安装/同步依赖 uv sync # 带覆盖率运行 uv run pytest --cov=src/zotero_arxiv_daily --cov-report=term-missing值得注意的实现细节:main.py启动时设置TOKENIZERS_PARALLELISM=false并用dotenv.load_dotenv()加载本地.env文件(main.py#L9-L10),本地运行时可直接在项目根目录准备.env注入秘密;日志级别由config.executor.debug决定(DEBUG/INFO),并借助loguru输出带颜色与调用位置信息的结构化日志,同时把第三方库日志静默到 WARNING(main.py#L15-L26)。项目未配置 linter 或 formatter,属于刻意精简的工具链。
本地完整运行示例(需先按上文表格导出环境变量):
export ZOTERO_ID=xxxx export ZOTERO_KEY=xxxx export SENDER=abc@qq.com export SENDER_PASSWORD=xxxx export RECEIVER=abc@outlook.com export OPENAI_API_KEY=sk-xxx export OPENAI_API_BASE=https://api.openai.com/v1 uv run src/zotero_arxiv_daily/main.py调试模式与运行边界
executor.debug: true是一把双刃剑:它一方面把日志级别降到 DEBUG 便于排查,另一方面会显著缩小数据规模——arXiv Retriever 只取前 10 个 RSS 条目(arxiv_retriever.py#L264-L265),本地嵌入模型会保留进度条输出。README 中对应的 GitHub Actions "Test-Workflow" 工作流正是 debug 版:无论日期如何始终抓取少量论文用于验证链路,而主工作流每天自动运行、只抓取昨天发布的新论文(周末与节假日无新论文时,主工作流日志会出现 "No new papers found")。
关于max_paper_num,README 明确提示其上限受限于 GitHub Actions 运行器配额(公共仓库单次 6 小时、私有仓库每月 2000 分钟):数值过高会导致执行超时。需要更大吞吐时,可考虑自有服务器部署(参考 assets/use_docker.md 的 Docker/Compose 方案,支持定时执行、日志持久化与模型缓存)。
扩展指南:如何接入新论文源或新嵌入模型
结合插件机制,扩展点非常清晰:
- 新增论文源:在
retriever/下新建类,继承BaseRetriever,用@register_retriever("你的名字")注册,实现_retrieve_raw_papers()(返回原始条目列表)与convert_to_paper()(转为Paper),然后在config.executor.source中追加该名称即可; - 新增嵌入方式:在
reranker/下继承BaseReranker,用@register_reranker("你的名字")注册,只需实现get_similarity_score(s1, s2) -> np.ndarray(返回候选×语料的相似度矩阵),时间衰减加权与排序逻辑由基类免费提供; - 换 LLM:无需改代码,
llm.api.base_url+generation_kwargs.model支持任意 OpenAI 兼容端点。
可以推断,这种"注册-发现-模板方法"的组合,使项目在保持流水线单线推进的前提下,将"抓什么""怎么排序""谁生成摘要"三个易变点全部开放为配置与插件,这是它作为 GitHub Actions 零成本服务仍能保持良好可维护性的根本原因。
总结
zotero-arxiv-daily 用一个克制而完整的架构回答了"如何每天自动推荐感兴趣的论文":Executor单线程编排六阶段流水线,CorpusPaper/Paper数据类承载全链路类型契约,Hydra + OmegaConf 把"仓库默认配置、用户覆盖配置、环境变量秘密"三者干净分离,Retriever/Reranker 双插件系统让数据源与排序算法可独立替换,时间衰减加权让推荐始终偏向最近的研究兴趣,而"慢测试默认跳过 + 纯 stub 可测"的策略保障了零成本 CI 的可行性。对希望借鉴"订阅制个性化推荐 + 定时任务 + LLM 增强"工程范式的开发者来说,这份仓库是一份低门槛、可逐行验证的完整参考实现。
- 人工智能
- AI 应用
- RAG
- 科研
【免费下载链接】zotero-arxiv-daily
Recommend new arxiv papers of your interest daily according to your Zotero libarary.
相关推荐
如何用Zotero-arXiv-Daily打造专属论文推荐系统:每天3分钟获取领域前沿研究
如何用Zotero arXiv Daily打造专属论文推荐系统:每天3分钟获取领域前沿研究 Zotero arXiv Daily是一款基于Zotero图书馆的a
人工智能AI 应用RAG科研【亲测免费】 Zotero-arXiv-Daily:每日推荐您感兴趣的 arXiv 论文
Zotero arXiv Daily:每日推荐您感兴趣的 arXiv 论文 项目介绍 Zotero arXiv Daily 是一个开源项目,旨在帮助科研人员跟踪
人工智能AI 应用RAG科研如何快速上手FastDeploy:5分钟部署你的第一个AI模型
如何快速上手FastDeploy:5分钟部署你的第一个AI模型 FastDeploy是一个简单易用且高效的深度学习模型部署工具包,支持云、移动端和边缘设备,涵盖
人工智能大模型模型推理服务推理引擎模型量化强化学习本地部署
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考