基于GitHub Actions与Python的LLM论文自动化追踪系统设计与实现
1. 项目概述一个AI论文追踪器的诞生在AI领域尤其是大语言模型LLM方向每天都有海量的新论文在arXiv、ACL、EMNLP等顶会预印本网站上涌现。对于研究者、工程师甚至是狂热爱好者来说如何高效地追踪这些前沿动态从信息的洪流中精准捕捉到对自己有价值的内容一直是个头疼的问题。手动刷网站效率低下RSS订阅又过于庞杂而一些商业化的论文推送服务往往不够灵活无法完全贴合个人的研究方向。正是在这样的背景下我萌生了构建一个属于自己的、高度定制化的LLM论文每日追踪工具的想法并将其开源为xianshang33/llm-paper-daily这个项目。简单来说llm-paper-daily是一个自动化工具它能够每天定时从指定的学术源目前主要支持arXiv抓取与大语言模型相关的最新论文经过初步的筛选、分类和格式化处理后通过邮件或其它通知渠道推送给订阅者。它的核心价值在于“降噪”和“个性化”——你不再需要面对成千上万篇论文的标题列表而是能收到一份经过初步整理、只包含你关心领域比如“指令微调”、“长上下文窗口”、“推理能力提升”的论文摘要日报。这个项目适合谁呢首先当然是AI领域的研究人员和学生他们需要紧跟最前沿的技术以寻找灵感或进行文献综述。其次是工业界的算法工程师和技术负责人他们需要了解最新的模型架构或训练技巧评估其落地的可能性。最后即便是对AI有浓厚兴趣的开发者也可以通过它来保持对技术趋势的敏感度。我自己作为一个长期混迹在这个圈子的从业者深感信息过载之苦因此打造这个工具的首要目的就是为了解决自己的痛点并希望它能惠及更多同路人。2. 核心设计思路与技术选型2.1 需求拆解与架构设计在动手之前我仔细梳理了核心需求。一个合格的论文追踪器至少需要做到以下几点定时触发每天在固定时间如UTC时间凌晨自动运行。稳定抓取能够从arXiv等网站稳定、高效地获取论文元数据标题、作者、摘要、链接、分类。智能过滤根据用户预设的关键词列表对抓取到的论文进行筛选只保留相关项。内容组织将筛选后的论文按照某种逻辑如子领域、相关性进行分组和排序。格式化输出生成一份人类可读、美观的日报如Markdown、HTML格式。可靠推送将生成的日报通过稳定的渠道如邮件发送给用户。易于配置与部署用户无需复杂操作即可配置自己的关键词和接收邮箱。基于这些需求我设计了一个经典的数据流水线架构数据源 (arXiv API) - 抓取器 (Fetcher) - 过滤器 (Filter) - 组织器 (Organizer) - 渲染器 (Renderer) - 发送器 (Sender)整个流程由定时任务调度器驱动。这个架构清晰、模块化每个环节都可以独立开发和替换比如未来可以轻松增加ACL Anthology作为数据源或者将邮件推送替换为Slack、钉钉机器人通知。2.2 技术栈选型背后的考量技术选型上我遵循了“轻量、稳定、易维护”的原则。编程语言Python。这是毫无疑问的选择。AI社区生态围绕Python构建有大量成熟的库用于网络请求、数据处理、文本解析和邮件发送。其语法简洁开发效率高非常适合这种数据抓取和处理任务。定时任务GitHub Actions。这是我个人认为最巧妙的一环。传统做法可能需要自己维护一台服务器配置cron job。但利用GitHub Actions的免费额度我们可以实现“Serverless”的定时任务。只需在仓库中配置一个YAML文件设定每天运行的时间GitHub就会自动在一个干净的虚拟环境中执行我们的脚本。这省去了服务器维护的成本和精力也天然与代码仓库集成非常适合开源项目。网络请求与解析requestsfeedparser。arXiv提供了基于Atom的RSS订阅源feedparser库是解析RSS/Atom feed的瑞士军刀非常稳定。requests则用于处理一些额外的API调用或网页抓取如果需要。内容过滤纯Python字符串与正则匹配。初期过滤逻辑基于论文标题和摘要中是否包含用户定义的关键词。这里我采用了“关键词组”的概念即用户可以定义多个兴趣组每个组包含一系列相关的关键词如[“reasoning”, “chain-of-thought”, “CoT”]论文只要匹配任一组的任一关键词即可被收录。未来可以考虑集成更复杂的NLP模型进行语义相似度匹配但初期简单规则的效果和效率已经足够好。模板渲染Jinja2。为了生成格式优美的邮件或Markdown日报使用模板引擎是最佳实践。Jinja2语法直观功能强大可以轻松地将论文数据列表循环插入到预设的HTML或Markdown模板中动态生成最终内容。邮件发送smtplib(内置) 或第三方库如yagmail。Python内置的smtplib足以完成任务但yagmail等封装库让发送邮件变得更加简单几行代码就能搞定降低了使用门槛。注意使用GitHub Actions发送邮件时需要配置Secrets来存储邮箱的SMTP密码或授权码切勿将敏感信息直接写在代码或配置文件中。这是安全红线。3. 核心模块实现与实操要点3.1 arXiv论文元数据抓取器arXiv是预印本论文的主要阵地它提供了非常友好的OAI-PMH和RSS接口。我选择了其分类下的cs.CL计算与语言和cs.AI人工智能作为主要抓取范围因为LLM论文大多分布于此。核心的抓取函数大致如下import feedparser from datetime import datetime, timedelta import time def fetch_arxiv_papers(categories[cs.CL, cs.AI], days_back1): 从arXiv抓取最近几天指定分类的论文 Args: categories: arXiv分类列表如 [cs.CL, cs.AI] days_back: 抓取多少天内的论文 Returns: papers: 论文字典列表 base_url http://export.arxiv.org/api/query? papers [] # 计算查询日期范围 end_date datetime.utcnow() start_date end_date - timedelta(daysdays_back) # arXiv API 使用UTC日期格式需要调整 # 实际使用中更简单的方式是直接使用RSS feed按日期过滤 for category in categories: # 构建查询特定分类最新论文的RSS feed URL feed_url fhttp://arxiv.org/rss/{category} feed feedparser.parse(feed_url) for entry in feed.entries: # 解析每篇论文的详细信息 paper_id entry.id.split(/abs/)[-1] published datetime(*entry.published_parsed[:6]) # 只保留最近days_back天的论文 if datetime.utcnow() - published timedelta(daysdays_back): continue paper { id: paper_id, title: entry.title, authors: [author.name for author in entry.authors], summary: entry.summary, published: published, link: entry.link, pdf_link: entry.link.replace(/abs/, /pdf/), primary_category: category, all_categories: [tag[term] for tag in entry.tags] } papers.append(paper) # 避免请求过快 time.sleep(1) return papers实操要点与避坑速率限制arXiv API虽然没有严格的速率限制但过于频繁的请求仍可能被暂时拒绝。在循环抓取不同分类时务必添加time.sleep(1)这样的间隔做一个“友好的爬虫”。日期处理arXiv RSS feed中的论文是按更新日期排序的可能包含一些旧论文的版本更新。fetch_arxiv_papers函数中的日期过滤逻辑是关键确保我们只获取“每日新增”的论文而不是重复抓取。这里需要仔细处理时区问题统一使用UTC时间以避免混乱。字段解析feedparser解析出的entry对象结构稳定但authors和tags分类字段需要小心处理它们可能是列表或特殊对象。上述代码展示了如何安全地提取作者名和分类标签。3.2 基于关键词组的智能过滤逻辑抓取到原始论文列表后下一步是过滤。我设计了一个支持多组关键词的过滤系统。用户可以在配置文件如config.yaml中这样定义自己的兴趣interest_groups: - name: 推理与思维链 keywords: [reasoning, chain of thought, cot, logical, inference] - name: 高效微调 keywords: [fine-tuning, parameter-efficient, lora, adapters, prompt tuning] - name: 长上下文 keywords: [long context, extended context, window, attention, kv cache]过滤器的核心逻辑是对于每一篇论文检查其标题和摘要中是否出现了任何一个兴趣组中的任何一个关键词不区分大小写。为了提高匹配质量我采用了简单的词边界正则匹配避免将“attention”匹配到“attentional”这种不相关的情况。import re def filter_papers_by_interests(papers, interest_groups): 根据兴趣组过滤论文 Args: papers: 抓取到的论文列表 interest_groups: 配置的兴趣组列表 Returns: filtered_papers: 过滤后的论文列表每篇论文会附加匹配到的兴趣组信息 filtered_papers [] for paper in papers: matched_groups [] text_to_search f{paper[title]} {paper[summary]}.lower() for group in interest_groups: group_name group[name] keywords group[keywords] for keyword in keywords: # 使用正则匹配单词边界提高准确性 pattern r\b re.escape(keyword.lower()) r\b if re.search(pattern, text_to_search): matched_groups.append(group_name) break # 匹配到该组一个关键词即可跳出该组循环 if matched_groups: paper[matched_groups] matched_groups filtered_papers.append(paper) return filtered_papers注意事项关键词设计关键词的选择需要技巧。太宽泛如“model”会匹配到太多无关论文太生僻如某个特定模型的全称可能会漏掉相关论文。建议从你精读过的高质量论文中提取高频、有区分度的术语并同时考虑其常见变体如缩写“LoRA”和全称“Low-Rank Adaptation”。假阳性与假阴性基于关键词的匹配必然存在误差。例如一篇关于“注意力机制在长序列中的优化”的论文可能因为包含“attention”而被“长上下文”组捕获这也许是合理的。但如果一篇讲“视觉注意力”的论文也被捕获就是假阳性。目前阶段我倾向于“宁可错杀不可放过”在日报中看到一两篇不相关的论文总比漏掉一篇重要的要好。用户可以通过不断调整关键词列表来优化过滤效果。3.3 使用Jinja2生成美观的日报模板过滤后的论文列表是原始数据我们需要将其转化为易于阅读的格式。我选择使用Jinja2模板引擎来生成HTML格式的邮件正文。一个简单的模板示例daily_report_template.html如下!DOCTYPE html html head style body { font-family: sans-serif; line-height: 1.6; } .paper { border-left: 4px solid #3498db; padding-left: 15px; margin-bottom: 25px; } .title { font-size: 1.2em; font-weight: bold; margin-bottom: 5px; } .title a { color: #2c3e50; text-decoration: none; } .title a:hover { text-decoration: underline; } .authors, .summary { color: #555; margin-bottom: 8px; } .tags { font-size: 0.9em; } .tag { display: inline-block; background: #ecf0f1; padding: 3px 8px; border-radius: 3px; margin-right: 5px; margin-bottom: 5px; } .group-header { color: #e74c3c; border-bottom: 2px solid #e74c3c; padding-bottom: 5px; margin-top: 30px; } /style /head body h2 LLM Paper Daily - {{ date }}/h2 p今日共追踪到 strong{{ papers|length }}/strong 篇您可能感兴趣的论文。/p {% for group_name, group_papers in papers_by_group.items() %} div classgroup-header h3️ {{ group_name }} ({{ group_papers|length }}篇)/h3 /div {% for paper in group_papers %} div classpaper div classtitle a href{{ paper.link }}{{ paper.title }}/a {% if paper.pdf_link %}[a href{{ paper.pdf_link }}PDF/a]{% endif %} /div div classauthors{{ paper.authors|join(, ) }}/div div classsummary{{ paper.summary[:300] }}{% if paper.summary|length 300 %}...{% endif %}/div div classtags span classtag{{ paper.primary_category }}/span {% for group in paper.matched_groups %} span classtag stylebackground:#d5f4e6;{{ group }}/span {% endfor %} /div /div {% endfor %} {% endfor %} hr p stylefont-size:0.9em; color:#888; 本日报由 llm-paper-daily 自动生成。如需调整订阅兴趣请修改项目配置。 /p /body /html渲染过程非常简单from jinja2 import Environment, FileSystemLoader import datetime def render_daily_report(filtered_papers): # 按匹配到的兴趣组重新组织论文 papers_by_group {} for paper in filtered_papers: for group in paper.get(matched_groups, []): papers_by_group.setdefault(group, []).append(paper) # 设置Jinja2环境 env Environment(loaderFileSystemLoader(templates/)) template env.get_template(daily_report_template.html) # 渲染模板 html_content template.render( datedatetime.datetime.utcnow().strftime(%Y-%m-%d), papersfiltered_papers, papers_by_grouppapers_by_group ) return html_content这样我们就得到了一个按兴趣组分类、包含论文标题带链接、作者、摘要缩略图和标签的HTML日报。4. 自动化部署与推送GitHub Actions实战整个项目的“自动化大脑”是GitHub Actions。我们在项目根目录下的.github/workflows目录中创建一个YAML文件例如daily-paper.yml。name: Daily LLM Paper Digest on: schedule: # 每天UTC时间00:01运行 (即北京时间08:01) - cron: 1 0 * * * workflow_dispatch: # 允许手动触发 jobs: fetch-and-send: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt # requirements.txt 包含 feedparser, requests, jinja2, pyyaml, yagmail等 - name: Run the paper fetcher and send email env: # 将邮箱配置存储在GitHub仓库的Secrets中 SENDER_EMAIL: ${{ secrets.SENDER_EMAIL }} SENDER_PASSWORD: ${{ secrets.SENDER_PASSWORD }} RECEIVER_EMAIL: ${{ secrets.RECEIVER_EMAIL }} SMTP_SERVER: ${{ secrets.SMTP_SERVER }} SMTP_PORT: ${{ secrets.SMTP_PORT }} run: python main.py关键配置解析触发条件schedule下的cron表达式1 0 * * *表示每天UTC时间0点1分运行。workflow_dispatch允许我们在GitHub页面上手动点击运行方便测试。运行环境runs-on: ubuntu-latest指定使用最新的Ubuntu系统作为临时虚拟机。依赖安装步骤中会安装requirements.txt里列出的所有Python库。务必确保这个文件存在且内容正确。安全秘钥邮箱的SMTP密码等敏感信息绝不能写在代码或YAML文件里。我们通过env环境变量引入其值在GitHub仓库的Settings - Secrets and variables - Actions中设置。这是保障项目安全的核心操作。main.py脚本则是所有功能的集大成者它按顺序调用上述模块# main.py 概览 def main(): # 1. 加载用户配置 config load_config(config.yaml) # 2. 抓取论文 all_papers fetch_arxiv_papers(categoriesconfig[arxiv_categories]) # 3. 过滤论文 filtered_papers filter_papers_by_interests(all_papers, config[interest_groups]) if not filtered_papers: print(今日无匹配论文无需发送。) return # 4. 渲染日报 report_html render_daily_report(filtered_papers) # 5. 发送邮件 send_email_via_smtp( receiveros.environ[RECEIVER_EMAIL], subjectfLLM Paper Daily - {datetime.date.today()}, html_contentreport_html, senderos.environ[SENDER_EMAIL], passwordos.environ[SENDER_PASSWORD], smtp_serveros.environ[SMTP_SERVER], smtp_portint(os.environ.get(SMTP_PORT, 587)) ) print(日报发送成功) if __name__ __main__: main()5. 进阶优化与个性化扩展基础版本运行稳定后可以考虑以下方向进行深化使其更加强大和智能。5.1 引入向量数据库与语义搜索关键词匹配的局限性在于它无法理解语义。例如你的关键词是“efficient fine-tuning”高效微调但一篇论文的表述是“parameter-efficient transfer learning”参数高效迁移学习虽然核心意思高度相关但可能因为字面不匹配而被漏掉。解决方案是引入文本嵌入模型和向量数据库。生成嵌入使用如all-MiniLM-L6-v2这类轻量级句子转换模型将每篇论文的“标题摘要”转换为一个高维向量嵌入。存储向量将向量存入ChromaDB或Qdrant这类轻量级向量数据库同时关联论文元数据。语义查询将用户的兴趣描述如“关于大模型推理效率提升的方法”也转换为向量然后在向量数据库中进行相似度搜索如余弦相似度返回最相关的Top-K篇论文。这样过滤逻辑就从“字面匹配”升级为“语义匹配”能发现更多潜在相关的论文极大地提升了召回率。5.2 实现多数据源支持arXiv虽然是主流但并非唯一来源。许多顶级会议NeurIPS, ICLR, ACL的论文在开放评审或录用后也会发布在官方页面。我们可以为项目增加多数据源适配器。class PaperFetcher: def __init__(self, sourcearxiv): self.source source def fetch(self): if self.source arxiv: return self._fetch_from_arxiv() elif self.source acl: return self._fetch_from_acl_anthology() # ... 其他数据源 def _fetch_from_arxiv(self): # 之前的arXiv抓取逻辑 pass def _fetch_from_acl_anthology(self): # 解析ACL Anthology的RSS或API pass通过抽象出PaperFetcher接口我们可以轻松扩展新的数据源只需实现对应的_fetch_from_xxx方法即可。5.3 构建Web界面进行个性化管理对于非技术用户编辑YAML配置文件可能有些门槛。一个更友好的方式是开发一个简单的Web界面例如使用Flask或Streamlit让用户可以通过勾选、输入关键词等方式来管理自己的兴趣组并查看历史日报。# 一个极简的Streamlit示例 import streamlit as st import yaml st.title(LLM Paper Daily 管理后台) st.write(管理你的论文订阅兴趣) # 从文件加载现有配置 with open(config.yaml, r) as f: config yaml.safe_load(f) # 让用户编辑兴趣组 interest_groups st.data_editor(config[interest_groups]) if st.button(保存配置): config[interest_groups] interest_groups with open(config.yaml, w) as f: yaml.dump(config, f) st.success(配置已保存)这样项目的易用性将得到质的提升。6. 常见问题与排查实录在实际部署和运行过程中你可能会遇到以下问题。这里记录了我踩过的坑和解决方案。6.1 GitHub Actions运行失败问题工作流在Install dependencies或Run the paper fetcher步骤失败报错ModuleNotFoundError。排查检查requirements.txt文件是否存在于仓库根目录并且列出了所有必需的包feedparser,requests,jinja2,pyyaml等。在本地环境运行pip install -r requirements.txt看是否能成功安装。确保main.py或其他入口文件在Actions的虚拟环境中路径正确。解决在Actions的Install dependencies步骤后可以添加一个pip list步骤列出已安装的包确认安装成功。6.2 邮件发送失败问题Actions日志显示脚本运行成功但收不到邮件。或者报错SMTPAuthenticationError。排查检查Secrets这是最常见的原因。确保在GitHub仓库Secrets中设置的SENDER_PASSWORD是SMTP授权码而不是邮箱的登录密码。对于QQ邮箱、163邮箱、Gmail等都需要在邮箱设置中专门生成一个用于第三方客户端登录的授权码。检查SMTP配置SMTP_SERVER和SMTP_PORT是否正确例如QQ邮箱是smtp.qq.com和465SSL或587TLS。Gmail是smtp.gmail.com和587。检查发件人邮箱设置部分邮箱如Gmail需要开启“允许不够安全的应用”选项不过现在更推荐使用OAuth2.0但这更复杂。QQ邮箱需要开启SMTP服务。检查接收邮箱确认RECEIVER_EMAIL没有拼写错误并检查垃圾邮件文件夹。解决建议先在本地环境使用相同的Secrets值存储为本地环境变量测试邮件发送功能确保无误后再提交到GitHub Actions。6.3 抓取到的论文数量为0或异常少问题日志显示抓取成功但过滤后论文数为0或者连续几天都只有一两篇。排查检查arXiv分类确认config.yaml中arxiv_categories设置正确。cs.CL和cs.AI是主要分类你也可以添加cs.LG机器学习等。检查关键词你的兴趣组关键词是否太偏、太具体尝试将一两个关键词替换为更通用的相关术语。打开生成的HTML报告可以先本地运行保存为文件查看看看原始抓取到的论文列表里是否有你期望的论文但被过滤掉了。检查日期逻辑确认fetch_arxiv_papers函数中的days_back参数设置合理。如果设为1但你在UTC时间凌晨运行可能只能抓到过去几小时内发布的论文。可以适当调大比如2以确保覆盖完整的一天。网络问题偶尔arXiv API可能暂时不可用。可以在代码中添加重试机制和更详细的错误日志。解决在本地运行脚本并打印出抓取到的原始论文数量以及过滤前后的对比这是最直接的调试方式。6.4 日报格式错乱或内容不全问题收到的邮件排版混乱或者摘要显示不完整。排查HTML模板检查Jinja2模板中的HTML和CSS是否合法。过于复杂的CSS可能在某些邮件客户端如Outlook中不被支持。尽量使用内联样式和简单的表格布局以保障兼容性。摘要截断模板中使用了{{ paper.summary[:300] }}来截断长摘要。如果摘要本身包含HTML实体如直接截断可能导致HTML标签不闭合从而破坏整个页面结构。解决对于摘要截断一个更安全的方法是先使用html.escape()处理摘要文本或者使用Jinja2的striptags过滤器移除HTML标签后再截断。对于CSS坚持使用最基本的内联样式属性。这个项目从解决个人需求出发最终形成了一个有一定通用性的小工具。它的价值不在于技术有多高深而在于切实地解决了一个高频、刚需的痛点。通过开源我收到了不少反馈和建议比如增加对特定会议论文集的抓取、集成论文摘要翻译等这些都成为了项目迭代的方向。技术服务于需求一个简单的自动化脚本如果能每天为你节省15分钟的信息筛选时间并让你更有可能抓住前沿动态那它的投入产出比就非常高。如果你也在为追踪AI论文而烦恼不妨Fork这个项目根据自己的兴趣配置一下明天早上就能在邮箱里收到一份专属的论文早餐了。