返回案例库

精选案例 · Agent / 实践案例

每日论文推送

这个案例围绕「每日论文推送」记录了一条真实 AI 实践线索,正文重点集中在「目标范围」「整体架构」,适合先按任务意图阅读再判断复用。

案例速读

README 标题「每日论文推送」下已经出现运行/配置路径、脚本或接口线索、结果证据,正文重点集中在「目标范围」「整体架构」,比纯概念介绍更适合进入精选阅读流。 这篇案例的阅读价值在于,它把真实任务、模型辅助过程和可迁移做法放在同一个上下文里,读者可以从 「每日论文推送」、「目标范围」、「整体架构」、「采集流程」 进入正文。

  • 建议重点看 可参考其中的运行与配置路径、包含可迁移的命令、脚本或接口线索、已有结果或观测证据可用于判断复用价值。结合 Agent / 实践案例 和「任务驱动用户、AI 实践者」这一受众定位,它更适合作为任务检索后的精读材料,而不是只看一句短摘要后快速跳过。
  • 正文目录和原始材料仍然是判断依据;导读只帮助你更快定位阅读重点。
看点
每日论文推送
读者
任务驱动用户、AI 实践者
复用
可参考其中的运行与配置路径
结构
12 个目录入口

原文内容

每日论文推送

这是一个 Docker 化的论文采集、筛选、摘要生成和邮件推送工具。它面向统一身份认证和身份安全研究,每周自动从论文数据源采集候选论文,使用 DeepSeek 判断相关性并生成中文摘要,保存到本地 SQLite;每天从数据库中选择未发送论文,通过邮件推送给配置的收件人。

当前默认调度:

  • 每周日 09:00(UTC+8 / Asia/Shanghai)采集论文。
  • 每天 08:00(UTC+8 / Asia/Shanghai)发送最多 3 篇未发送论文。
  • Docker 容器、服务和镜像名统一为 mind-feed,镜像为 mind-feed:latest

目标范围

论文筛选重点是统一身份认证平台和账号身份安全,包括:

  • SSO、OAuth、OpenID Connect、OIDC、SAML。
  • MFA、RBA、FIDO2、WebAuthn、Passkeys。
  • 账号盗用、凭证填充、账号共享、身份威胁识别与响应。
  • 零信任身份、网络接入认证、持续认证。
  • Bot 检测、Agent/AI Agent 检测,以及与身份安全控制相关的 Agent 研究。

项目会尽量排除明显偏离方向的领域,例如无人机、车联网、V2G、通用 IoT、传感器网络、物理层认证、区块链等。只有当论文明确与企业身份、Web 账号安全或上述主题相关时才会保留。

整体架构

flowchart TD
    Scheduler["APScheduler 定时器"] --> Collect["每周采集任务 collect_papers"]
    Scheduler --> Send["每日邮件任务 send_digest"]

    Collect --> Fetchers["数据源 Fetchers"]
    Fetchers --> Crossref["Crossref"]
    Fetchers --> DBLP["DBLP"]
    Fetchers --> Arxiv["arXiv"]
    Fetchers --> IACR["IACR ePrint"]

    Collect --> Dedupe["标题/链接去重"]
    Dedupe --> AgeFilter["近 10 年过滤"]
    AgeFilter --> DomainFilter["偏题领域过滤"]
    DomainFilter --> Content["原文摘要/正文抓取"]
    Content --> Metadata["OpenAlex 元数据增强"]
    Metadata --> LLM["DeepSeek 相关性判断与中文摘要"]
    LLM --> SQLite["SQLite 数据库 data/papers.sqlite3"]

    Send --> SQLite
    SQLite --> Rank["待发送论文排序"]
    Rank --> Email["SMTP 邮件发送"]

核心模块:

  • app/main.py:命令行入口,支持 schedulercollectsendinit-dbstats
  • app/scheduler.py:APScheduler 定时任务配置。
  • app/services.py:采集和发送主流程。
  • app/fetchers/:不同数据源的候选论文抓取器。
  • app/content.py:论文官网、DOI 页面、PDF、OpenAlex、Semantic Scholar 内容抓取。
  • app/metadata.py:OpenAlex 元数据增强,补全作者、年份、正式发表来源。
  • app/llm.py:DeepSeek 调用、相关性判断、中文摘要生成。
  • app/storage.py:SQLite 初始化、去重、保存、查询、发送状态更新。
  • app/emailer.py:Markdown 风格邮件正文生成和 SMTP 发送。

