ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AI论文周报系统全解析:从arXiv采集到大模型摘要的自动化实现

2026/8/30 3:38:06 拓冰建站 浏览量
AI论文周报系统全解析:从arXiv采集到大模型摘要的自动化实现 在实际的 AI 技术社区里跟进论文进展一直是刚需。DAIR.AI 上线 AI 论文周报平台本质上是在解决一个问题arXiv 上每天新增的论文数量太多研究者不可能靠人工把每周几百篇相关论文全部读完。周报平台要做的不是简单搬运论文标题而是把采集、筛选、摘要、排版和分发这一整条流程自动化让读者用最短时间抓住一周内值得关注的 AI 研究动态。这篇文章以 DAIR.AI 论文周报平台为背景梳理一套可以实操落地的周报系统设计。内容覆盖从 arXiv API 采集数据、论文筛选与评分、大模型摘要生成、Markdown 周报产出到定时调度与生产环境部署的完整链路。适合正在搭建技术内容平台、内部论文调研工具或 AI 信息聚合应用的开发者阅读。学完之后你可以基于这里的代码和配置快速搭出一个能自动运行的 AI 论文周报系统也可以根据团队需要扩展出更多内容形态。1. 先理解 DAIR.AI 论文周报平台的定位与核心流程1.1 平台定位不是内容堆砌而是高效筛选DAIR.AI 是以 AI 教育、论文综述和社区分享为核心的平台。论文周报不是把 arXiv 上所有论文列出来而是围绕“哪些工作值得读”这一目标做精选。这里的核心判断是论文数量会持续增长但读者的注意力是有限的。因此周报平台必须有一个明确的筛选逻辑告诉读者为什么选这篇、这篇解决了什么问题、和已有工作有什么差异。从这个角度看周报平台的技术难度不在“抓数据”而在“把数据变成可读的信息”。抓取 arXiv 接口是半小时能完成的工作但筛选规则、摘要质量、排版可读性、分发稳定性才是决定平台能否长期运行的关键。架构设计时要把这些环节拆成独立模块而不是写成一个几百行的脚本。1.2 周报生成的核心链路采集、筛选、摘要、排版、分发一条完整的 AI 论文周报生成链路可以拆成五个环节论文采集从 arXiv API 获取一周内新增或更新的论文元数据。论文筛选用分类、关键词、热度、引用等条件过滤出候选论文。摘要生成调用大模型对论文标题和摘要做二次凝练形成周报中的简介。周报排版把筛选和摘要结果填入 Markdown 模板生成可读文档。自动分发将 Markdown 转成 HTML、PDF 或邮件内容发布到站点或推送给订阅者。这五个环节相对独立适合用模块化代码实现。任何一个环节出问题都不应该影响其他环节。例如采集失败时周报可以基于上一次的数据继续生成而不是直接中断。摘要服务超时时可以跳过本轮的摘要生成保留论文原始摘要。注意周报系统的价值在于持续稳定输出。宁可每周只产出 10 篇高质量推荐也不要因为某个环节不稳定而断更。2. 环境准备与整体架构设计2.1 技术选型Python arXiv API 大模型摘要 Markdown 分发AI 论文周报平台的技术栈不需要特别复杂但每个模块要考虑可替换性。下面是一套适合中小团队快速上手的选型模块推荐技术选型理由开发语言Python 3.10数据处理生态成熟arXiv API 和 LLM SDK 都有现成客户端论文数据源arXiv API官方接口免费支持按分类、时间、关键词查询数据存储SQLite JSON 文件周报数据量不大轻量级存储便于备份和迁移摘要生成OpenAI 兼容接口 / 本地部署模型松耦合设计便于切换供应商或自建模型周报格式Markdown HTMLMarkdown 适合版本管理HTML 适合网页发布定时调度cron / GitHub Actions服务器环境用 cron代码托管环境用 Actions邮件分发SMTP / Resend / SendGrid按订阅量选择早期用 SMTP 足够这套选型的核心思路是把容易变化的服务大模型供应商、邮件平台都放在适配层后面核心流程不受具体厂商绑定。2.2 项目目录结构与关键模块划分建议按功能模块组织代码而不是把所有逻辑放在一个文件里。下面是一个可直接扩展的目录结构ai_weekly/ ├── config/ │ ├── settings.py │ └── paper_rules.yaml ├── collector/ │ ├── arxiv_client.py │ └── storage.py ├── filter/ │ ├── scorer.py │ └── rules.py ├── summarizer/ │ ├── llm_client.py │ └── cache.py ├── renderer/ │ ├── template.md.j2 │ └── markdown_renderer.py ├── publisher/ │ ├── email_sender.py │ └── site_publisher.py ├── scheduler/ │ └── run_weekly.py ├── logs/ └── data/ └── papers.dbconfig 目录放全局配置和筛选规则collector 负责与 arXiv 交互filter 负责筛选和评分summarizer 负责大模型摘要renderer 负责生成 Markdownpublisher 负责分发。这样划分后每个模块都可以单独测试。2.3 环境依赖安装创建虚拟环境并安装依赖python -m venv .venv source .venv/bin/activate pip install arxiv pyyaml jinja2 requests openai pandas如果使用本地模型或自建网关openai SDK 同样可以接入兼容接口。需要邮件发送时再安装对应的邮件客户端库Python 标准库的 smtplib 也够用。安装完成后先把配置文件写清楚。# config/settings.py DATA_DIR data DB_PATH data/papers.db LOG_DIR logs ARXIV_QUERY { categories: [cs.CL, cs.CV, cs.LG, cs.AI], max_results: 500, } LLM_CONFIG { api_base: https://api.example.com/v1, api_key: replace-with-your-key, model: weekly-summary-model, } EMAIL_CONFIG { smtp_host: smtp.example.com, smtp_port: 465, sender: weeklyexample.com, receiver_list: [teamexample.com], }这里用 YAML 管理业务规则用 Python 模块管理系统配置避免把关键词和模型参数混在一起。3. 实现论文采集模块调用 arXiv API 获取每周论文数据3.1 arXiv API 基础用法arXiv 提供了基于 Atom XML 的 API可以通过 HTTP 查询论文数据。Python 有现成的 arxiv 库封装了查询和解析逻辑推荐直接使用。基础查询示例如下import arxiv client arxiv.Client() search arxiv.Search( querycat:cs.AI AND submittedDate:[20250101 TO 20250107], max_results100, sort_byarxiv.SortCriterion.SubmittedDate, ) for result in client.results(search): print(result.entry_id) print(result.title) print(result.summary[:200])这个例子展示了三个关键点query 使用 arXiv 的搜索语法支持 cat、submittedDate、all 等字段。max_results 控制单次返回数量避免一次拉取过多数据。sort_by 按提交时间排序方便构造时间窗口。3.2 按时间窗口抓取论文并解析返回结果周报通常以自然周为单位。计算上周一到上周日的时间范围然后拼接到查询语句中from datetime import datetime, timedelta def get_last_week_range(): today datetime.now() last_monday (today - timedelta(daystoday.weekday() 7)).date() last_sunday (last_monday timedelta(days6)).date() return last_monday, last_sunday def build_query(categories, start_date, end_date): cat_part OR .join(fcat:{c} for c in categories) date_part fsubmittedDate:[{start_date.strftime(%Y%m%d)} TO {end_date.strftime(%Y%m%d)}] return f({cat_part}) AND {date_part}注意 arXiv 搜索语法中的日期格式是 YYYYMMDD不能用带横线的日期字符串。抓取时还要处理分页arXiv API 单次请求有数量上限推荐采用游标方式def fetch_papers(query, max_results500): papers [] search arxiv.Search( queryquery, max_resultsmax_results, sort_byarxiv.SortCriterion.SubmittedDate, ) client arxiv.Client( page_size100, delay_seconds3, num_retries3, ) for result in client.results(search): papers.append({ paper_id: result.entry_id, title: result.title, summary: result.summary, authors: [a.name for a in result.authors], published: result.published.isoformat(), categories: result.categories, primary_category: result.primary_category, pdf_url: result.pdf_url, }) return papers设置 page_size 为 100delay_seconds 为 3是为了降低对 arXiv 服务的压力。如果是生产环境建议在代码里记录每次请求的耗时和返回数量方便后期排查。3.3 增量采集与去重策略论文采集不能每周全量重来。因为论文一旦提交后可能会有更新版本同一个 paper_id 会对应多次变化。正确的做法是把 paper_id 作为唯一标识。每次采集时记录 published 日期和 updated 日期。如果论文已存在则更新其元数据而不是重复插入。SQLite 表结构可以这样设计CREATE TABLE IF NOT EXISTS papers ( paper_id TEXT PRIMARY KEY, title TEXT NOT NULL, summary TEXT, authors TEXT, published TEXT, updated TEXT, categories TEXT, primary_category TEXT, pdf_url TEXT, fetched_at TEXT DEFAULT CURRENT_TIMESTAMP );使用INSERT OR REPLACE可以简化增量逻辑def upsert_papers(conn, papers): for paper in papers: conn.execute( INSERT OR REPLACE INTO papers (paper_id, title, summary, authors, published, updated, categories, primary_category, pdf_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) , ( paper[paper_id], paper[title], paper[summary], ,.join(paper[authors]), paper[published], paper.get(updated, paper[published]), ,.join(paper[categories]), paper[primary_category], paper[pdf_url], ), ) conn.commit()这种设计的优点是幂等。同一个 paper_id 重复执行多次不会产生重复数据。3.4 采集结果落库用 SQLite 保存论文元数据写入数据库之后还应该保留一版原始 JSON 快照方便排查问题。可以在每次采集结束后生成一个带时间戳的 JSON 文件import json from datetime import datetime def save_snapshot(papers, data_dirdata): filename datetime.now().strftime(arxiv_%Y%m%d_%H%M%S.json) path f{data_dir}/{filename} with open(path, w, encodingutf-8) as f: json.dump(papers, f, ensure_asciiFalse, indent2) return path这样即使之后修改了数据库结构也能从快照中恢复数据。注意不要直接在生产数据库上做结构变更。先测试迁移脚本再对正式库执行避免历史数据丢失。4. 实现论文筛选与排序模块4.1 基于关键词与分类的过滤规则原始采集数据里有大量论文并不适合进入周报。例如有些论文只是技术报告有些论文和平台主题完全无关。因此需要一套可配置的过滤规则。推荐使用 YAML 管理规则而不是把关键词写死在代码里。# config/paper_rules.yaml include_keywords: - large language model - retrieval augmented generation - agent - diffusion - reinforcement learning - multi-modal exclude_keywords: - benchmark - dataset - survey required_categories: - cs.CL - cs.LG - cs.AI min_abstract_length: 100筛选逻辑可以这样实现import yaml def load_rules(pathconfig/paper_rules.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def is_match(paper, rules): title_summary (paper[title] paper[summary]).lower() if not any(kw in title_summary for kw in rules[include_keywords]): return False if any(kw in title_summary for kw in rules[exclude_keywords]): return False if paper[primary_category] not in rules[required_categories]: return False if len(paper[summary]) rules[min_abstract_length]: return False return True这套规则在实际运行中需要反复调整。建议把每次筛选的通过率记录下来如果通过率过低说明收录标准太严格通过率过高说明没有起到筛选作用。4.2 基于引用数、热度、新颖度的综合评分关键词过滤只是第一步。为了让周报更有含金量还需要给入选论文排序。这里可以设计一个综合评分函数把多个信号转换为一个可排序的分值。常见的评分信号包括关键词命中数量命中的关键词越多说明与主题越相关。标题长度和完整度标题信息量越高越容易进入读者视野。论文新鲜度距离当前时间越近越符合周报定位。作者历史文章数量如果作者过去有高引用论文可以适当加权。外部引用数据通过 Semantic Scholar API 获取引用数但要注意接口限流。一个简单可用的评分公式def score_paper(paper, rules): score 0.0 text (paper[title] paper[summary]).lower() for kw in rules[include_keywords]: if kw in text: score 1.0 if len(paper[title]) 20: score 0.5 if agent in paper[title].lower(): score 1.5 return score实际项目中可以把规则权重也放进 YAML例如score_weights: keyword_hit: 1.0 title_length_penalty: 0.2 hot_topic_bonus: agent: 1.5 rag: 1.5这样运营人员不需要改代码只需要调整配置就能改变排序策略。4.3 用规则引擎与配置文件分离业务规则如果团队里不止一个人维护周报建议把过滤、排序规则全部外置。这样做的目的是让非技术人员也能参与内容策略调整。规则引擎不一定要引入 Drools 这类重框架用 YAML Python 字典就能应对大多数场景。筛选和排序可以拆成两个阶段硬过滤不符合条件的直接排除。软评分符合条件的计算分数排序取前 N 篇。def select_weekly_papers(papers, rules, top_n20): matched [p for p in papers if is_match(p, rules)] matched.sort(keylambda p: score_paper(p, rules), reverseTrue) return matched[:top_n]这个函数是整个周报系统的核心。建议在日志中记录每次被排除的论文数量以及前 20 篇论文的标题和分数方便后期复盘。5. 实现摘要与周报生成模块5.1 调用大模型 API 生成论文摘要的工程注意点筛选出来的论文通常还有很长的原始摘要。周报读者需要的是“两句话讲清楚这篇论文做了什么”。这时可以调用大模型做二次凝练。调用大模型时要注意几个工程问题API 调用失败要重试但不能无限重试。摘要结果要缓存避免同一篇论文重复生成。输入内容要控制长度防止超出模型上下文窗口。输出结果要校验防止模型返回空内容或明显错误。下面是一个带重试和缓存的摘要客户端示例import time import json import hashlib import requests class LLMClient: def __init__(self, config, cache_filedata/summary_cache.json): self.config config self.cache_file cache_file self.cache self._load_cache() def _load_cache(self): try: with open(self.cache_file, r, encodingutf-8) as f: return json.load(f) except FileNotFoundError: return {} def _save_cache(self): with open(self.cache_file, w, encodingutf-8) as f: json.dump(self.cache, f, ensure_asciiFalse, indent2) def _generate(self, title, abstract): prompt ( 请你用两到三句话概括论文的核心贡献。\n f论文标题{title}\n f论文摘要{abstract[:2000]}\n 输出格式一句话说明问题一句话说明方法一句话说明结论。 ) resp requests.post( f{self.config[api_base]}/chat/completions, headers{Authorization: fBearer {self.config[api_key]}}, json{ model: self.config[model], messages: [{role: user, content: prompt}], temperature: 0.3, }, timeout30, ) resp.raise_for_status() return resp.json()[choices][0][message][content].strip() def summarize(self, title, abstract): key hashlib.md5(f{title}|{abstract[:500]}.encode()).hexdigest() if key in self.cache: return self.cache[key] for attempt in range(3): try: result self._generate(title, abstract) self.cache[key] result self._save_cache() return result except Exception as exc: if attempt 2: return abstract time.sleep(2 ** attempt)注意这里的关键点失败重试采用指数退避最后一次失败时返回原始摘要保证周报仍然可以生成。5.2 摘要结果的缓存与失败重试上面代码中的缓存文件是 JSON 格式字段值是论文标题加摘要前 500 字后的 MD5。这个方案适合单机运行。如果平台扩容到多实例建议把缓存迁移到 RedisMD5 作为 key过期时间设为 30 天。摘要生成有一个必须考虑的问题大模型存在幻觉。模型可能根据论文标题编造出不存在的实验数据。缓解措施包括摘要生成的输入严格限制为论文标题和原始摘要不额外补充外部信息。设置较低 temperature例如 0.3。在提示词中要求模型“只能基于给定内容总结不能添加原摘要没有的信息”。人工抽查采集结果收集模型输出质量反馈。5.3 生成 Markdown 周报模板周报最终要生成人类可读的文档。推荐使用 Jinja2 模板把数据和样式分离。下面是一个简单的模板# AI Weekly 论文周报 发布日期{{ issue_date }} 本周共筛选出 {{ papers|length }} 篇论文。 {% for paper in papers %} ## {{ loop.index }}. {{ paper.title }} - 分类{{ paper.primary_category }} - 作者{{ paper.authors|join(, ) }} - 链接{{ paper.pdf_url }} {{ paper.ai_summary }} {% endfor %}渲染代码from jinja2 import Environment, FileSystemLoader def render_weekly(papers, issue_date, output_path): env Environment(loaderFileSystemLoader(renderer)) template env.get_template(template.md.j2) content template.render(paperspapers, issue_dateissue_date) with open(output_path, w, encodingutf-8) as f: f.write(content) return output_path这里使用模板的好处是后续如果要改成 HTML 格式只需要增加一个 HTML 模板不需要改动采集和筛选模块。5.4 导出 HTML、PDF 与邮件分发Markdown 是中间格式真正发布时需要转成 HTML。可以使用 markdown 库转换import markdown def md_to_html(md_path, html_path): with open(md_path, r, encodingutf-8) as f: md_content f.read() html markdown.markdown( md_content, extensions[tables, fenced_code], ) with open(html_path, w, encodingutf-8) as f: f.write(fhtmlbody{html}/body/html)邮件分发可以用标准库 smtplib 实现。如果只想快速验证可以先把 HTML 输出到本地目录再用邮件客户端预览。生产环境建议使用邮件服务商的 HTTP API因为 SMTP 在部分云厂商里需要额外开放端口。import smtplib from email.mime.text import MIMEText from email.header import Header def send_email(config, subject, html_content): msg MIMEText(html_content, html, utf-8) msg[Subject] Header(subject, utf-8) msg[From] config[sender] msg[To] , .join(config[receiver_list]) with smtplib.SMTP_SSL(config[smtp_host], config[smtp_port]) as server: server.login(config[sender], config[smtp_password]) server.sendmail(config[sender], config[receiver_list], msg.as_string())注意不要把邮箱密码直接写进代码库。应该通过环境变量或密钥管理服务注入防止泄露。6. 自动化调度与部署6.1 用 cron 或 GitHub Actions 定时触发周报平台必须定时运行。两个常见方案是 cron 和 GitHub Actions。如果代码部署在自己的服务器上推荐 cron0 9 * * 1 cd /opt/ai_weekly /opt/ai_weekly/.venv/bin/python scheduler/run_weekly.py logs/cron.log 21上面这行表示每周一早上 9 点执行一次周报生成。注意要使用虚拟环境中的 Python 解释器避免系统 Python 版本不对。如果项目托管在 GitHub 上可以用 GitHub Actions 定时任务name: generate-weekly on: schedule: - cron: 0 9 * * 1 workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.10 - run: pip install -r requirements.txt - run: python scheduler/run_weekly.py env: LLM_API_KEY: ${{ secrets.LLM_API_KEY }} SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}GitHub Actions 适合没有独立服务器的场景但要注意 Actions 的执行环境是临时的数据库和缓存文件不能依赖本地持久化需要把数据放到远程对象存储或外部数据库。6.2 日志、监控与异常告警自动化调度最怕“看似执行成功实际没产出”。建议至少记录以下日志采集模块请求了多少篇、成功多少篇、失败多少篇。筛选模块候选论文数、最终入选论文数。摘要模块成功摘要数、失败重试数、跳过数。渲染模块生成 Markdown 和 HTML 的路径。分发模块邮件发送成功与否。日志格式建议统一为 JSON方便采集到日志平台import logging import json class JsonFormatter(logging.Formatter): def format(self, record): data { time: self.formatTime(record), level: record.levelname, module: record.name, message: record.getMessage(), } return json.dumps(data, ensure_asciiFalse) logger logging.getLogger(weekly) handler logging.FileHandler(logs/weekly.log, encodingutf-8) handler.setFormatter(JsonFormatter()) logger.addHandler(handler)如果周报生成失败应当通过 webhook 发一条告警到企业微信、钉钉或 Slack。最简单的方式是在异常 catch 块里发送 HTTP 请求。6.3 学习环境 vs 生产环境的差异学习环境和生产环境的部署方式差别很大。这里整理一个对照表项目学习环境生产环境数据库SQLite 文件PostgreSQL / MySQL远端存储摘要缓存JSON 文件Redis带过期时间大模型 API测试 Key独立 Key配额和成本监控日志控制台和文件集中日志平台按级别告警调度手动运行cron / Kubernetes CronJob分发本地文件预览邮件 API、CMS 发布密钥本地环境变量密钥管理服务定期轮转这个对照表可以作为部署前的检查清单。如果一开始就按生产环境标准做会拖慢开发速度但进入生产前至少要把数据库、密钥和调度这三块补上。7. 常见问题排查7.1 arXiv API 返回空或请求受限现象采集模块运行后数据库中没有新增论文日志显示返回 0 条结果。可能原因查询语句中的日期范围写错。分类名写错例如把 cs.LG 写成 cs.lg。单次请求频率过高被 arXiv 限流。网络环境无法访问 arXiv API。检查方式# 手动验证查询 import arxiv search arxiv.Search(querycat:cs.AI, max_results5) client arxiv.Client() results list(client.results(search)) print(len(results))如果手动查询有结果说明问题出在日期或分类拼接逻辑。如果手动查询也返回空大概率是网络或接口限制。解决方案调整查询中的日期格式。用 arXiv 官方查询页面确认分类名。增加 delay_seconds 和 num_retries。设置代理或更换部署地域时要注意合规要求。7.2 大模型摘要不稳定或出现幻觉现象同一篇论文多次生成摘要结果差异较大摘要中出现了原论文没有的方法名称或数据。可能原因temperature 设置过高。提示词没有限制模型只能基于给定内容总结。输入摘要过长模型丢失了部分关键信息。解决方案将 temperature 降到 0.2 到 0.4。在提示词中明确“只基于给定内容总结”。对输入摘要长度做截断保留开头的核心内容。增加人工抽查与反馈机制把质量差的生成结果记录到日志。7.3 周报格式错乱现象渲染出的 Markdown 或 HTML 标题层级错误列表符号不统一代码块被转义。可能原因论文标题中包含特殊字符例如#、*、|。模板中使用了不兼容的 Markdown 扩展。转换 HTML 时没有启用表格和代码块扩展。解决方案在模板渲染前对标题做转义例如把#改成#。统一使用 Jinja2 的 escape 过滤器。检查 markdown 扩展参数是否包含 tables 和 fenced_code。from markupsafe import escape def safe_title(title): return escape(title)7.4 自动调度不执行现象cron 已配置但每周没有收到周报。可能原因cron 进程未启动或配置了错误的执行时间。脚本中使用相对路径导致找不到配置文件和数据库。Python 解释器路径错误。脚本运行报错但日志输出到了错误位置。检查方式crontab -l查看当前用户的 cron 任务。然后手动执行一次完整命令观察是否有报错信息。解决方案在脚本开头切换到项目根目录import os os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))使用绝对路径写日志避免 cron 环境下 PATH 不同。先在命令行手动运行确认成功后再写入 cron。8. 最佳实践与扩展方向8.1 可复用清单周报平台上线前检查清单以下清单可以在每次周报发布前逐项检查检查项是否通过备注arXiv 采集数据是否包含上周完整时间窗口必查缺日期会导致内容不完整筛选后的论文数量是否在合理范围建议 10-30 篇过多或过少都要检查规则每篇论文是否都有 AI 摘要或原始摘要兜底必查防止空白内容Markdown 渲染出的标题和链接是否正确必查重点看特殊字符转义邮件或站点是否成功发布必查检查发送日志日志中是否有未处理的异常建议为下一期迭代收集问题大模型 API 消耗是否在预算内建议每周关注 token 用量这个清单适合打印出来放在项目 README 里每期发布时对照执行。8.2 从周报扩展到 AI Agent 与问答系统DAIR.AI 这类平台并不满足于每周发一次内容更长期的方向是把积累的论文库变成可持续使用的知识资产。在现有采集、筛选、摘要的基础上可以继续做三个扩展论文检索问答把已生成的摘要和原文片段向量化搭建一个 RAG 问答系统让读者用自然语言提问例如“这周有哪些关于 Agent 的论文”。AI Agent 自动选题训练或配置一个 Agent让它根据历史周报点击数据自动调整关键词权重和筛选规则。多模态内容分发把 Markdown 周报转成音频摘要或短视频脚本覆盖更多阅读场景。这些扩展都不需要推翻现有架构。只要数据落库、模块解耦做得好新增功能只是接入新的 API 或渲染模板。8.3 对学习者的建议如果是从零开始学习论文周报平台建议按下面顺序练习先只用 Python 脚本抓取 arXiv 数据打印论文标题不做筛选。加入 SQLite 存储跑两周增量采集。用规则筛选出 20 篇论文生成简单 Markdown。接入大模型 API 做摘要。最后加 cron 调度和邮件分发。每一步都先跑通再进入下一步。这样即使某个环节出现问题也能快速定位。论文周报平台的价值不在于技术多复杂而在于它能持续、稳定地帮读者节省时间。真正值得投入精力的地方是筛选规则和摘要质量也就是那些直接决定读者体验的环节。