RSS是一种非常经典的内容订阅格式,很多博客、新闻站点至今都保留着RSS或Atom源,输出的内容就是一份标准的XML文档。对爬虫来说这简直是好消息——不需要写复杂的XPath去抠HTML页面,只要解析XML里的几个固定节点,就能稳定拿到文章标题、链接、发布时间和摘要。本文介绍用Python爬取和解析RSS源的几种常见做法,并覆盖编码、命名空间、反爬这些容易踩坑的地方。

一、用requests抓取RSS源内容
第一步是把RSS源的内容下载下来。最直接的方式是用requests库发送GET请求。大多数RSS源不需要登录,也不需要复杂的参数,直接请求源地址即可。示例代码如下:
import requests
url = "https://ippipp.com/feed.xml"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 请求失败时抛出异常
# 根据HTTP头或XML声明推断编码
response.encoding = response.apparent_encoding
xml_text = response.text
print(xml_text[:500])这里有几个细节值得注意。第一,一定要带上User-Agent,部分站点会过滤掉默认的python-requests标识,直接返回403。第二,编码问题非常常见,有些老站点的RSS用的是GBK编码,而requests默认按ISO-8859-1解析,会导致中文乱码,用apparent_encoding让chardet自动探测通常能解决。第三,建议加上timeout参数,避免网络异常时程序长时间挂起。
如果想更稳妥一些,可以拿到底层字节后自己交给解析器处理,即使用response.content而不是response.text,把编码判断交给XML解析器自己完成,因为规范的XML文档第一行会声明自己的编码,解析器读取声明比外部猜测更准确。
二、用ElementTree解析XML节点
拿到XML文本后,Python标准库自带的xml.etree.ElementTree就能完成解析,不需要额外安装依赖。RSS的结构比较固定:根元素是<rss>,下面有<channel>,频道信息里再嵌套若干<item>,每条item对应一篇文章。
import xml.etree.ElementTree as ET
root = ET.fromstring(xml_text)
# RSS 2.0 的基本结构
for item in root.iter("item"):
title = item.findtext("title", default="")
link = item.findtext("link", default="")
pub_date = item.findtext("pubDate", default="")
description = item.findtext("description", default="")
print(f"标题: {title}")
print(f"链接: {link}")
print(f"发布时间: {pub_date}")
print(f"摘要: {description[:80]}")
print("-" * 40)如果抓的是Atom格式的源,节点名就不一样了,条目叫<entry>,链接放在<link>标签的href属性里,时间字段叫<updated>。更麻烦的是Atom大量使用命名空间,直接用find("entry")可能什么都找不到,需要带上命名空间前缀:
ns = {"atom": "http://www.w3.org/2005/Atom"}
for entry in root.findall("atom:entry", ns):
title = entry.findtext("atom:title", namespaces=ns)
link_el = entry.find("atom:link", ns)
link = link_el.get("href") if link_el is not None else ""
updated = entry.findtext("atom:updated", namespaces=ns)
print(title, link, updated)这种手写命名空间的方式功能上没问题,但代码容易变得臃肿。如果你的数据源混合了RSS 2.0和Atom两种格式,写两套解析逻辑维护成本不低,这时候就轮到专用工具出场了。
三、用feedparser统一处理各种格式
feedparser是Python生态里专门处理RSS和Atom的库,安装也很简单:pip install feedparser。它最大的价值在于屏蔽了格式差异,无论是RSS 1.0、RSS 2.0还是Atom,解析出来的数据结构都是统一的,命名空间也被自动处理掉了。
import feedparser
url = "https://ippipp.com/feed.xml"
feed = feedparser.parse(url)
print(f"订阅源名称: {feed.feed.title}")
for entry in feed.entries:
print(f"标题: {entry.get('title', '')}")
print(f"链接: {entry.get('link', '')}")
# feedparser 会把时间统一转成 struct_time
published = entry.get("published_parsed")
print(f"时间: {published}")
print(f"摘要: {entry.get('summary', '')[:80]}")
print("-" * 40)注意几个好用的特性。feedparser可以直接传入URL,内部会完成下载,也可以传入已经下载好的字符串或字节。它会把各种格式的时间字段统一解析成struct_time,省去了自己处理RFC 822和ISO 8601两种时间格式的麻烦。此外它对畸形XML有较强的容错能力,遇到漏写闭合标签的源也不容易直接崩溃。
不过feedparser默认不发User-Agent头,遇到有反爬的站点可能被拒,所以更稳妥的组合是:用requests带请求头下载内容,再把response.content传给feedparser.parse,这样下载环节完全可控,解析环节交给专业的库。
四、常见的坑与应对办法
实际抓取中问题不少,这里总结几条高频出现的。一是编码乱码,前面提到的apparent_encoding和直接传字节给解析器是主要解法。二是摘要里混着HTML标签,description字段经常包含完整的HTML片段,如果想拿纯文本,可以用一个简单的方法剥离标签:
import re
def strip_html(text):
# 去掉HTML标签,再把实体还原
clean = re.sub(r"<[^>]+>", "", text)
return clean.strip()
print(strip_html(entry.get("summary", "")))三是频率控制,抓RSS不要太猛,多数站点几分钟更新一次已经算频繁了,循环采集时加上time.sleep(60)之类的间隔既礼貌又安全。四是增量抓取,可以记录每条目的链接或GUID,只保存新出现的条目,避免重复入库。
最后在方案选择上给个建议:临时抓一两个固定的RSS 2.0源,标准库ElementTree零依赖就够用;数据源多、格式混杂、要长期维护采集程序,直接上feedparser,代码量少一半而且坑少。把上面几个模块组合起来,一个几十行的Python脚本就能稳定地订阅你关心的所有站点内容。