6 Commits

Author SHA1 Message Date
glidea
094600ee26 update README
Removed sponsorship section and updated images with links.
2025-11-22 14:59:24 +08:00
glidea
c03e4c8359 Merge pull request #31 from Twelveeee/main
feat:add RSSHub RSSHubAccessKey
2025-11-07 15:59:20 +08:00
Twelveeee
584f94e1ef feat:add RSSHub RSSHubAccessKey 2025-11-07 14:27:29 +08:00
Twelveeee
6c4223de92 feat:add RSSHub RSSHubAccessKey 2025-11-06 15:58:11 +08:00
Twelveeee
f67db8ea86 feat:add RSSHub RSSHubAccessKey 2025-11-06 11:06:26 +08:00
Twelveeee
bc54cc852e feat:add RSSHub RSSHubAccessKey 2025-11-05 14:55:01 +00:00
8 changed files with 82 additions and 169 deletions

View File

@@ -1,5 +1,3 @@
[Nano Banana🍌 公益站](https://image-generation.zenfeed.xyz/):集成 Twitter 热门 Prompt轻松玩转各种姿势
---
[English](README-en.md)
---
@@ -55,14 +53,6 @@ zenfeed 是你的 <strong>AI 信息中枢</strong>。它既是<strong>智能 RSS
---
**赞助项目可以领取 Gemini Key**
<a href="https://afdian.com/a/glidea"><img src="docs/images/sponsor.png" width="500"></a>
<br/>
<a href="https://afdian.com/a/glidea">赞助项目,支持发展</a>
---
## 💡 前言
RSS简易信息聚合诞生于 Web 1.0 时代,旨在解决信息分散的问题,让用户能在一个地方聚合、追踪多个网站的更新,无需频繁访问。它将网站更新以摘要形式推送给订阅者,便于快速获取信息。
@@ -215,18 +205,26 @@ $env:API_KEY = "sk-..."; docker-compose -p zenfeed up -d
<table>
<tr>
<td align="center">
<img src="docs/images/wechat.png" alt="Wechat QR Code" width="300">
<img src="https://github.com/glidea/zenfeed/blob/main/docs/images/wechat.png?raw=true" alt="Wechat QR Code" width="300">
<br>
<strong>AI 学习交流社群</strong>
</td>
<td align="center">
<img src="docs/images/sponsor.png" width="500">
<img src="https://github.com/glidea/banana-prompt-quicker/blob/main/images/glidea.png?raw=true" width="250">
<br>
<strong><a href="https://afdian.com/a/glidea">请杯奶茶 🧋</a></strong>
<strong><a href="https://glidea.zenfeed.xyz/">我的其它项目</a></strong>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<img src="https://github.com/glidea/banana-prompt-quicker/blob/main/images/readnote.png?raw=true" width="400">
<br>
<strong><a href="https://www.xiaohongshu.com/user/profile/5f7dc54d0000000001004afb">📕 小红书账号 - 持续分享 AI 原创</a></strong>
</td>
</tr>
</table>
都看到这里了,顺手点个 **Star ⭐️** 呗,这是我持续维护的最大动力!
有好玩的 AI 工作也请联系我!

View File

@@ -1,152 +0,0 @@
# 竞品调研Zenfeed
更新时间2025-10基于公开已知信息与产品体验的综合判断可能随时间变化
目的:梳理 Zenfeed 所在赛道的主要竞品与替代方案,按产品类型与能力维度对比,识别差异化与机会点,指导路线和定位。
一、产品类型与代表选手
1) AI 加持的 RSS/SaaS 阅读器
- Feedly含 Feedly AI/Leo
- Readwise Reader
- Inoreader规则、搜索较强近年也引入部分 AI 功能)
- NewsBlur基于训练的过滤偏好
2) 自托管 RSS 聚合器(开源)
- FreshRSS
- Tiny Tiny RSSTT-RSS
- Miniflux
- + 插件生态(例如部分 OpenAI/总结插件)
3) 信息监控/自动化编排
- Huginn自托管 IFTTT/IF-This-Then-That
- n8n低代码自动化编排SaaS/自托管)
- changedetection.io网页变更监控侧重 DOM 变化)
- Distill.io / VisualpingSaaS 页面变更监控)
4) 情报监测/关键词追踪(论坛/社媒/开发者社区)
- Syften跨社区关键词监控
- 万物追踪(中文市场关注度较高)
- TeamWiseFlow/WiseFlow“AI 首席情报官”思路)
5) 爬取/采集与任务平台(更多是基础设施/补充)
- Scrapy、Apify、Crawlee、Playwright/Chromium 爬取方案
- RSSHub补齐数据源的通用 RSS 生成器,侧重“供给侧”,非直接竞品)
二、核心对比维度
- 数据源覆盖与可扩展性:
- 是否仅限 RSS是否支持 RSSHub/网页爬取、API、邮件等是否易于接入新源。
- 信息筛选与查询:
- 规则引擎(关键词/布尔条件/正则/评分)、保存的搜索、优先级/标签。
- AI 能力:
- 摘要、打标、主题聚类、意图识别、相似度搜索/去重、长文重写、对话式问答。
- 工作流/编排:
- 是否可声明式编排pipeline、分步处理、多路分发、可插拔 Prompt/算子。
- 通知与路由:
- 邮件/Slack/Telegram/企业微信/飞书/Webhook/RSS 二次生成,是否支持相似分组/摘要汇总。
- 存储与检索:
- 结构化索引、倒排索引、向量库、对象存储(音频/附件)、缓存与过期策略。
- 部署与成本:
- SaaS 订阅 vs 自托管API Key 成本、并发限速、隐私数据掌控。
- 协作与团队能力:
- Board/共享、标注/高亮、评论、权限与多用户。
- 开放接口:
- JSON-RPC/HTTP API、MCP Server、二次开发友好度。
- 体验与生态:
- Web/移动端、浏览器插件、自动化生态与第三方集成。
三、典型竞品速览
1) Feedly + Feedly AILeo
- 定位:面向专业/团队的信息监测与阅读AI“Leo”用于优先级、主题、去重与摘要。
- 优势:成熟度高,协作/Board 丰富,企业方案完善,数据源与集成较多。
- 局限:闭源、订阅价格较高;自定义度受限;自托管不可行;对中文开发者生态与私有化场景不友好。
2) Readwise Reader
- 定位:稍偏“稍后读/知识管理”;支持网页/邮件/订阅AI 摘要和高亮回顾。
- 优势:体验优秀、阅读流程顺滑、与 Readwise 知识库联动。
- 局限:闭源、偏个人阅读;自动化编排/告警监控能力有限;难以做灵活的规则路由。
3) Inoreader / NewsBlur
- 定位:传统 RSS 强者Inoreader 的规则、过滤、搜索很成熟NewsBlur 有“训练”机制。
- 优势:稳定可靠、生态与客户端广、学习成本低。
- 局限AI 能力与向量检索通常非核心;深度个性化编排和二次开发空间有限。
4) FreshRSS / TT-RSS / Miniflux自托管
- 定位:轻量的开源 RSS 聚合;可通过插件接 AI 摘要。
- 优势:自托管、可控、成本低;社区活跃。
- 局限:缺少原生的“语义处理流水线”和“查询/路由/通知”的深度耦合要实现“AI + 监控 + 通知”的端到端闭环需要较多自定义。
5) Huginn / n8n自动化
- 定位:通用自动化与数据流编排。
- 优势:灵活、组件多、可拉通任意 API 与存储。
- 局限非“RSS/情报”领域专用;需要自己拼装爬取、解析、去重、总结、相似分组、通知等环节,心智与运维成本较高。
6) changedetection.io / Distill.io / Visualping页面变更监控
- 定位:检测网页 DOM/文本变化,并告警。
- 优势:上手快、对不提供 RSS 的页面友好。
- 局限语义层理解弱AI 总结/聚类/多源关联较少;难以形成“知识库 + 查询”。
7) Syften / 万物追踪 / WiseFlow 等(情报监控)
- 定位:跨社区关键词/实体/主题跟踪,生成简报。
- 优势:场景契合“情报/监控/简报”SaaS 即开即用。
- 局限:闭源或私有;可编排性有限;数据可控性与隐私诉求难满足;对中文/特定垂直源支持取决于厂商。
8) RSSHub重要补充而非直接竞品
- 定位:把各种站点“喂成 RSS”。
- 价值:极大丰富数据源;与 Zenfeed 组合可形成“源→处理→查询→路由”的闭环。
四、Zenfeed 的差异化与定位
- 面向“AI + RSS/情报/监控”的自托管开源引擎:
- 声明式 YAML 配置 + 热更新;组件化框架,订阅 Watcher 实时生效。
- 处理管道以“标签集”为核心抽象,支持 LLM 驱动的摘要、打标、评分、过滤、聚类、脚本生成、抓取增强等。
- 存储侧内置主索引、倒排索引与向量索引NutsDB 缓存嵌入;可选 MinIO/S3 存储富媒体。
- 调度器支持基于语义/规则的查询生成“事件”;通知器做相似合并与 LLM 汇总,路由到 Email/Webhook 等。
- 对外提供 JSON-RPC/HTTP、MCP Server、导出 RSS易于二次集成。
- 与 SaaS 阅读器相比:更可定制、数据私有、可与私有模型/网络融合;缺点是需要自行部署与维护。
- 与通用自动化平台相比Zenfeed 在“RSS/情报管道”有更强的领域内建语义模型与数据结构;通用性略弱但落地更快。
- 与纯 RSS 聚合器相比:原生引入 LLM 语义处理、向量搜索、调度/路由/通知,强调“读写闭环”和“监控/简报”。
五、潜在短板与改进机会(相对竞品)
- 认证与多用户/权限:当前暴露到公网存在安全顾虑;完善 Auth、分权与审计将有助企业团队采用。
- GUI 配置与可视化编排YAML 心智强,建议提供可视化 Pipeline/路由设计器、规则调试器、Prompt 管理。
- 移动端/客户端生态:继续完善官方 Web/移动端体验;浏览器插件“保存到 Zenfeed/一键追踪”。
- 第三方通知与协作:拓展 Slack/Telegram/飞书/企业微信等官方集成;通知中的交互(反馈、标注)回写管道。
- 数据源扩展内置“RSSHub 模板化连接”、网页自动提取Readability/Boilerplate、邮件源、GitHub/Reddit 等常见垂直源。
- 模型与成本:
- 多模型策略(廉价 Embedding + 中等 Summarize + 高质少量 Deep Reasoning
- 限速/重试/缓存/去重更精细以优化成本;
- 插件式模型提供商抽象,方便企业接私有 LLM。
- 团队协作Board/共享视图、事件指派、评论、标签规范与知识沉淀。
六、目标用户与场景建议
- 高阶 RSS 用户、研究员/分析师、开发者、投资/BD、开源维护者、增长/品牌观察;
- 典型场景:
- 行业/竞品/技术情报监控与周报;
- 个性化“高质量源 + 语义过滤 + 摘要 + 去重 + 分发”;
- 会议/活动/政策/漏洞/版本发布等事件跟踪;
- 私有化部署的“团队情报中枢”。
七、结论(定位与策略)
- 定位:自托管、可编排的 AI 信息中枢“RSS × 语义处理 × 调度路由 × 通知”)。
- 策略:
1) 做强“声明式管道 + 语义检索 + 调度路由”的一体化体验;
2) 打通 RSSHub 与常见垂直源,降低“连接成本”;
3) 完善认证/多用户与 GUI 编排,提升团队与企业可用性;
4) 提供一组开箱即用的“行业模板/蓝图”(安全情报、开源监控、投研资讯、竞品监测、政策合规等);
5) 控制模型成本,提供可观测性(速率、花费、召回/精确度、摘要质量回馈)。
备注:以上信息仅用于产品规划参考,不构成对第三方产品的评价承诺。实际功能与定价以各产品官方为准。

