🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [📚 上期回顾](#📚 上期回顾)
- [🎯 本篇目标](#🎯 本篇目标)
- [💡 什么是 RSS?为什么选它?](#💡 什么是 RSS?为什么选它?)
-
- [RSS 简介](#RSS 简介)
- [RSS 数据结构示例](#RSS 数据结构示例)
- [🗄️ 数据库设计](#🗄️ 数据库设计)
- [🛠️ 代码实战:完整实现](#🛠️ 代码实战:完整实现)
- [📊 运行效果](#📊 运行效果)
- [⚠️ 常见问题处理](#⚠️ 常见问题处理)
-
- [问题 1:RSS 源解析失败](#问题 1:RSS 源解析失败)
- [问题 2:时间格式不统一](#问题 2:时间格式不统一)
- [🚀 扩展功能](#🚀 扩展功能)
-
- [扩展 1:定时采集](#扩展 1:定时采集)
- [扩展 2:导出 OPML(RSS 源分享格式)](#扩展 2:导出 OPML(RSS 源分享格式))
- [📝 小结](#📝 小结)
- [🎯 下期预告](#🎯 下期预告)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》
订阅后更新会优先推送,按目录学习更高效~
📚 上期回顾
恭喜你完成前 7 章的学习!从最基础的 Requests 静态爬取,到解析清洗、数据入库、增量去重、断点续爬,再到 Playwright 动态页面和 API 逆向------你已经掌握了爬虫工程化的完整链路。
但学了这么多,总感觉缺点什么?缺少一个能拿得出手的完整项目。
今天开始,我们进入实战环节。第一个项目很实用:RSS 聚合器------从多个新闻源采集内容,去重入库,提供简单查询。麻雀虽小,五脏俱全,而且能真正跑起来!
🎯 本篇目标
看完这篇,你能做到:
- 理解 RSS 协议(最简单的数据源)
- 设计数据库表结构(多源聚合场景)
- 实现完整采集流程(采集→解析→去重→入库)
- 提供简单查询接口(按时间、关键词搜索)
验收标准:从 3 个 RSS 源采集至少 100 条新闻,存入数据库,能按关键词搜索。
💡 什么是 RSS?为什么选它?
RSS 简介
RSS(Really Simple Syndication)是一种内容订阅协议,很多网站提供 RSS Feed 让用户订阅更新。
特点:
- ✅ 数据格式标准(XML),不需要写复杂解析
- ✅ 合法获取(网站主动提供)
- ✅ 稳定可靠(格式变化少)
- ✅ 适合新手(零门槛入门项目)
典型 RSS 源:
json
https://www.zhihu.com/rss
https://sspai.com/feed
https://blog.example.com/feed.xml
RSS 数据结构示例
xml
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>某科技博客</title>
<link>https://blog.example.com</link>
<item>
<title>Python爬虫入门教程</title>
<link>https://blog.example.com/post/123</link>
<pubDate>Fri, 23 Jan 2026 10:00:00 GMT</pubDate>
<description>今天我们来学习爬虫...</description>
<guid>https://blog.example.com/post/123</guid>
</item>
<item>
<title>数据清洗技巧</title>
<link>https://blog.example.com/post/124</link>
<pubDate>Fri, 22 Jan 2026 15:30:00 GMT</pubDate>
</item>
</channel>
</rss>
核心字段:
title:标题link:原文链接pubDate:发布时间description:摘要/正文guid:唯一标识(去重用)
🗄️ 数据库设计
表结构设计
sql
-- 创建文章表
CREATE TABLE articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- 去重键(核心字段)
dedup_key TEXT UNIQUE NOT NULL,
-- 来源信息
source_name TEXT NOT NULL, -- 来源名称(如"知乎")
source_url TEXT NOT NULL, -- RSS 源地址
-- 文章信息
title TEXT NOT NULL,
link TEXT NOT NULL,
description TEXT, -- 摘要
content TEXT, -- 完整正文(可选)
author TEXT, -- 作者
-- 时间信息
pub_date TEXT, -- 发布时间(原始格式)
pub_timestamp INTEGER, -- 发布时间戳(方便排序)
crawled_at TEXT NOT NULL, -- 采集时间
-- 索引字段
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
);
-- 创建索引(提升查询性能)
CREATE INDEX idx_source ON articles(source_name);
CREATE INDEX idx_pub_timestamp ON articles(pub_timestamp);
CREATE INDEX idx_crawled_at ON articles(crawled_at);
CREATE INDEX idx_title ON articles(title); -- 支持关键词搜索
-- 创建 RSS 源管理表
CREATE TABLE rss_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL, -- 源名称
url TEXT UNIQUE NOT NULL, -- RSS 地址
enabled BOOLEAN DEFAULT TRUE, -- 是否启用
last_crawl_time TEXT, -- 上次采集时间
article_count INTEGER DEFAULT 0, -- 累计文章数
created_at TEXT DEFAULT (datetime('now'))
);
🛠️ 代码实战:完整实现
第一步:安装依赖
bash
pip install feedparser requests
feedparser 是专门解析 RSS/Atom 的库,非常好用!
第二步:RSS 源管理器
python
import sqlite3
from datetime import datetime
from pathlib import Path
class RSSSourceManager:
"""RSS 源管理器"""
def __init__(self, db_path="rss_aggregator.db"):
self.db_path = db_path
self.conn = sqlite3.connect(db_path, check_same_thread=False)
self._init_db()
def _init_db(self):
"""初始化数据库表"""
# 创建文章表
self.conn.execute("""
CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dedup_key TEXT UNIQUE NOT NULL,
source_name TEXT NOT NULL,
source_url TEXT NOT NULL,
title TEXT NOT NULL,
link TEXT NOT NULL,
description TEXT,
author TEXT,
pub_date TEXT,
pub_timestamp INTEGER,
crawled_at TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)
""")
# 创建索引
self.conn.execute("CREATE INDEX IF NOT EXISTS idx_source ON articles(source_name)")
self.conn.execute("CREATE INDEX IF NOT EXISTS idx_pub_timestamp ON articles(pub_timestamp)")
# 创建 RSS 源表
self.conn.execute("""
CREATE TABLE IF NOT EXISTS rss_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
url TEXT UNIQUE NOT NULL,
enabled BOOLEAN DEFAULT TRUE,
last_crawl_time TEXT,
article_count INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now'))
)
""")
self.conn.commit()
def add_source(self, name, url):
"""添加 RSS 源"""
try:
self.conn.execute("""
INSERT INTO rss_sources (name, url)
VALUES (?, ?)
""", (name, url))
self.conn.commit()
print(f"✅ 添加 RSS 源:{name}")
return True
except sqlite3.IntegrityError:
print(f"⚠️ RSS 源已存在:{name}")
return False
def get_enabled_sources(self):
"""获取启用的 RSS 源"""
cursor = self.conn.execute("""
SELECT name, url FROM rss_sources WHERE enabled = 1
""")
return cursor.fetchall()
def update_crawl_time(self, name):
"""更新采集时间"""
self.conn.execute("""
UPDATE rss_sources
SET last_crawl_time = datetime('now')
WHERE name = ?
""", (name,))
self.conn.commit()
第三步:RSS 采集器
python
import feedparser
import requests
from hashlib import md5
from datetime import datetime
import time
class RSSCrawler:
"""RSS 采集器"""
def __init__(self, db_manager):
self.db = db_manager
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (compatible; RSSBot/1.0)'
})
def fetch_feed(self, source_name, feed_url):
"""
采集单个 RSS 源
Returns:
int: 新增文章数
"""
print(f"\n🔍 正在采集:{source_name}")
print(f" URL: {feed_url}")
try:
# 方式 1:直接用 feedparser(推荐)
feed = feedparser.parse(feed_url)
# 方式 2:先用 requests 下载再解析(更可控)
# resp = self.session.get(feed_url, timeout=30)
# feed = feedparser.parse(resp.content)
if feed.bozo: # 解析出错
print(f"❌ RSS 解析失败:{feed.bozo_exception}")
return 0
entries = feed.entries
print(f" 📄 发现 {len(entries)} 篇文章")
new_count = 0
for entry in entries:
if self.save_article(source_name, feed_url, entry):
new_count += 1
print(f" ✅ 新增 {new_count} 篇文章")
# 更新采集时间
self.db.update_crawl_time(source_name)
return new_count
except Exception as e:
print(f"❌ 采集失败:{e}")
return 0
def save_article(self, source_name, source_url, entry):
"""
保存单篇文章(带去重)
Returns:
bool: 是否为新增
"""
# 生成去重键(优先用 guid,否则用 link)
unique_id = entry.get('id') or entry.get('link')
dedup_key = md5(f"{source_name}_{unique_id}".encode()).hexdigest()
# 提取字段
title = entry.get('title', '').strip()
link = entry.get('link', '').strip()
description = entry.get('description') or entry.get('summary', '')
author = entry.get('author', '')
# 解析时间
pub_date = entry.get('published') or entry.get('updated')
pub_timestamp = None
if pub_date:
try:
# feedparser 会自动解析时间
time_struct = entry.get('published_parsed') or entry.get('updated_parsed')
if time_struct:
pub_timestamp = int(time.mktime(time_struct))
except:
pass
# 插入数据库(去重)
try:
self.db.conn.execute("""
INSERT INTO articles (
dedup_key, source_name, source_url,
title, link, description, author,
pub_date, pub_timestamp, crawled_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
""", (
dedup_key, source_name, source_url,
title, link, description, author,
pub_date, pub_timestamp
))
self.db.conn.commit()
return True # 新增成功
except sqlite3.IntegrityError:
# 重复数据,跳过
return False
def crawl_all_sources(self):
"""采集所有启用的 RSS 源"""
sources = self.db.get_enabled_sources()
if not sources:
print("⚠️ 没有可用的 RSS 源")
return
print(f"📦 共 {len(sources)} 个 RSS 源待采集")
total_new = 0
for name, url in sources:
new_count = self.fetch_feed(name, url)
total_new += new_count
time.sleep(1) # 礼貌延迟
print(f"\n✨ 采集完成!共新增 {total_new} 篇文章")
第四步:查询接口
python
class RSSQuery:
"""RSS 查询接口"""
def __init__(self, db_manager):
self.db = db_manager
def search_by_keyword(self, keyword, limit=20):
"""按关键词搜索"""
cursor = self.db.conn.execute("""
SELECT source_name, title, link, pub_date, description
FROM articles
WHERE title LIKE ? OR description LIKE ?
ORDER BY pub_timestamp DESC
LIMIT ?
""", (f'%{keyword}%', f'%{keyword}%', limit))
return cursor.fetchall()
def get_latest(self, source=None, limit=20):
"""获取最新文章"""
if source:
cursor = self.db.conn.execute("""
SELECT source_name, title, link, pub_date
FROM articles
WHERE source_name = ?
ORDER BY pub_timestamp DESC
LIMIT ?
""", (source, limit))
else:
cursor = self.db.conn.execute("""
SELECT source_name, title, link, pub_date
FROM articles
ORDER BY pub_timestamp DESC
LIMIT ?
""", (limit,))
return cursor.fetchall()
def get_stats(self):
"""获取统计信息"""
cursor = self.db.conn.execute("""
SELECT
source_name,
COUNT(*) as count,
MAX(pub_date) as latest_pub_date
FROM articles
GROUP BY source_name
ORDER BY count DESC
""")
return cursor.fetchall()
第五步:主程序
python
# main.py
from rss_aggregator import RSSSourceManager, RSSCrawler, RSSQuery
def init_sources(manager):
"""初始化 RSS 源(首次运行时调用)"""
sources = [
('少数派', 'https://sspai.com/feed'),
('阮一峰博客', 'https://www.ruanyifeng.com/blog/atom.xml'),
('Python官方博客', 'https://blog.python.org/feeds/posts/default')
]
for name, url in sources:
manager.add_source(name, url)
def main():
# 初始化
db_manager = RSSSourceManager()
crawler = RSSCrawler(db_manager)
query = RSSQuery(db_manager)
# 首次运行:添加 RSS 源
# init_sources(db_manager)
# 采集所有源
print("="*50)
print("🚀 开始采集 RSS...")
print("="*50)
crawler.crawl_all_sources()
# 查询统计
print("\n" + "="*50)
print("📊 采集统计")
print("="*50)
stats = query.get_stats()
for source, count, latest in stats:
print(f" {source}: {count} 篇(最新:{latest})")
# 查询最新文章
print("\n" + "="*50)
print("📰 最新 10 篇文章")
print("="*50)
latest = query.get_latest(limit=10)
for i, (source, title, link, pub_date) in enumerate(latest, 1):
print(f"{i}. [{source}] {title}")
print(f" {link}")
print()
# 关键词搜索
keyword = "Python"
print(f"\n{'='*50}")
print(f"🔍 搜索关键词:{keyword}")
print("="*50)
results = query.search_by_keyword(keyword, limit=5)
for source, title, link, pub_date, desc in results:
print(f"[{source}] {title}")
print(f" {link}")
print()
if __name__ == '__main__':
main()
📊 运行效果
json
==================================================
🚀 开始采集 RSS...
==================================================
📦 共 3 个 RSS 源待采集
🔍 正在采集:少数派
URL: https://sspai.com/feed
📄 发现 20 篇文章
✅ 新增 20 篇文章
🔍 正在采集:阮一峰博客
URL: https://www.ruanyifeng.com/blog/atom.xml
📄 发现 15 篇文章
✅ 新增 15 篇文章
🔍 正在采集:Python官方博客
URL: https://blog.python.org/feeds/posts/default
📄 发现 10 篇文章
✅ 新增 10 篇文章
✨ 采集完成!共新增 45 篇文章
==================================================
📊 采集统计
==================================================
少数派: 20 篇(最新:2026-01-23)
阮一峰博客: 15 篇(最新:2026-01-22)
Python官方博客: 10 篇(最新:2026-01-21)
==================================================
📰 最新 10 篇文章
==================================================
1. [少数派] 2026年值得入手的生产力工具
https://sspai.com/post/123456
2. [阮一峰博客] 科技爱好者周刊(第 288 期)
https://www.ruanyifeng.com/blog/2026/01/weekly-issue-288.html
...
⚠️ 常见问题处理
问题 1:RSS 源解析失败
现象:feed.bozo = True
原因:XML 格式不规范、编码问题、网络超时
解决:
python
# 容错处理
if feed.bozo:
print(f"⚠️ 解析有问题,但尝试继续:{feed.bozo_exception}")
# 有些源虽然 bozo=True 但 entries 还是能用的
if not feed.entries:
return 0
问题 2:时间格式不统一
解决:
python
from dateutil import parser
def parse_date_safe(date_str):
"""容错时间解析"""
try:
return parser.parse(date_str)
except:
return None
🚀 扩展功能
扩展 1:定时采集
python
import schedule
def job():
print(f"\n⏰ {datetime.now()} 开始定时采集")
crawler.crawl_all_sources()
# 每小时采集一次
schedule.every(1).hours.do(job)
while True:
schedule.run_pending()
time.sleep(60)
扩展 2:导出 OPML(RSS 源分享格式)
python
def export_opml(db_manager):
"""导出 RSS 源列表为 OPML 格式"""
sources = db_manager.get_enabled_sources()
opml = '<?xml version="1.0" encoding="UTF-8"?>\n'
opml += '<opml version="2.0">\n'
opml += ' <body>\n'
for name, url in sources:
opml += f' <outline text="{name}" xmlUrl="{url}"/>\n'
opml += ' </body>\n'
opml += '</opml>'
with open('subscriptions.opml', 'w', encoding='utf-8') as f:
f.write(opml)
print("✅ OPML 已导出:subscriptions.opml")
📝 小结
今天我们完成了第一个完整项目------RSS 聚合器,涵盖了:
- 数据库设计(去重键、索引、多源管理)
- RSS 解析(feedparser 使用)
- 采集流程(循环采集、去重入库)
- 查询接口(关键词搜索、最新文章)
这个项目麻雀虽小,但包含了爬虫工程化的核心要素。你可以把它部署到服务器上,每小时定时采集,打造属于自己的信息聚合平台!
🎯 下期预告
RSS 聚合器相对简单,下一篇我们要挑战更硬的骨头------信息聚合站 Demo!
《项目 2:信息聚合站 Demo(列表+详情+增量+质量报告)》将实现:
- 列表页→详情页的二段式采集
- 增量采集策略
- 数据质量报告
- 断点续爬机制
这是一个真正的"作品级"项目,可以写进简历!
验收作业:部署你的 RSS 聚合器,从 3 个源采集 100+ 篇文章,截图给我看看!加油!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。