采集流程

一次完整采集任务由 collect_papers() 执行:

  1. 读取配置,并检查 DeepSeek API key。
  2. 按顺序启用数据源:Crossref -> DBLP -> arXiv -> IACR ePrint
  3. 从各数据源拉取论文候选。
  4. 使用全局 max_candidates_per_run 限制一次采集最多处理的候选数,当前为 120
  5. 对候选执行同批次去重和数据库去重。
  6. 丢弃年份缺失或超过近 10 年范围的候选。
  7. 根据偏题关键词丢弃明显不相关领域论文。
  8. 抓取原始摘要或正文片段;没有原始摘要/正文的候选会被跳过。
  9. 使用 OpenAlex 补全作者、年份、正式发表来源和链接。
  10. 调用 DeepSeek 判断是否属于目标范围,并基于原始摘要/正文生成中文摘要。
  11. 相关论文保存到 SQLite,初始状态为未发送。

注意:max_candidates_per_run 是一次完整采集任务的总候选数,不是单个 venue 或单个数据源的数量。例如当前值为 120,表示 Crossref、DBLP、arXiv、IACR ePrint 加起来最多处理 120 篇候选。

数据源与候选数量

当前配置位于 config/config.yaml

  • arXiv:max_results: 80,检索窗口 lookback_days: 3650
  • IACR ePrint:max_results: 80,检索窗口 lookback_days: 3650
  • Crossref:rows_per_venue: 35,按 venue 和 topic term 检索。
  • DBLP:rows_per_query: 30,按 venue 和 topic term 检索。

支持的主要来源标签包括:

TDSC
TIFS
Journal of Cryptology
CCS
EUROCRYPT
S&P
CRYPTO
USENIX Security
NDSS
arXiv
IACR ePrint

其中 IEEE Symposium on Security and Privacy 的展示标签统一为 S&P

去重策略

当前有两层去重:

  1. 单次采集内去重:collect_papers() 维护 seen_titles,对规范化标题去重。
  2. 入库前去重:PaperStore.exists() 检查 title_hashlink 是否已存在。

标题规范化逻辑会转小写、去掉标点和多余空格,再计算 SHA-256 哈希。数据库字段 title_hash 有唯一约束。

当前去重主要依赖:

规范化标题 hash
论文链接

这能处理 arXiv 和会议版本标题基本一致的重复论文。但如果预印本标题和正式发表标题差异较大,可能仍需要后续扩展 DOI、arXiv ID、OpenAlex ID 或模糊标题匹配来增强去重。

内容抓取与摘要生成

LLM 不会只根据标题生成摘要。进入 DeepSeek 前,系统至少需要拿到以下内容之一:

  • 论文页面中的原始摘要。
  • DOI 或官网页面中的摘要。
  • PDF 中提取出的摘要或正文片段。
  • OpenAlex 或 Semantic Scholar 返回的公开摘要。

如果可以拿到 PDF 正文,系统会提取前若干页文本,并限制输入长度,避免过长请求。当前 DeepSeek 输入长度上限为 max_input_chars: 22000

DeepSeek 返回 JSON,包含:

  • relevant:是否相关。
  • confidence:置信度。
  • tags:主题标签,当前邮件中不展示。
  • reason:判定理由。
  • summary_zh:中文摘要。

只有 relevant = true 且置信度不低于 min_confidence 的论文才会保存。

发送流程

每日发送任务由 send_digest() 执行:

  1. 从 SQLite 读取最多 3 篇 sent_at IS NULL 的论文。
  2. 排序时优先选择非 arXiv/IACR ePrint 来源。
  3. 同一优先组内按 published_dateyear 从新到旧排序。
  4. 生成 Markdown 风格正文和 HTML 正文。
  5. 通过 SMTP SSL 发送邮件。
  6. 发送成功后,将这些论文的 sent_at 写入当前 UTC 时间。
  7. 如果没有可发送论文,会发送一封“无可推送论文”的提示邮件。

