开源信息聚合工具Carrot:从RSS订阅到智能推送的自动化实践
1. 项目概述一个开源信息聚合工具的诞生最近在折腾一些个人项目时我一直在寻找一个能帮我高效聚合、筛选和追踪特定领域信息的工具。市面上的商业解决方案要么太贵要么不够灵活无法满足我自定义数据源和复杂过滤规则的需求。直到我发现了xx025/carrot这个项目它像一把瑞士军刀精准地切中了我这个痛点。简单来说carrot是一个轻量级、可高度自定义的开源信息聚合与推送工具。它的核心思想是“订阅-处理-推送”你可以订阅任何能提供结构化或半结构化数据的源比如 RSS、API接口、网页然后通过一系列你定义的规则我们称之为“处理器”来清洗、过滤、转换这些数据最后将你真正关心的内容通过你喜欢的渠道如 Telegram Bot、邮件、Webhook推送给你。它不是为了替代 Feedly 或 Inoreader 这类通用阅读器而是为开发者、运维、市场分析师或任何需要从海量信息流中“淘金”的人提供一个可以自己掌控流水线的工具箱。我花了大概两周时间从部署、配置到编写自己的处理规则完整地跑通了一个监控行业技术动态的流程。整个过程下来我感觉carrot的设计哲学非常清晰配置即代码一切皆可编程。它没有试图做一个大而全的界面而是通过 YAML 配置文件和 Python 插件把最大的灵活性交给了用户。这对于喜欢折腾、追求自动化效率的极客来说吸引力是巨大的。接下来我会详细拆解我是如何用它来构建我的个人信息中枢的包括核心概念、配置详解、实战案例以及踩过的那些坑。2. 核心架构与设计哲学拆解要玩转carrot首先得理解它的几个核心组件和它们之间的协作关系。整个系统的运行逻辑可以概括为“管道Pipeline”模式数据像水流一样从一个环节流向下一个环节。2.1 核心组件订阅源、处理器与执行器订阅源Feeds是数据的入口。carrot原生支持多种类型RSS/Atom这是最常用的来源绝大多数博客、新闻网站、论坛都支持。JSON API对于提供了开放 API 的服务你可以直接订阅 API 返回的 JSON 数据。HTML通过 CSS 选择器对于一些没有提供友好接口的古老网站你可以直接抓取网页并用类似 jQuery 的选择器来提取你需要的数据块。这功能非常强大但也更脆弱因为网站改版会导致选择器失效。内置支持的其他类型比如监控 Git 仓库的提交、特定关键词的社交媒体流等。处理器Processors是carrot的大脑也是其灵魂所在。数据从订阅源获取后会变成一个一个的“条目Item”。处理器则是对这些条目进行加工的逻辑单元。常见的处理器包括过滤Filter根据条目的标题、内容、发布时间、作者等字段进行筛选。例如只保留标题中含有“漏洞”或“Release”关键词的条目。去重Deduplicate基于条目的唯一标识如链接、ID或内容哈希值过滤掉已经处理过的条目避免信息重复轰炸。格式化Format修改条目的呈现格式。比如你希望推送到 Telegram 的消息包含一个固定的标题模板和表情符号。Python 脚本这是终极武器。当内置处理器无法满足你的复杂逻辑时你可以写一段 Python 代码作为处理器。例如调用一个自然语言处理NLP服务来对内容进行情感分析只有正面或中性的新闻才推送或者将条目的摘要翻译成中文。执行器Senders是数据的出口负责将处理好的条目发送出去。carrot支持丰富的推送渠道Telegram Bot这是我个人最推荐的方式即时、互动性强而且 Telegram 的频道/群组/私聊架构非常灵活。电子邮件适合需要归档或正式通知的场景。Webhook将数据以 HTTP POST 请求的形式发送到你自己搭建的服务实现无限的可能性比如自动发到 Slack、钉钉或者存入数据库。命令行输出/日志文件主要用于调试和测试。2.2 配置驱动与“一切皆可编程”carrot的所有行为都通过一个或多个 YAML 配置文件来定义。这种“配置即代码”的方式带来了几个显著好处版本控制你可以用 Git 来管理你的订阅规则和处理器逻辑随时回滚到历史版本。可移植性配置文件可以轻松地在不同环境开发、生产之间迁移和共享。灵活性YAML 的结构化特性使得定义复杂的处理流水线变得清晰直观。而“一切皆可编程”则体现在对 Python 插件的深度支持上。你不仅可以用 Python 写处理器甚至可以自定义订阅源和执行器虽然这需要更深入的开发。这意味着只要你能用代码描述的逻辑carrot几乎都能实现。例如我写过一个处理器它会提取条目中的 CVE 编号然后去另一个安全数据库 API 查询该漏洞的严重等级和影响范围最后将丰富后的信息再塞回条目中供后续推送使用。注意这种强大的灵活性也带来了复杂性。对于简单的 RSS 订阅推送你可能只需要 YAML 配置。但一旦涉及 Python 脚本就需要一定的编程基础并且要仔细考虑脚本的执行效率和错误处理避免一个脚本出错导致整个管道瘫痪。3. 从零开始部署与基础配置实战理论讲得再多不如动手做一遍。下面我就以在 Linux 服务器上使用 Docker 部署carrot并配置一个最简单的“技术博客 RSS - Telegram 推送”为例带你走完全程。3.1 环境准备与部署我选择 Docker 部署因为它能完美解决环境依赖问题最省心。假设你已经在服务器上安装好了 Docker 和 Docker Compose。首先创建一个项目目录比如~/carrot然后进入该目录。mkdir -p ~/carrot cd ~/carrot接下来创建两个核心文件docker-compose.yml和config.yml。1. 创建docker-compose.ymlversion: 3.8 services: carrot: image: xx025/carrot:latest # 使用官方镜像 container_name: carrot restart: unless-stopped # 确保服务意外停止后能自动重启 volumes: - ./config.yml:/app/config.yml:ro # 将本地配置文件挂载到容器内 - ./data:/app/data # 挂载数据卷用于持久化存储如去重数据库 # 如果需要使用 Python 自定义处理器可能需要挂载包含脚本的目录 # - ./my_scripts:/app/processors:ro这个配置非常简洁它定义了一个名为carrot的服务使用官方镜像并挂载了两个卷配置文件config.yml和数据目录data。2. 创建基础的config.yml我们先创建一个最小化的配置来测试服务是否能跑起来。# config.yml feeds: [] tasks: []然后启动服务docker-compose up -d使用docker-compose logs -f carrot查看日志如果没有报错说明carrot服务已经成功运行。不过现在它什么都没做因为配置是空的。3.2 创建你的第一个任务订阅博客并推送现在我们来创建一个真正的任务。假设我想订阅“阮一峰的网络日志”的 RSS并将新文章推送到我的 Telegram。1. 获取 Telegram Bot Token 和 Chat ID在 Telegram 中搜索BotFather按指引创建一个新的 Bot你会得到一个TOKEN形如1234567890:ABCdefGhIJKlmNoPQRsTUVwxyZ。将你的 Bot 添加到一个频道或群组或者直接给它发一条消息。访问这个 URL 来获取你的CHAT_IDhttps://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates。在返回的 JSON 中找到message.chat.id字段的值。2. 编写完整的config.yml# config.yml feeds: # 定义一个名为 ruan-yifeng 的 RSS 订阅源 ruan-yifeng: type: rss url: http://www.ruanyifeng.com/blog/atom.xml # 可以设置请求头有些网站可能需要 User-Agent # headers: # User-Agent: Mozilla/5.0 ... tasks: # 定义一个名为 daily-tech-news 的任务 daily-tech-news: # 任务执行间隔这里设置为每30分钟执行一次 interval: 30m # 该任务使用的订阅源列表 feeds: - ruan-yifeng # 处理器流水线按顺序执行 processors: # 1. 去重处理器基于条目的链接进行去重避免重复推送 - name: deduplicate # 使用文件存储去重状态会保存在挂载的 ./data 目录下 type: deduplicate # 基于条目的哪个字段判断唯一性这里用链接最可靠 key: {{ item.link }} # 状态存储后端这里用简单的文件存储 storage: type: file path: /app/data/deduplicate.db # 2. 过滤处理器只推送标题中包含特定关键词的文章可选 # - name: filter-keywords # type: filter # conditions: # # 条件标题(title)包含“周刊”或“科技” # - field: title # op: contains # value: [周刊, 科技] # # op 也可以是 matches (正则匹配), eq (等于), gt (大于) 等 # 3. 格式化处理器为推送消息设计一个好看的格式 - name: format-for-telegram type: format template: | 新文章推送 *标题*: {{ item.title }} *链接*: {{ item.link }} *摘要*: {{ item.summary | truncate(100) }}... *发布时间*: {{ item.published }} # 执行器定义将处理后的条目发送到哪里 senders: # 使用 Telegram Bot 发送 - name: my-telegram-bot type: telegram token: YOUR_TELEGRAM_BOT_TOKEN # 替换为你的真实 Token chat_id: YOUR_CHAT_ID # 替换为你的真实 Chat ID # 可以设置消息的解析模式如 Markdown 或 HTML parse_mode: Markdown3. 更新配置并重启服务修改完config.yml后需要重启carrot容器以加载新配置docker-compose restart carrot再次查看日志你应该能看到类似这样的信息INFO - Fetching feed: ruan-yifeng INFO - Feed ruan-yifeng fetched, got 10 items. INFO - Task daily-tech-news processed 2 new items. INFO - Sent 2 items via sender my-telegram-bot.此时打开你的 Telegram应该就能收到格式化的新文章推送了如果没有收到请仔细检查日志中的错误信息常见问题包括网络问题无法获取 RSS、Token 或 Chat ID 错误等。4. 高级玩法与自定义处理器开发基础流程跑通后我们可以玩点更花的。carrot的真正威力在于自定义处理器。下面我分享两个我实际在用的高级案例。4.1 案例一智能关键词过滤与摘要生成单纯的关键词包含过滤有时不够精准。我希望能更智能地筛选关于“机器学习模型部署”的文章并且为长文章自动生成一个简短的摘要。这需要用到Python 处理器。首先在项目目录下创建一个processors文件夹并在里面新建一个 Python 文件例如smart_processor.py。# processors/smart_processor.py import re import logging from some_summarization_lib import summarize_text # 假设的摘要库如 sumy, transformers # 配置中会传入的参数 def init(params): # 可以在这里初始化模型、加载词典等耗时操作 # params 来自 YAML 配置中的 args keyword_list params.get(keywords, []) return {keywords: keyword_list} # 核心处理函数每个条目都会调用它 def process(item, ctx): item: 字典包含 title, link, summary, content 等字段 ctx: 上下文包含配置信息等 title item.get(title, ) content item.get(content, ) or item.get(summary, ) # 1. 更智能的关键词匹配使用正则表达式避免子串误匹配 keywords ctx[keywords] pattern r\b( |.join(map(re.escape, keywords)) r)\b title_match re.search(pattern, title, re.IGNORECASE) content_match re.search(pattern, content, re.IGNORECASE) # 如果标题和内容都不包含目标关键词则过滤掉此条目 if not (title_match or content_match): logging.info(fItem {title[:50]}... filtered out by keyword rule.) return None # 返回 None 表示丢弃该条目 # 2. 自动生成摘要如果内容过长 if len(content) 1000: # 假设超过1000字则生成摘要 try: # 调用摘要生成函数这里需要你实际安装并导入相关库 generated_summary summarize_text(content, length3) # 生成3句摘要 # 将新摘要存入 item 的一个新字段供后续格式化使用 item[smart_summary] generated_summary except Exception as e: logging.error(fSummarization failed for {title}: {e}) item[smart_summary] content[:300] ... # 失败则截取前300字 # 必须返回修改后的 item或者新的 item 字典 return item然后在config.yml中引用这个自定义处理器tasks: my-smart-task: interval: 1h feeds: [some-tech-feed] processors: - name: deduplicate type: deduplicate key: {{ item.link }} storage: type: file path: /app/data/smart.db # 使用自定义 Python 处理器 - name: smart-filter-and-summary type: python path: /app/processors/smart_processor.py # 容器内的路径 # 传递给处理器的参数 args: keywords: [模型部署, ONNX, TensorRT, 推理优化] - name: format-with-smart-summary type: format template: | *智能筛选推送* **{{ item.title }}** {{ item.smart_summary | default(item.summary) }} 原文: {{ item.link }} senders: [...]实操心得自定义 Python 处理器时务必做好异常处理try-except和日志记录logging。一个未处理的异常可能会导致整个任务中断。另外在init函数中加载重型模型如深度学习模型是明智的这样可以避免每次处理条目时都重复加载但要注意这会增加内存消耗。4.2 案例二多源聚合与关联分析我有时需要监控某个开源项目的动态信息可能散落在 GitHub Releases、官方博客、社区论坛等多个地方。carrot可以轻松聚合多源。feeds: project-github-releases: type: json # GitHub API 返回的是 JSON url: https://api.github.com/repos/username/projectname/releases headers: User-Agent: Carrot-Aggregator/1.0 # GitHub API 要求 User-Agent # 使用 jmespath 从复杂的 JSON 中提取我们需要的数据将其转换成 carrot 能理解的条目列表 items_path: $ # 根节点就是数组 item_fields: # 定义如何将 JSON 字段映射到 item 的标准字段 title: {{ name }} link: {{ html_url }} published: {{ published_at }} summary: {{ body }} # 可以添加自定义字段 tag_name: {{ tag_name }} project-official-blog: type: rss url: https://project.org/blog/feed.xml tasks: project-monitor: interval: 20m feeds: - project-github-releases - project-official-blog processors: - name: deduplicate type: deduplicate key: {{ item.link }} storage: type: file path: /app/data/project.db # 一个简单的关联处理器如果博客文章标题里提到了版本号而 GitHub Releases 里也有同版本可以尝试关联这里用 Python 处理器示意逻辑 - name: correlate-version type: python path: /app/processors/correlate.py args: version_pattern: rv?\d\.\d\.\d # 匹配版本号的正则 - name: format-combined type: format template: | *项目动态聚合* 【{{ item.feed_name | default(‘未知来源’) }}】 *{{ item.title }}* {% if item.tag_name %}版本: {{ item.tag_name }}{% endif %} {{ item.summary | truncate(150) }} 链接: {{ item.link }} senders: [...]这个配置展示了如何将不同结构的数据源JSON API 和 RSS统一到同一个处理流水线中并通过自定义处理器实现简单的数据关联让推送信息更加丰富和有上下文。5. 运维、调试与常见问题排坑指南即使配置再仔细在实际运行中也会遇到各种问题。下面是我在运维carrot过程中积累的一些经验和常见问题的解决方法。5.1 日志查看与调试技巧carrot的日志输出非常详细是排查问题的第一手资料。查看所有日志docker-compose logs carrot实时跟踪日志docker-compose logs -f carrot查看特定任务的详细日志可以在任务配置中增加debug: true选项但要注意这会产生大量日志。手动触发任务执行用于调试carrot本身不提供直接的 CLI 触发命令但你可以通过缩短任务间隔如设为1m来快速测试或者写一个简单的脚本调用其内部 API如果启用的话。一个更实用的调试方法是在处理器流水线的最开始添加一个“调试”处理器将原始的条目数据打印到日志或保存到文件。processors: - name: debug-raw-data type: python path: /app/processors/debug_processor.pydebug_processor.py内容import json import logging def process(item, ctx): logging.debug(fRaw item received: {json.dumps(item, ensure_asciiFalse, indent2)}) return item # 原样返回不影响后续处理5.2 常见问题与解决方案实录下面这个表格总结了我遇到过的典型问题及解决办法问题现象可能原因排查步骤与解决方案收不到任何推送日志显示Failed to fetch feed1. 网络问题容器无法访问外网2. RSS 源地址错误或失效3. 目标网站反爬需要 User-Agent1. 进入容器 (docker exec -it carrot sh) 用curl或wget测试能否访问该 URL。2. 在浏览器中验证 RSS 链接是否有效。3. 在feed配置中添加headers设置合理的User-Agent。推送成功但 Telegram 显示“无效的 Markdown”或格式错乱Telegram 的 Markdown 解析有特定语法要求你的模板可能包含非法字符或不匹配的符号。1. 简化模板先只用纯文本测试。2. 确保特殊字符如*,_,,[等都正确配对转义。3. 将parse_mode改为HTML试试或者直接用纯文本。去重失效重复收到同一条目1. 去重key设置不合理如用了会变化的内容。2. 去重存储文件损坏或权限问题。3. 任务被多次并行触发。1.确保key是唯一且稳定的如item.link或item.guid。避免使用title或summary。2. 检查挂载的data目录权限确保容器有读写权限。可以尝试删除旧的.db文件重启。3. 检查interval设置是否过短确保一个任务执行完再开始下一个。自定义 Python 处理器导入模块失败1. 容器内缺少依赖包。2. Python 脚本路径错误或语法错误。1.最佳实践基于官方镜像构建自己的 Docker 镜像在 Dockerfile 中RUN pip install你需要的包。2. 在本地测试好 Python 脚本确保语法和导入无误。查看容器日志会有详细的 Python 错误堆栈。任务执行一次后不再执行1.interval设置错误如30被解析为30秒实际上需要30m。2. 某个处理器发生未处理的异常导致任务崩溃。1. 确认interval格式30m(30分钟),2h(2小时),1d(1天)。2.仔细查看日志中的 ERROR 信息修复处理器中的 Bug。在所有自定义处理器中加强异常捕获。内存或CPU占用过高1. 订阅源过多或更新频率太高。2. 自定义 Python 处理器存在内存泄漏或执行效率低下。3. 去重数据库文件过大。1. 合理设置interval非实时性需求可以拉长间隔。2. 优化 Python 代码避免在process函数中加载大模型或进行重型计算。考虑使用缓存。3. 定期清理或归档旧的去重数据库文件。5.3 性能优化与最佳实践合理规划任务间隔不要所有任务都设为5m。根据信息源的更新频率来定技术博客可能6h或12h一次就够了新闻类可以30m。这能显著减少不必要的网络请求和负载。使用高效的去重后端对于订阅源多、条目量大的场景文件存储的去重方式可能成为瓶颈。可以考虑使用 Redis 作为去重存储后端如果carrot支持或通过自定义实现速度会快很多。处理器流水线优化把最可能过滤掉大量条目的处理器如关键词过滤放在去重处理器之后、耗时处理器如调用外部 API 摘要之前。这样可以避免对不感兴趣的条目做无用功。配置版本化管理一定要用 Git 管理你的config.yml和自定义处理器目录。每次修改前先拉分支方便回滚和协作。监控与告警carrot本身是工具你需要监控它的运行状态。可以将carrot的日志接入你的集中日志系统如 ELK或者写一个简单的健康检查脚本定期检查carrot容器是否在运行、最近是否有成功推送。如果长时间没有日志可能意味着服务挂了。经过一段时间的深度使用carrot已经成为了我个人信息流中不可或缺的自动化枢纽。它把我从频繁刷网站、刷群消息的碎片化信息焦虑中解放了出来让我能更专注地处理经过筛选和加工的、对我真正有价值的信息。它的门槛确实存在主要在于 YAML 配置和 Python 脚本的编写但这份投入带来的回报是巨大的——一个完全为你量身定制、随你心意变化的信息获取管道。如果你也受困于信息过载同时又有点技术动手能力强烈建议你试试carrot从订阅一两个博客开始逐步搭建你的数字世界“胡萝卜农庄”。