View File

@@ -59,6 +59,7 @@
| `scrape.past` | `time.Duration` | 抓取 Feed 的回溯时间窗口。例如 `1h` 表示只抓取过去 1 小时的 Feed。 | `24h` | 否 |
| `scrape.interval` | `time.Duration` | 抓取每个源的频率 (全局默认值)。例如 `1h`。 | `1h` | 否 |
| `scrape.rsshub_endpoint` | `string` | RSSHub 的端点。你可以部署自己的 RSSHub 服务器或使用公共实例 (参见 [RSSHub 文档](https://docs.rsshub.app/guide/instances))。例如 `https://rsshub.app`。 | | 是 (如果使用了 `rsshub_route_path`) |
| `scrape.rsshub_access_key` | `string` | RSSHub 的访问密钥。用于访问控制。(详情见 [RSSHub文档访问控制](https://docs.rsshub.app/deploy/config#access-control-configurations)) | | 否 |
| `scrape.sources` | `对象列表` | 用于抓取 Feed 的源列表。详见下方的 **抓取源配置**。 | `[]` | 是 (至少一个) |
### 抓取源配置 (`scrape.sources[]`)

View File

@@ -59,6 +59,7 @@ This section configures parameters related to the Jina AI Reader API, primarily
| `scrape.past` | `time.Duration` | Time window to look back when scraping feeds. E.g., `1h` means only scrape feeds from the past 1 hour. | `24h` | No |
| `scrape.interval` | `time.Duration` | Frequency to scrape each source (global default). E.g., `1h`. | `1h` | No |
| `scrape.rsshub_endpoint` | `string` | Endpoint for RSSHub. You can deploy your own RSSHub server or use a public instance (see [RSSHub Documentation](https://docs.rsshub.app/guide/instances)). E.g., `https://rsshub.app`. | | Yes (if `rsshub_route_path` is used) |
| `scrape.rsshub_access_key` | `string` | The access key for RSSHub. Used for access control. (see [RSSHub config](https://docs.rsshub.app/deploy/config#access-control-configurations))| | No |
| `scrape.sources` | `list of objects` | List of sources to scrape feeds from. See **Scrape Source Configuration** below. | `[]` | Yes (at least one) |
### Scrape Source Configuration (`scrape.sources[]`)

View File

@@ -95,10 +95,11 @@ type LLM struct {
}
type Scrape struct {
Past timeutil.Duration `yaml:"past,omitempty" json:"past,omitempty" desc:"The lookback time window for scraping feeds. e.g. 1h means only scrape feeds in the past 1 hour. Default: 3d"`
Interval timeutil.Duration `yaml:"interval,omitempty" json:"interval,omitempty" desc:"How often to scrape each source, it is a global interval. e.g. 1h. Default: 1h"`
RSSHubEndpoint string `yaml:"rsshub_endpoint,omitempty" json:"rsshub_endpoint,omitempty" desc:"The endpoint of the RSSHub. You can deploy your own RSSHub server or use the public one (https://docs.rsshub.app/guide/instances). e.g. https://rsshub.app. It is required when sources[].rss.rsshub_route_path is set."`
Sources []ScrapeSource `yaml:"sources,omitempty" json:"sources,omitempty" desc:"The sources for scraping feeds."`
Past timeutil.Duration `yaml:"past,omitempty" json:"past,omitempty" desc:"The lookback time window for scraping feeds. e.g. 1h means only scrape feeds in the past 1 hour. Default: 3d"`
Interval timeutil.Duration `yaml:"interval,omitempty" json:"interval,omitempty" desc:"How often to scrape each source, it is a global interval. e.g. 1h. Default: 1h"`
RSSHubEndpoint string `yaml:"rsshub_endpoint,omitempty" json:"rsshub_endpoint,omitempty" desc:"The endpoint of the RSSHub. You can deploy your own RSSHub server or use the public one (https://docs.rsshub.app/guide/instances). e.g. https://rsshub.app. It is required when sources[].rss.rsshub_route_path is set."`
RSSHubAccessKey string `yaml:"rsshub_access_key,omitempty" json:"rsshub_access_key,omitempty" desc:"The access key for RSSHub. Used for access control. (see [RSSHub config](https://docs.rsshub.app/deploy/config#access-control-configurations))"`
Sources []ScrapeSource `yaml:"sources,omitempty" json:"sources,omitempty" desc:"The sources for scraping feeds."`
}
type Storage struct {

View File

@@ -80,6 +80,7 @@ func (c *Config) From(app *config.App) {
URL: app.Scrape.Sources[i].RSS.URL,
RSSHubEndpoint: app.Scrape.RSSHubEndpoint,
RSSHubRoutePath: app.Scrape.Sources[i].RSS.RSSHubRoutePath,
RSSHubAccessKey: app.Scrape.RSSHubAccessKey,
}
}
}

View File

@@ -33,6 +33,7 @@ type ScrapeSourceRSS struct {
URL string
RSSHubEndpoint string
RSSHubRoutePath string
RSSHubAccessKey string
}
func (c *ScrapeSourceRSS) Validate() error {
@@ -46,9 +47,22 @@ func (c *ScrapeSourceRSS) Validate() error {
return errors.New("URL must be a valid HTTP/HTTPS URL")
}
// Append access key as query parameter if provided
c.appendAccessKey()
return nil
}
func (c *ScrapeSourceRSS) appendAccessKey() {
if c.RSSHubEndpoint != "" && c.RSSHubAccessKey != "" && !strings.Contains(c.URL, "key=") {
if strings.Contains(c.URL, "?") {
c.URL += "&key=" + c.RSSHubAccessKey
} else {
c.URL += "?key=" + c.RSSHubAccessKey
}
}
}
// --- Factory code block ---
func newRSSReader(config *ScrapeSourceRSS) (reader, error) {
if err := config.Validate(); err != nil {

View File

@@ -122,6 +122,55 @@ func TestNewRSS(t *testing.T) {
},
},
},
{
Scenario: "Valid Configuration - RSSHub with Access Key",
Given: "a valid configuration with RSSHub details and access key",
When: "creating a new RSS reader",
Then: "should succeed, construct the URL with access key, and return a valid reader",
GivenDetail: givenDetail{
config: &ScrapeSourceRSS{
RSSHubEndpoint: "http://rsshub.app/",
RSSHubRoutePath: "/_/test",
RSSHubAccessKey: "testkey",
},
},
WhenDetail: whenDetail{},
ThenExpected: thenExpected{
wantErr: false,
validateFunc: func(t *testing.T, r reader) {
Expect(r).NotTo(BeNil())
rssReader, ok := r.(*rssReader)
Expect(ok).To(BeTrue())
Expect(rssReader.config.URL).To(Equal("http://rsshub.app/_/test?key=testkey"))
Expect(rssReader.config.RSSHubEndpoint).To(Equal("http://rsshub.app/"))
Expect(rssReader.config.RSSHubRoutePath).To(Equal("/_/test"))
Expect(rssReader.config.RSSHubAccessKey).To(Equal("testkey"))
},
},
},
{
Scenario: "Valid Configuration - URL with Access Key",
Given: "a valid configuration with URL and access key",
When: "creating a new RSS reader",
Then: "should succeed, append access key to URL, and return a valid reader",
GivenDetail: givenDetail{
config: &ScrapeSourceRSS{
URL: "http://example.com/feed",
RSSHubAccessKey: "testkey",
},
},
WhenDetail: whenDetail{},
ThenExpected: thenExpected{
wantErr: false,
validateFunc: func(t *testing.T, r reader) {
Expect(r).NotTo(BeNil())
rssReader, ok := r.(*rssReader)
Expect(ok).To(BeTrue())
Expect(rssReader.config.URL).To(Equal("http://example.com/feed"))
Expect(rssReader.config.RSSHubAccessKey).To(Equal("testkey"))
},
},
},
}
// --- Run tests ---