1. 项目概述:一个AI论文追踪器的诞生
作为一名长期混迹于AI研究一线的从业者,我深知信息过载的痛。每天,ArXiv、ACL Anthology、OpenReview等平台都会涌现出数十甚至上百篇与大型语言模型(LLM)相关的新论文。从模型架构的革新、训练技巧的突破,到具体应用场景的落地,信息洪流让人应接不暇。手动追踪不仅效率低下,还极易错过那些“藏在深巷”里的关键工作。正是在这种背景下,我萌生了构建一个自动化、个性化LLM论文追踪工具的想法,并将其开源为xianshang33/llm-paper-daily这个项目。
简单来说,llm-paper-daily是一个旨在帮助研究者、工程师和爱好者高效获取、筛选和整理每日最新LLM相关学术论文的工具。它的核心价值在于“降噪”和“提效”。它不是一个简单的RSS订阅器,而是通过一系列自动化流程,从源头抓取、关键词过滤、信息结构化,到最终通过邮件或Webhook进行个性化推送,形成了一套完整的解决方案。无论你是想紧跟SOTA(State-of-the-art)模型动态,还是专注于某个细分领域(如推理、对齐、长文本处理),这个工具都能帮你从海量信息中打捞出真正有价值的内容。
2. 核心需求与设计思路拆解
2.1 痛点分析:我们到底需要什么?
在动手之前,我花了些时间梳理了自身以及身边同行们的核心痛点:
- 信息源分散:高质量的论文分布在多个平台,没有一个聚合入口。
- 筛选成本高:即使订阅了某个平台的更新,90%的论文标题可能都与你的兴趣无关,需要人工逐一点开判断。
- 信息结构化差:获取到的往往是简单的标题和链接,缺乏摘要、作者、关键代码/项目链接等结构化信息,无法快速评估论文价值。
- 缺乏个性化:每个人的研究兴趣不同,有人关心高效微调,有人专注多模态理解,通用的推送无法满足个性化需求。
- 无法回溯与归档:今天看到一篇好论文,过几天想再找,可能就淹没在历史记录里了。
基于这些痛点,我确定了项目的核心设计目标:自动化、个性化、结构化、可归档。
2.2 技术选型与架构设计
为了实现上述目标,我选择了以Python为核心的技术栈,这主要基于其丰富的科学计算和网络爬虫生态。整个系统的架构可以概括为“采集-处理-分发”三层管道。
采集层:负责从目标数据源抓取原始数据。我首选了ArXiv的官方API,因为它稳定、免费且提供了结构化的元数据(标题、摘要、作者、分类、PDF链接等)。对于其他没有开放API的平台,则采用requests和BeautifulSoup4进行定向爬取,但会严格遵守网站的robots.txt规则,并设置合理的请求间隔,避免对服务器造成压力。
处理层:这是项目的“大脑”,负责核心的过滤和增强。
- 关键词过滤:这是实现个性化的关键。用户可以通过配置文件定义一组关键词(如“LoRA”、“reasoning”、“retrieval-augmented”)。系统会将论文标题和摘要与这些关键词进行匹配,支持简单的布尔逻辑(AND/OR)和模糊匹配,以筛选出相关论文。
- 信息结构化与增强:从API或网页中解析出的信息被整理成统一的JSON格式。此外,我还集成了第三方工具,例如尝试调用学术搜索引擎的API(在合规前提下)来补充论文的被引用数、代码仓库链接(如GitHub链接)等信息,让推送内容更丰富。
- 去重机制:利用论文的唯一标识符(如ArXiv ID)或标题+作者的特征哈希,确保同一天或历史周期内不会重复推送同一篇论文。
分发层:负责将处理好的内容送达用户。我实现了两种主流方式:
- 邮件推送:使用
smtplib库,通过配置好的SMTP服务器(如Gmail、QQ邮箱、SendGrid等)发送格式精美的HTML日报。邮件模板清晰列出了论文标题、作者、摘要、链接以及匹配到的关键词,一目了然。 - Webhook推送:为了适配现代工作流,我增加了Webhook支持。可以将每日摘要推送到企业微信机器人、钉钉机器人、Slack频道甚至自己的笔记软件(如Notion)中,实现无缝集成。
整个系统通过schedule库或操作系统的定时任务(如cron)驱动,实现每日定时运行。
注意:在设计爬虫部分时,务必保持友好。过快的请求频率可能导致IP被封锁。我的经验是,为每个目标网站设置至少5-10秒的请求间隔,并实现简单的错误重试和日志记录机制,这对于长期稳定运行至关重要。
3. 核心模块实现与实操要点
3.1 数据采集器的稳健实现
数据采集是流水线的源头,必须保证其稳定性和可扩展性。以ArXiv API为例,其查询接口非常灵活。
import arxiv import logging def fetch_arxiv_papers(keywords, max_results=100): """ 从ArXiv获取与关键词相关的近期论文 """ client = arxiv.Client() # 构建查询字符串,例如:all:“large language model” AND (abs:“reasoning” OR abs:“LoRA”) query = f'all:"large language model" AND ({" OR ".join([f"abs:\\"{k}\\"" for k in keywords])})' search = arxiv.Search( query=query, max_results=max_results, sort_by=arxiv.SortCriterion.SubmittedDate, sort_order=arxiv.SortOrder.Descending ) papers = [] try: for result in client.results(search): paper_info = { "id": result.entry_id, "title": result.title, "abstract": result.summary, "authors": [a.name for a in result.authors], "published": result.published.strftime("%Y-%m-%d"), "pdf_url": result.pdf_url, "primary_category": result.primary_category, "categories": result.categories } papers.append(paper_info) except Exception as e: logging.error(f"从ArXiv获取数据失败: {e}") return papers实操要点:
- 查询构造:ArXiv查询语法支持
ti(标题)、abs(摘要)、au(作者)等字段。精心构造查询式可以极大减少后续过滤的压力。例如,abs:“reinforcement learning from human feedback”能精准抓取摘要中包含RLHF的论文。 - 错误处理与日志:网络请求充满不确定性。必须用
try-except包裹核心请求逻辑,并记录详细的日志,便于问题排查。 - 速率限制:即使使用官方API,也应避免短时间内发起大量请求。可以引入
time.sleep()或在客户端配置延迟。
3.2 个性化过滤引擎的设计
采集到的论文可能仍有大量无关内容。过滤引擎需要平衡准确性和灵活性。
import re from typing import List, Dict class PaperFilter: def __init__(self, user_keywords: List[str], match_mode: str = "or"): """ :param user_keywords: 用户定义的关键词列表 :param match_mode: “or” 表示匹配任一关键词,“and” 表示需匹配所有关键词 """ self.keywords = [k.lower() for k in user_keywords] self.match_mode = match_mode # 可以预编译一些常见的关键词变体或同义词正则 self.patterns = {kw: re.compile(rf'\b{re.escape(kw)}\b', re.IGNORECASE) for kw in self.keywords} def is_relevant(self, paper: Dict) -> bool: """判断一篇论文是否相关""" text_to_check = f"{paper['title']} {paper['abstract']}".lower() matches = [] for kw in self.keywords: # 使用正则进行单词边界匹配,避免匹配到单词的一部分(如“cat”匹配到“category”) if self.patterns[kw].search(text_to_check): matches.append(kw) if self.match_mode == "or": return len(matches) > 0 elif self.match_mode == "and": return set(self.keywords).issubset(set(matches)) return False def get_matched_keywords(self, paper: Dict) -> List[str]: """返回匹配到的具体关键词,用于在推送中高亮显示""" text_to_check = f"{paper['title']} {paper['abstract']}".lower() matched = [] for kw in self.keywords: if self.patterns[kw].search(text_to_check): matched.append(kw) return matched实操心得:
- 模糊匹配与精确匹配:简单的关键词匹配可能不够。例如,用户关心“efficient fine-tuning”,但论文中可能只写“parameter-efficient tuning”。初期可以采用模糊匹配(如计算文本相似度),但计算成本较高。一个折中的办法是维护一个“关键词-同义词”映射表。
- 基于分类的过滤:ArXiv论文自带分类标签(如
cs.CL代表计算语言学,cs.AI代表人工智能)。在配置中允许用户排除某些不感兴趣的类别(如cs.DC分布式计算),可以进一步降噪。 - 负向关键词:允许用户设置“不想看到”的关键词,对于过滤掉某些过于热门但你不关心的子领域非常有效。
3.3 内容生成与推送
将过滤后的论文列表转化为用户友好的格式并推送出去,是价值交付的最后一步。
邮件推送示例: 我使用Jinja2模板引擎来生成HTML邮件,这样可以使日报的排版更美观、更专业。
from jinja2 import Template import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart def send_email_report(filtered_papers, recipient, smtp_config): """生成并发送HTML日报邮件""" # 1. 准备数据 context = { "date": datetime.now().strftime("%Y-%m-%d"), "papers": filtered_papers, # 每篇paper包含 matched_keywords "total_count": len(filtered_papers) } # 2. 渲染HTML模板 html_template = """ <html> <body> <h2>📚 LLM论文日报 {{ date }}</h2> <p>今日共筛选到 <strong>{{ total_count }}</strong> 篇相关论文。</p> <hr> {% for paper in papers %} <div style="margin-bottom: 20px; padding: 10px; border-left: 4px solid #4CAF50;"> <h3><a href="{{ paper.pdf_url }}">{{ paper.title }}</a></h3> <p><strong>作者:</strong> {{ paper.authors | join(', ') }}</p> <p><strong>发布于:</strong> {{ paper.published }} | <strong>分类:</strong> {{ paper.categories | join(', ') }}</p> <p><strong>匹配关键词:</strong> {% for kw in paper.matched_keywords %} <span style="background-color: #e0f7fa; padding: 2px 6px; margin: 2px; border-radius: 3px; font-size: 0.9em;">{{ kw }}</span> {% endfor %} </p> <p><strong>摘要:</strong><br>{{ paper.abstract[:300] }}{% if paper.abstract|length > 300 %}...{% endif %}</p> </div> {% endfor %} </body> </html> """ template = Template(html_template) html_content = template.render(context) # 3. 构建并发送邮件 msg = MIMEMultipart('alternative') msg['Subject'] = f'LLM论文日报 {context["date"]} - {context["total_count"]}篇新论文' msg['From'] = smtp_config['sender'] msg['To'] = recipient msg.attach(MIMEText(html_content, 'html')) with smtplib.SMTP_SSL(smtp_config['server'], smtp_config['port']) as server: server.login(smtp_config['username'], smtp_config['password']) server.send_message(msg) logging.info(f"邮件已发送至 {recipient}")Webhook推送示例(以企业微信机器人为例):
import requests import json def send_wechat_webhook(papers, webhook_url): """推送Markdown格式消息到企业微信机器人""" if not papers: return papers_text = "" for i, paper in enumerate(papers[:10]): # 限制前10篇,避免消息过长 kw_tags = " ".join([f"`{kw}`" for kw in paper.get('matched_keywords', [])]) papers_text += f"{i+1}. **{paper['title']}**\n" papers_text += f" 作者: {', '.join(paper['authors'][:3])}{'等' if len(paper['authors'])>3 else ''}\n" papers_text += f" 关键词: {kw_tags}\n" papers_text += f" [阅读摘要与PDF]({paper['pdf_url']})\n\n" markdown_content = f"### 🚀 今日LLM论文精选 ({len(papers)}篇)\n---\n{papers_text}" payload = { "msgtype": "markdown", "markdown": { "content": markdown_content } } try: resp = requests.post(webhook_url, json=payload, timeout=10) resp.raise_for_status() logging.info("Webhook推送成功") except requests.exceptions.RequestException as e: logging.error(f"Webhook推送失败: {e}")提示:邮件推送更适合深度阅读,而Webhook推送(如群聊机器人)适合团队共享和快速浏览。在实际部署中,我建议两者都配置。个人用邮件归档,团队用机器人同步信息。
4. 部署、配置与持续运行
4.1 环境配置与依赖管理
项目使用requirements.txt或pyproject.toml来管理Python依赖。核心依赖包括arxiv、requests、beautifulsoup4、jinja2、schedule等。为了隔离环境,强烈建议使用虚拟环境(venv或conda)。
# 克隆项目 git clone https://github.com/xianshang33/llm-paper-daily.git cd llm-paper-daily # 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装依赖 pip install -r requirements.txt4.2 配置文件详解
项目的灵活性很大程度上来自于配置文件(如config.yaml)。用户无需修改代码,只需编辑此文件即可定制自己的论文追踪器。
# config.yaml 示例 user: interests: - keywords: ["LoRA", "QLoRA", "parameter-efficient fine-tuning"] match_mode: "or" category_blacklist: ["cs.CV", "cs.LG"] # 排除计算机视觉和机器学习通用类 - keywords: ["reasoning", "chain of thought", "CoT"] match_mode: "or" - keywords: ["retrieval augmented generation", "RAG"] match_mode: "and" # 必须同时包含“retrieval”和“generation” sources: arxiv: enabled: true max_results_per_query: 150 categories: ["cs.CL", "cs.AI", "stat.ML"] # 限定主要类别 acl_anthology: enabled: false # 暂时关闭 base_url: "https://aclanthology.org" notification: email: enabled: true sender: "your-email@gmail.com" recipients: ["yourself@domain.com", "teammate@domain.com"] smtp_server: "smtp.gmail.com" smtp_port: 465 smtp_username: "your-email@gmail.com" smtp_password: "your-app-password" # 注意:使用应用专用密码,非邮箱密码 webhook: wecom: enabled: true url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY" slack: enabled: false url: "YOUR_SLACK_WEBHOOK_URL" schedule: daily_run_time: "08:00" # 每天上午8点运行 timezone: "Asia/Shanghai"配置关键点:
- 兴趣组:
interests是一个列表,允许你设置多组兴趣。系统会为每一组兴趣独立执行查询和过滤,这样推送的论文可以按主题分组,逻辑更清晰。 - 应用专用密码:对于Gmail等邮箱,务必使用生成的“应用专用密码”而非邮箱登录密码,否则会因安全策略导致登录失败。
- 时区设置:定时任务的时间必须考虑服务器所在时区,确保在正确的时间点触发。
4.3 部署方案选择
本地电脑(最简单):在个人电脑上运行,适合个人使用。缺点是电脑需要常开。
# 直接运行主脚本 python main.py # 或使用schedule库持续运行 python scheduler.py云服务器(推荐):购买一台最低配置的云服务器(如1核1G),使用系统的定时任务(cron)是最稳定、成本最低的方案。
# 编辑crontab crontab -e # 添加一行,例如每天北京时间早上8点运行 0 8 * * * cd /path/to/llm-paper-daily && /usr/bin/python3 main.py >> /path/to/log/daily.log 2>&1Serverless函数(最省心):利用云厂商的Serverless服务(如AWS Lambda, Google Cloud Functions, 阿里云函数计算)。将代码打包,配置定时触发器。优点是无需管理服务器,按实际运行时间计费,成本极低。但调试和依赖管理可能稍复杂。
我个人采用的方案:一台轻量级云服务器 + cron。理由是其稳定性最高,日志查看方便,并且可以同时运行其他一些小脚本。每月成本仅需少量费用。
5. 高级功能与扩展思路
基础功能稳定后,可以在此基础上添加更多提升体验的功能。
5.1 论文摘要的总结与翻译
对于非英语母语的研究者,或者想快速把握论文核心的读者,自动摘要和翻译是杀手锏功能。可以利用开源的LLM(如ChatGLM、Qwen)或大厂提供的API(需注意成本)来实现。
# 概念性代码,展示集成思路 def summarize_and_translate(abstract, api_key=None): """ 使用大模型API对摘要进行总结和翻译 """ # 本地模型方案(示例) # from transformers import pipeline # summarizer = pipeline("summarization", model="facebook/bart-large-cnn") # summary = summarizer(abstract, max_length=100, min_length=30, do_sample=False)[0]['summary_text'] # 调用API方案(示例,需替换为实际API) prompt = f"请用中文简要总结以下英文论文摘要的核心贡献(不超过100字):\n\n{abstract}" # 调用OpenAI GPT或国内合规的类似API # response = openai.ChatCompletion.create(...) # summary_zh = response.choices[0].message.content # 此处返回模拟结果 summary_zh = f"[模拟总结] 该论文主要研究了...,提出了...方法,在...数据集上取得了...效果。" return summary_zh注意事项:此功能会显著增加运行时间和成本(如果使用商用API)。建议作为可选功能,或仅对高相关性(匹配关键词最多)的论文启用。
5.2 历史数据存储与检索
简单的日报推送解决了“看新”的问题,但“查旧”同样重要。可以引入一个轻量级数据库(如SQLite)来存储所有处理过的论文。
import sqlite3 from datetime import datetime def init_database(db_path='papers.db'): conn = sqlite3.connect(db_path) c = conn.cursor() c.execute(''' CREATE TABLE IF NOT EXISTS papers (id TEXT PRIMARY KEY, title TEXT, abstract TEXT, authors TEXT, published DATE, pdf_url TEXT, source TEXT, matched_keywords TEXT, added_date DATE) ''') conn.commit() conn.close() def store_papers(papers_list, db_path='papers.db'): conn = sqlite3.connect(db_path) c = conn.cursor() today = datetime.now().date() for p in papers_list: # 避免重复插入 c.execute("SELECT id FROM papers WHERE id=?", (p['id'],)) if c.fetchone() is None: c.execute(''' INSERT INTO papers VALUES (?,?,?,?,?,?,?,?,?) ''', (p['id'], p['title'], p['abstract'], ','.join(p['authors']), p['published'], p['pdf_url'], 'arxiv', ','.join(p.get('matched_keywords', [])), today)) conn.commit() conn.close()有了数据库,就可以轻松实现按关键词、作者、时间范围进行历史论文检索的功能,甚至可以做一个简单的Web界面来查询。
5.3 多数据源聚合
除了ArXiv,还有许多重要的论文来源:
- ACL Anthology:计算语言学顶会论文库。
- OpenReview:许多会议(如NeurIPS, ICLR)的开放评审平台,可以获取到最新提交的论文。
- 特定会议官网:在CVPR、ICML等会议召开前后,官网会放出被接收论文列表。
扩展新的数据源,本质上就是实现一个新的“采集器”类,遵循统一的接口,将其集成到主流程中。这体现了项目良好的模块化设计。
6. 常见问题与排查技巧实录
在实际运行和维护过程中,我遇到了不少典型问题。这里记录下来,希望能帮你绕过这些坑。
6.1 数据源抓取失败
问题现象:日志显示ConnectionError或Timeout,或者返回的数据为空。
- 排查网络:首先确认服务器或本地机器网络通畅,可以
ping或curl一下目标网站。 - 检查API限制:查看目标数据源(如ArXiv)的API使用条款,是否有速率限制。我的脚本因为请求太快,IP曾被暂时限制过。解决方案是增加请求间隔
time.sleep(5)。 - 更新解析逻辑:网站结构可能发生变化。如果使用网页爬虫,需要定期检查并更新HTML元素的CSS选择器或XPath。为爬虫部分编写单元测试,定期运行,能及早发现问题。
- 使用重试机制:对于网络请求,实现一个简单的重试装饰器是很好的实践。
import time from functools import wraps import logging def retry(max_attempts=3, delay=2): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: logging.warning(f"Attempt {attempt+1} failed: {e}") if attempt < max_attempts - 1: time.sleep(delay) else: raise return wrapper return decorator @retry(max_attempts=3, delay=5) def fetch_from_source(url): # ... 请求逻辑
6.2 邮件推送被拒收或进入垃圾箱
问题现象:邮件发送成功日志,但收件箱没有收到。
- 检查发件人配置:确保SMTP服务器、端口、用户名密码正确。对于Gmail,需要开启“两步验证”并生成“应用专用密码”。
- 检查收件人地址:确认无误。
- 检查垃圾箱:很可能邮件被标记为垃圾邮件。优化方法:
- 设置合理的发件人名称:如“LLM论文助手”,不要用无意义的字符串。
- 优化邮件内容:HTML模板不要太花哨,避免包含太多外链或敏感词汇(如大量“free”、“prize”等)。加入纯文本版本(MIMEMultipart的
alternative就是为此设计)。 - 配置SPF/DKIM记录:如果你使用自己的域名邮箱,务必在域名DNS中正确配置SPF和DKIM记录,这是提升邮件送达率的关键。对于个人用户使用第三方邮箱(如Gmail、QQ),这一步通常由服务商处理好了。
6.3 关键词过滤不准
问题现象:推送的论文不相关,或者漏掉了相关论文。
- 调整匹配模式:将
match_mode从宽松的or改为严格的and,或者反之。 - 优化关键词列表:
- 增加同义词:例如,除了“efficient fine-tuning”,加上“PEFT”、“parameter-efficient”。
- 使用词组:用引号包裹词组,如
“reinforcement learning from human feedback”,避免拆开匹配。 - 引入负向关键词:在配置中增加
exclude_keywords: ["survey", "review"]来过滤掉综述类文章(如果你不想要的话)。
- 引入摘要重要性评分:更高级的做法是,不只判断关键词是否出现,还判断其出现的频率和位置(标题中的关键词权重高于摘要)。甚至可以微调一个小的文本分类模型来打分,但这属于进阶玩法了。
6.4 定时任务不执行
问题现象:配置了cron或schedule库,但脚本没有在预定时间运行。
- 检查cron日志:Linux下可以查看
/var/log/cron或syslog,看cron是否尝试执行了你的命令。 - 检查环境变量:cron执行的环境与用户shell环境不同,可能找不到
python3命令或项目依赖。在cron命令中使用绝对路径,或者在脚本开头通过source命令加载虚拟环境。# 在cron中,可以这样写 0 8 * * * cd /path/to/project && /path/to/venv/bin/python /path/to/project/main.py >> /path/to/log.log 2>&1 - 检查脚本权限:确保Python脚本有可执行权限 (
chmod +x main.py)。 - 对于
schedule库:确保主程序是持续运行的,并且没有因为未捕获的异常而退出。将主循环放在try-except块中,并记录所有异常。
6.5 项目依赖更新导致问题
问题现象:某天开始,脚本突然报错,提示某个模块找不到或方法不存在。
- 锁定依赖版本:在
requirements.txt中指定关键依赖的具体版本号,而不是使用>=。例如arxiv==2.0.0。 - 使用虚拟环境:这能隔离项目环境,避免与其他Python项目冲突。
- 定期测试与更新:可以设置一个单独的测试任务,每周运行一次,检查在最新依赖下脚本是否依然正常工作。如果测试通过,再考虑更新生产环境的
requirements.txt。
这个项目从最初的一个简单脚本,逐步演化成一个功能相对完善的小型系统,它实实在在地提升了我追踪学术前沿的效率。开源出来,是希望它能成为一个“种子”,大家可以根据自己的需求进行修改和扩展。比如,有人为它增加了对PubMed(生物医学)论文的支持,有人集成了Telegram Bot推送,这些都是我最初没想到的精彩应用。技术工具的价值,正是在于解决具体问题,并在社区协作中不断生长。如果你在使用中有什么问题或改进想法,欢迎在项目仓库中交流。