正常邮件主题:

每日论文推送 YYYY-MM-DD

无可发送论文时:

每日论文推送 YYYY-MM-DD 无可推送论文

快速开始

复制配置模板:

Copy-Item .\config\config.example.yaml .\config\config.yaml
Copy-Item .\.env.example .\.env

编辑密钥和密码:

notepad .\.env

至少需要配置:

DEEPSEEK_API_KEY=你的DeepSeekKey
SMTP_PASSWORD=你的邮件客户端密码

构建并启动:

docker compose up -d --build

查看容器:

docker ps --filter name=mind-feed

查看日志:

docker logs --tail 80 mind-feed

持续跟踪日志:

docker logs -f mind-feed

停止服务:

docker compose stop

重新启动:

docker compose up -d

重建并启动:

docker compose up -d --build

常用任务命令

初始化数据库或查看统计:

docker compose exec -T mind-feed python -m app init-db
docker compose exec -T mind-feed python -m app stats

手动采集论文:

docker compose exec -T mind-feed python -m app collect

限制本次最多处理 10 篇候选,适合测试:

docker compose exec -T mind-feed python -m app collect --max-candidates 10

手动发送一次邮件:

docker compose exec -T mind-feed python -m app send

进入容器:

docker compose exec mind-feed bash

查看 compose 解析后的配置:

docker compose config

查看镜像:

docker images mind-feed

删除旧容器并重新创建:

docker compose down
docker compose up -d --build

数据库常用查询

SQLite 数据库默认保存在:

data/papers.sqlite3

查看总数和未发送数量:

python -c "import sqlite3; con=sqlite3.connect('data/papers.sqlite3'); print(con.execute('select count(*) from papers').fetchone()[0]); print(con.execute('select count(*) from papers where sent_at is null').fetchone()[0])"

查看最近入库论文:

python -c "import sqlite3; con=sqlite3.connect('data/papers.sqlite3'); con.row_factory=sqlite3.Row; [print(dict(r)) for r in con.execute('select id,title,source,year,sent_at from papers order by id desc limit 10')]"

查看待发送的前 3 篇:

python -c "import sqlite3; con=sqlite3.connect('data/papers.sqlite3'); con.row_factory=sqlite3.Row; [print(r['id'], r['source'], r['year'], r['title']) for r in con.execute(\"select id,source,year,title from papers where sent_at is null order by case when lower(source) in ('arxiv','iacr eprint') then 1 else 0 end, coalesce(published_date, printf('%04d-12-31', year), '') desc, priority desc, created_at asc limit 3\")]"

将所有论文重置为未发送:

python -c "import sqlite3; con=sqlite3.connect('data/papers.sqlite3'); con.execute('update papers set sent_at = null'); con.commit()"

按标题删除某篇论文:

python -c "import sqlite3; title='论文标题'; con=sqlite3.connect('data/papers.sqlite3'); con.execute('delete from papers where title=?', (title,)); con.commit()"

备份数据库:

Copy-Item .\data\papers.sqlite3 .\data\papers.backup.sqlite3

配置说明

核心配置文件是 config/config.yaml

app

  • timezone:调度时区,当前为 UTC+8
  • log_level:日志级别。
  • user_agent:访问外部 API 和网页时使用的 User-Agent。
  • max_candidates_per_run:一次完整采集最多处理多少候选。
  • max_paper_age_years:只保存近多少年的论文。
  • per_candidate_timeout_seconds:单篇候选处理超时。

storage

  • sqlite_path:SQLite 数据库路径,默认 data/papers.sqlite3

network

  • proxy_url:统一代理地址。
  • http_proxy / https_proxy / no_proxy:可引用 .env 中的环境变量。

content_fetch

  • fetch_pdf:是否抓取 PDF。
  • min_abstract_chars:摘要最小长度。
  • min_full_text_chars:正文片段最小长度。
  • max_full_text_chars:正文片段最大长度。
  • max_pdf_bytes:PDF 最大下载大小。
  • pdf_page_limit:PDF 解析页数上限。
  • use_openalex / use_semantic_scholar:是否使用公开元数据服务兜底。

