RSS 订阅源的生成与解析
RSS 是什么
RSS(Really Simple Syndication,简易信息聚合)是一种基于 XML 规范的内容分发格式,用于将网站的文章、新闻、博客更新等内容封装为标准化的订阅源。
网站通过生成 RSS 源对外输出标题、摘要、发布时间、原文链接等元数据,然后客户端解析器读取该 XML 文档后,即可获得站点的更新内容,用户无需访问原始网页,就能获取最新资讯。
效果类似于订阅报纸,只要登记了报社的投递地址,每天就有新报纸自动送到家门口,不出家门便可知晓天下事。
RSS 实现了内容发布与内容阅读的分离,不依赖平台算法推送,兼顾隐私性与信息获取效率,广泛应用于博客、新闻站点、播客等场景,常见格式包含 RSS 2.0 以及功能更完善的 Atom 1.0 规范,二者是并列的两种标准。
在 C# 中使用什么库来处理 RSS
System.ServiceModel.Syndication 是 .NET 生态中的 Syndication(内容聚合)标准库,专门用于生成与解析 RSS 2.0、Atom 1.0 格式的订阅源。该库提供一套面向对象的模型,封装了订阅源、条目、作者、发布时间、摘要、链接等 RSS/Atom 的核心实体,开发者不需要手动拼接、解析原始 XML 字符串。
通过 SyndicationFeed、SyndicationItem 等核心类型,既可以将业务数据导出输出为标准 RSS 或 Atom XML 文档,也能够读取网络或本地的订阅源数据流,反序列化为内存对象,完成 RSS 源的解析读取。并且能够自动处理两种格式之间的格式差异,降低开发 RSS 相关功能的编码复杂度。
SyndicationFeed代表整个订阅源,在 RSS 2.0 中对应<channel>元素,在 Atom 1.0 中对应<feed>元素。SyndicationItem代表订阅源里单条资讯条目,对应每一篇文章、新闻记录。
生成 RSS / Atom 格式的订阅源
使用 System.ServiceModel.Syndication 可以非常方便地在 .NET 中生成 RSS 2.0 和 Atom 1.0。该库的核心优势在于模型与格式分离:只需构建一个 SyndicationFeed 对象,然后通过不同的 Formatter 输出不同格式。
- 模型:无论 RSS 还是 Atom,都通过
SyndicationFeed/SyndicationItem操作 - 格式:
Rss20FeedFormatter和Atom10FeedFormatter负责模型到 XML 的序列化
另外在使用之前,需要先安装该库,因为从 .NET 5 开始就不内置了。
dotnet add package System.ServiceModel.Syndication构建模型
using System.ServiceModel.Syndication;
// 创建订阅源对象
// 对应 RSS 2.0 中的 <channel> 或 Atom 中的 <feed>
var feed = new SyndicationFeed(
title: "订阅源名称", // 订阅源标题
description: "网站简介", // 订阅源描述
feedAlternateLink: new Uri("https://example.com"), // 网站主页链接
id: $"urn:uuid:{Guid.NewGuid()}", // 订阅源的唯一标识符(URN 格式)
lastUpdatedTime: DateTime.Now // 最后更新时间
)
{
Language = "zh-cn", // 订阅源的语言(中文简体)
ImageUrl = new Uri("https://example.com/logo.png") // 订阅源图标/Logo 的 URL
};
// 添加订阅源的作者信息(支持多个作者)
feed.Authors.Add(new SyndicationPerson(
email: "editor@example.com", // 作者邮箱
name: "名称", // 作者名称
uri: "https://example.com" // 作者主页链接
));
// 添加自定义链接,这里添加的是 Atom 规范中的 self link
// self link 指向订阅源文件本身的地址,方便订阅器识别源地址
feed.Links.Add(new SyndicationLink(
uri: new Uri("https://example.com/feed.atom"), // 链接地址
relationshipType: "self", // 关系类型为 self,表示这是订阅源自身
title: "", // 链接标题(此处为空)
mediaType: "application/atom+xml", // MIME 类型,表明是 Atom 格式
length: 0 // 内容长度(未知时设为 0)
));
// 构建订阅条目列表
// 每个 SyndicationItem 对应一篇文章/一条资讯
var items = new List<SyndicationItem>
{
new(
title: "文章名称", // 条目标题
content: new TextSyndicationContent( // 条目正文内容
"<p>正文 HTML 格式的内容...</p>", // HTML 格式的正文
TextSyndicationContentKind.Html // 指定内容类型为 HTML
),
itemAlternateLink: new Uri("https://example.com/posts/1"), // 文章原文链接
id: $"urn:uuid:{Guid.NewGuid()}", // 条目唯一标识符
lastUpdatedTime: DateTime.Now // 条目最后更新时间
)
{
PublishDate = DateTime.Now, // 条目首次发布时间
Summary = new TextSyndicationContent("文章摘要纯文本...") // 条目摘要(纯文本)
}
};
// 将条目列表赋值给订阅源
feed.Items = items;将模型序列化
序列化为 RSS 2.0
// 配置 XML 输出设置
var settings = new XmlWriterSettings
{
Indent = true, // 启用缩进,使输出的 XML 格式美观可读
Encoding = new UTF8Encoding(false), // 使用 UTF-8 编码,false 表示不写入 BOM 头
};
// 创建内存流,用于接收序列化后的 XML 数据
using var stream = new MemoryStream();
// 创建 XmlWriter,将格式化后的 XML 写入内存流
using (var writer = XmlWriter.Create(stream, settings))
{
// 使用 Rss20FeedFormatter 将 SyndicationFeed 模型序列化为 RSS 2.0 格式的 XML
var rssFormatter = new Rss20FeedFormatter(feed);
rssFormatter.WriteTo(writer); // 执行写入操作
}
// 将内存流中的字节数组转换为 UTF-8 字符串,得到最终的 RSS XML 文本
var rssXml = Encoding.UTF8.GetString(stream.ToArray());序列化为 Atom 1.0
只需将 Formatter 替换为 Atom10FeedFormatter,即可输出 Atom 1.0 格式。
var atomFormatter = new Atom10FeedFormatter(feed);
atomFormatter.WriteTo(writer);封装序列化过程中的通用逻辑
在输出不同格式之前,可以把通用的操作封装成一个单独的方法,然后传入想要的格式即可。
/// <summary>
/// 将 SyndicationFeedFormatter 序列化为 XML 字符串。
/// 利用多态特性,传入 Rss20FeedFormatter 或 Atom10FeedFormatter 均可正常工作。
/// </summary>
/// <param name="formatter">
/// 订阅源格式化器,SyndicationFeedFormatter 是 Rss20FeedFormatter
/// 和 Atom10FeedFormatter 的共同基类
/// </param>
/// <returns>序列化后的 XML 字符串</returns>
public string FormatFeed(SyndicationFeedFormatter formatter)
{
// 配置 XML 写入设置
var settings = new XmlWriterSettings
{
Indent = true,
Encoding = new UTF8Encoding(false), // 不带 BOM
};
// 使用内存流作为写入目标
using var stream = new MemoryStream();
// 通过 XmlWriter 将格式化器内容写入流
using (var writer = XmlWriter.Create(stream, settings))
{
formatter.WriteTo(writer);
}
// 将流内容转为字符串并返回
return Encoding.UTF8.GetString(stream.ToArray());
}// 分别输出为 RSS 2.0 和 Atom 1.0
var rssOutput = FormatFeed(new Rss20FeedFormatter(feed));
var atomOutput = FormatFeed(new Atom10FeedFormatter(feed));在 FormatFeed 方法中,SyndicationFeedFormatter 类是 Rss20FeedFormatter 和 Atom10FeedFormatter 共同的基类,也就是说,只需要传入不同的派生子类,就可以无需关心具体实现,也是利用了面向对象中的多态特性。
输出结果
RSS 2.0:
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>订阅源名称</title>
<link>https://example.com/</link>
<description>网站简介</description>
<language>zh-cn</language>
<managingEditor>editor@example.com</managingEditor>
<lastBuildDate>Wed, 05 Aug 2026 14:46:52 +0800</lastBuildDate>
<image>
<url>https://example.com/logo.png</url>
<title>订阅源名称</title>
<link>https://example.com/</link>
</image>
<a10:id>urn:uuid:75551e8c-cefa-4510-a6ff-335614da5435</a10:id>
<a10:link rel="self" type="application/atom+xml" href="https://example.com/feed.atom" />
<item>
<guid isPermaLink="false">urn:uuid:0e684b46-1dbf-4f69-9db3-496fdad6b32e</guid>
<link>https://example.com/posts/1</link>
<title>文章名称</title>
<description>文章摘要纯文本...</description>
<pubDate>Wed, 05 Aug 2026 14:46:52 +0800</pubDate>
<a10:updated>2026-08-05T14:46:52+08:00</a10:updated>
<a10:content type="html"><p>文章内容</p></a10:content>
</item>
</channel>
</rss>Atom 1.0:
<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="zh-cn" xmlns="http://www.w3.org/2005/Atom">
<title type="text">订阅源名称</title>
<subtitle type="text">网站简介</subtitle>
<id>urn:uuid:75551e8c-cefa-4510-a6ff-335614da5435</id>
<updated>2026-08-05T14:46:52+08:00</updated>
<logo>https://example.com/logo.png</logo>
<author>
<name>名称</name>
<uri>https://example.com</uri>
<email>editor@example.com</email>
</author>
<link rel="alternate" href="https://example.com/" />
<link rel="self" type="application/atom+xml" href="https://example.com/feed.atom" />
<entry>
<id>urn:uuid:0e684b46-1dbf-4f69-9db3-496fdad6b32e</id>
<title type="text">文章名称</title>
<summary type="text">文章摘要纯文本...</summary>
<published>2026-08-05T14:46:52+08:00</published>
<updated>2026-08-05T14:46:52+08:00</updated>
<link rel="alternate" href="https://example.com/posts/1" />
<content type="html"><p>文章内容</p></content>
</entry>
</feed>解析 RSS / Atom 订阅源
System.ServiceModel.Syndication 提供了统一的解析模型:无论输入是 RSS 2.0 还是 Atom 1.0,都通过 SyndicationFeed.Load() 自动识别并映射到同一个 SyndicationFeed 对象。
// 创建 XmlReader 读取远程订阅源(支持 RSS 2.0 和 Atom 1.0,自动识别格式)
using var reader = XmlReader.Create("https://example.com/feed");
// 使用 SyndicationFeed.Load() 解析订阅源,返回统一的 SyndicationFeed 对象
// 无论原始格式是 RSS 还是 Atom,都会映射到相同的对象模型
SyndicationFeed feed = SyndicationFeed.Load(reader);Console.WriteLine($"标题: {feed.Title?.Text}");
Console.WriteLine($"描述: {feed.Description?.Text}");
Console.WriteLine($"最后更新: {feed.LastUpdatedTime}");
Console.WriteLine($"作者: {string.Join(", ", feed.Authors.Select(a => a.Name))}");
Console.WriteLine($"链接数: {feed.Links.Count}");
Console.WriteLine($"条目数: {feed.Items.Count()}");
Console.WriteLine();
foreach (SyndicationItem item in feed.Items)
{
Console.WriteLine($"--- 条目 ---");
Console.WriteLine($" 标题: {item.Title?.Text}");
Console.WriteLine($" ID: {item.Id}");
Console.WriteLine($" 发布: {item.PublishDate}");
Console.WriteLine($" 更新: {item.LastUpdatedTime}");
Console.WriteLine($" 链接: {item.Links.FirstOrDefault()?.Uri}");
Console.WriteLine($" 摘要: {item.Summary?.Text}");
// 区分内容类型
if (item.Content is TextSyndicationContent textContent)
{
Console.WriteLine($" 内容类型: {textContent.Type}");
Console.WriteLine($" 内容: {textContent.Text}");
}
else if (item.Content is UrlSyndicationContent urlContent)
{
Console.WriteLine($" 媒体URL: {urlContent.Url} ({urlContent.MediaType})");
}
Console.WriteLine();
}