RSS内容截断的表现通常是:订阅源里只出现了文章标题和一小段摘要,阅读器下方挂着一个“阅读全文”的链接,点击后打开原网站。这个现象由RSS发布端决定,而不是客户端解析失败。无论是RSS 2.0还是Atom,都允许条目携带摘要字段和完整内容字段,但很多站点为了把流量留在自己域名下,会只输出摘要甚至空描述。理解这些字段的差异,以及客户端如何判断、恢复全文,是处理RSS内容截断的基础。

一、规范层面:description 与 content:encoded 的区别
RSS 2.0规范中,每个<item>元素必须包含<title>和<description>等基本信息。其中<description>既可以放纯文本摘要,也可以放HTML片段,但它的长度没有强制要求。很多发布系统只把文章的前几句话写入<description>,这就是内容截断的源头。相比之下,Atom规范把摘要和正文拆得更清楚:<summary>负责摘要,<content>负责完整正文。
RSS 2.0本身没有专门用于完整正文的标准元素,于是社区普遍使用<content:encoded>这个扩展元素来承载HTML格式的全文。如果发布端同时输出<description>和<content:encoded>,客户端可以优先读取后者。只输出<description>且只包含前几百字时,客户端就会遇到内容截断。
<item> <title>深入理解RSS内容截断</title> <link>https://bbccb.com/rss-truncation</link> <description>这篇文章讨论RSS摘要和全文的差异,先给出一个简单概述。</description> <content:encoded><p>这是完整正文的第一段。</p><p>这里是第二段,包含完整技术细节。</p></content:encoded> </item>
通过上面的结构可以看出,同一篇文章在RSS源里可能同时存在摘要和全文。如果只读取<description>,就会丢失大量正文。某些客户端会把这些字段拼接起来,但如果<content:encoded>缺失,拼接也无从谈起。
| 规范/扩展 | 字段 | 用途 | 是否常见截断 |
|---|---|---|---|
| RSS 2.0 | <description> | 条目描述或摘要 | 经常只含摘要 |
| RSS 2.0扩展 | <content:encoded> | 完整HTML正文 | 通常完整,但并非必填 |
| Atom | <summary> | 摘要 | 可能截断 |
| Atom | <content> | 完整正文 | 多数完整 |
二、发布端为什么会截断内容
从发布者的角度看,RSS全文输出意味着文章内容和图片会在自己的网站之外被完整消费。这会减少原站访问量,进而影响广告曝光、用户统计和会员转化。因此很多商业网站选择在RSS中只放出摘要,引导读者点击<link>回到原页面。这种行为在技术层面完全合法,但对订阅用户来说会打断阅读体验。
还有一部分截断并非刻意,而是发布系统默认行为。比如WordPress早期版本的RSS设置中,“在Feed中显示全文”和“显示摘要”是可选配置,如果站点管理员选择了摘要,RSS输出就自动变成截断内容。截断方式通常包括按字符数截取、按段落数截取,或者去除图片、表格、视频等富媒体。实现上,发布端会在保存文章时生成摘要字段,或在输出RSS时对正文做裁剪。
import re
def make_summary(html_content, max_chars=200):
# 去掉HTML标签,避免截断破坏结构
text = re.sub(r'<[^>]+>', '', html_content)
text = text.strip()
if len(text) <= max_chars:
return text
# 在最后一个标点处截断,避免词语被切断
cut = text[:max_chars]
last_period = max(cut.rfind('。'), cut.rfind('!'), cut.rfind('?'))
if last_period > 50:
cut = cut[:last_period + 1]
return cut + '...'
除了主动截断,部分发布端还会因为RSS模板不支持复杂HTML而丢失内容。比如某些论坛或CMS系统在生成RSS时只输出<description>的纯文本版本,而纯文本版本又经过一次格式清理,最终导致图片和代码块全部消失。这类情况对技术博客尤其严重,因为代码示例一旦丢失,读者很难判断文章质量。
三、客户端识别截断并尝试恢复全文
客户端拿到RSS条目后,首先需要判断内容是否截断。常见的判断策略包括:检查<content:encoded>是否存在,如果存在则基本可以认为正文完整;如果只有<description>,则比较文本长度、是否以省略号或“阅读全文”结尾;还可以解析<link>并抓取原网页,用正文抽取算法得到完整内容。
一个务实的流程是:优先读取<content:encoded>;如果没有,再读取<description>;如果描述文本少于200字符,或者包含明显的截断标记,就请求<link>指向的页面。抓取原页面后,不能直接使用整个HTML,应当借助可读性算法或CSS选择器提取正文区域,再去掉广告、导航、评论等噪声。
import feedparser
import requests
from bs4 import BeautifulSoup
def get_entry_content(entry):
full = entry.get('content', None)
if full:
return full[0].value
summary = entry.get('description', '')
link = entry.get('link', '')
# 如果摘要明显太短,尝试抓取原文
if len(summary) < 300 and link:
try:
resp = requests.get(link, timeout=10)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, 'html.parser')
article = soup.find('article') or soup.find('main') or soup.body
if article:
return article.get_text(separator='\n', strip=True)
except requests.RequestException:
pass
return summary
上面的代码演示了最基本的恢复逻辑,但它不够健壮。真实场景中还需要处理页面编码、动态内容、登录墙、验证码等问题。如果原网页使用了大量JavaScript渲染,requests拿到的HTML可能没有正文,这时需要引入无头浏览器或第三方提取服务。另一个细节是链接可能是相对地址,需要先用RSS源地址进行补全。
还有一些阅读器采用更保守的策略:只从原网页中提取<article>或<main>标签内的HTML,而不是纯文本,这样能保留段落、列表和代码块格式。提取后还必须把所有相对链接转换成绝对链接,否则图片和跳转都会失效。
四、降低截断影响的实践建议
如果你是RSS源发布者,最好的做法是在<content:encoded>中输出完整的HTML正文,并用CDATA包裹,避免XML解析错误。以WordPress为例,后台“设置—阅读”中选择“在Feed中显示全文”,同时使用插件确保<content:encoded>存在。对于自行开发的RSS接口,应该把原始正文HTML原样写入扩展字段,而不是重新编码或剥离样式。
客户端开发者则需要做好防御式解析:不要假设<description>一定包含完整内容;对<content:encoded>存在与否要做判断;抓取原页面时要限制超时、设置合理的用户代理,并缓存结果,避免频繁请求源站。对于订阅用户来说,理解这种截断行为后,可以选择支持全文抓取的阅读器,或者使用RSS全文服务来提升阅读体验。
总结起来,RSS内容截断不是单一环节的问题。规范允许截断,发布端有动机截断,客户端必须识别并恢复。成熟的处理方式是在字段层面优先选择完整内容字段,在展示层面提供原文链接,在抓取层面做好兼容和限流。只要把这几层都考虑到,RSS内容截断带来的体验损失就能降到最低。