metadata

  • enabled:是否启用 OpenAlex 元数据增强。
  • timeout_seconds:元数据请求超时。

deepseek

  • api_key:建议写成 env:DEEPSEEK_API_KEY,从 .env 注入。
  • base_url:当前为 https://api.deepseek.com,代码会自动补 /chat/completions
  • model:当前为 deepseek-v4-pro
  • temperature:摘要和判断的温度。
  • min_confidence:低于该置信度的论文不保存。
  • max_input_chars:传给 LLM 的摘要/正文最大字符数。

email

  • smtp_host / smtp_port:SMTP 地址和端口。
  • use_ssl / use_tls:邮件加密方式。
  • username / password:SMTP 用户名和客户端密码。
  • from_addr:发件人。
  • to_addrs:收件人列表。
  • subject_prefix:邮件主题前缀,当前为 每日论文推送

schedule

  • weekly_collect:每周采集时间。
  • daily_email:每日发送时间。
  • run_on_startup:容器启动时是否立即采集或发送,默认关闭。

sources

  • arxiv:arXiv 检索配置。
  • iacr_eprint:IACR ePrint RSS 配置。
  • crossref:Crossref 期刊/会议检索配置。
  • dblp:DBLP 期刊/会议检索配置。

filters

  • topics:检索和 LLM 参考的主题词。
  • target_description:LLM 判定目标范围。
  • strong_identity_terms:强身份安全关键词。
  • exclude_domain_keywords:偏题领域排除词。

代理配置

如果部分 API 或官网在当前网络环境下访问不稳定,可以在 .env 中配置代理:

HTTP_PROXY=http://127.0.0.1:7890
HTTPS_PROXY=http://127.0.0.1:7890
NO_PROXY=localhost,127.0.0.1

然后重启容器:

docker compose up -d

当前 Docker 默认 bridge 网络会让容器访问外网时通过宿主机 NAT 出口。配置代理后,Python 的 requests 会优先使用注入到容器的代理环境变量。

安全注意事项

  • 不要把 DeepSeek API key 和邮箱客户端密码写入 README 或代码。
  • 推荐只在 .env 中保存密钥,并通过 docker-compose.yml 注入容器。
  • .env 应避免提交到 Git 仓库。
  • 更换密钥后重启容器即可生效。

更换密钥:

notepad .\.env
docker compose restart mind-feed

常见问题

明天早上会不会收到邮件?

只要容器运行、SMTP 可用、数据库里有未发送论文,就会在 08:00(UTC+8)发送。可以用下面命令确认:

docker ps --filter name=mind-feed
docker logs --tail 80 mind-feed
docker compose exec -T mind-feed python -m app stats

为什么 arXiv 和会议论文可能重复?

系统会按规范化标题和链接去重;如果标题基本一致,会跳过重复记录。如果预印本和正式发表版本标题差异较大,可能需要人工清理或后续增强 DOI/OpenAlex ID 去重。

为什么邮件里 arXiv/IACR 比较少?

发送排序会优先选择非 arXiv/IACR ePrint 来源;arXiv 和 IACR 会保留在数据库中,但发送优先级较低。

为什么有些来源不是配置中的会议/期刊?

arXiv/IACR 候选会通过 OpenAlex 尝试匹配正式发表版本;如果 OpenAlex 找到正式来源,系统会用正式来源替换预印本来源。

为什么没有论文被保存?

常见原因包括:抓不到原始摘要/正文、论文超过 10 年、标题命中偏题领域排除词、DeepSeek 判断不相关、DeepSeek/API 网络错误、数据库已有重复记录。

为什么邮件发送后数据库记录还在?

发送后不会删除论文,只会写入 sent_at。这样可以保留历史记录并避免重复发送。

项目文件结构

.
├── app/
│   ├── main.py
│   ├── scheduler.py
│   ├── services.py
│   ├── storage.py
│   ├── llm.py
│   ├── content.py
│   ├── metadata.py
│   ├── emailer.py
│   └── fetchers/
├── config/
│   ├── config.example.yaml
│   └── config.yaml
├── data/
│   └── papers.sqlite3
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── README.md

返回顶部