1. 项目背景与核心需求
小宇宙作为国内头部播客平台,其节目数据蕴含着丰富的价值——从内容分析、竞品调研到用户行为研究,这些结构化数据都是宝贵的一手资料。但平台并未开放官方API接口,手动复制效率极低。这正是Python爬虫大显身手的场景:通过自动化采集将非结构化的网页信息转化为结构化数据库记录。
这个项目的核心目标是完整获取单档播客节目的元数据,包括:
- 基础信息:期数、标题、单集时长
- 发布信息:上线时间、更新频率
- 内容维度:shownotes文本、嘉宾信息
- 互动数据:点赞数、评论数(如有)
最终将这些字段存入SQLite数据库,形成可随时查询分析的本地数据集。不同于简单爬取,本项目需要处理:
- 动态加载内容抓取
- 反爬策略应对
- 异构数据清洗
- 数据库关系设计
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术方案设计
2.1 爬虫框架选型
放弃Scrapy等重型框架,选择轻量级组合:
- requests:处理HTTP请求
- BeautifulSoup:解析HTML
- json:处理API返回数据
- re:正则表达式清洗数据
这种组合的优势在于:
- 依赖少(仅需基础库+bs4)
- 调试直观(可直接打印中间结果)
- 适合中小规模抓取(小宇宙单档节目通常不超过500集)
2.2 反爬应对策略
实测发现小宇宙有这些防护措施:
- User-Agent检测:未设置UA的请求直接返回403
- 频率限制:连续请求超过5次/秒会触发验证码
- 动态参数:部分API需要携带
_signature等加密参数
应对方案:
python复制headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.xiaoyuzhoufm.com/'
}
# 每次请求后暂停0.5-2秒随机时间
time.sleep(random.uniform(0.5, 2))
2.3 数据存储设计
使用SQLite3作为存储方案,表结构设计如下:
sql复制CREATE TABLE episodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
episode_no INTEGER NOT NULL, -- 期数
title TEXT NOT NULL, -- 标题
duration TEXT, -- 时长(mm:ss)
publish_time DATETIME, -- 发布时间
shownotes TEXT, -- 节目备注
like_count INTEGER DEFAULT 0, -- 点赞数
comment_count INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
注意:DATETIME类型在SQLite中实际以TEXT存储,需在应用层处理时间格式转换
3. 核心代码实现
3.1 页面解析关键逻辑
通过Chrome开发者工具分析,发现节目数据通过API接口返回:
code复制https://www.xiaoyuzhoufm.com/api/web/episodes?podcast_id=xxx&page=1
响应数据示例:
json复制{
"data": {
"episodes": [
{
"eid": "abc123",
"title": "EP10: 对话AI专家",
"duration": 3765,
"pub_date": "2023-05-20T08:00:00Z",
"shownotes": "<p>本期我们讨论...</p>",
"like_count": 245,
"comment_count": 38
}
]
}
}
解析代码:
python复制def parse_episode(json_data):
episode = {
'episode_no': int(re.search(r'EP(\d+)', item['title']).group(1)),
'title': item['title'].strip(),
'duration': f"{item['duration']//60}:{item['duration']%60:02d}",
'publish_time': datetime.strptime(
item['pub_date'], '%Y-%m-%dT%H:%M:%SZ'
).strftime('%Y-%m-%d %H:%M:%S'),
'shownotes': BeautifulSoup(item['shownotes'], 'html.parser').get_text(),
'like_count': item['like_count'],
'comment_count': item['comment_count']
}
return episode
3.2 数据库操作封装
使用sqlite3的上下文管理确保连接安全:
python复制class PodcastDB:
def __init__(self, db_path='podcast.db'):
self.db_path = db_path
self._create_table()
def _create_table(self):
with sqlite3.connect(self.db_path) as conn:
conn.execute('''CREATE TABLE IF NOT EXISTS episodes(...)''')
def save_episode(self, episode):
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
INSERT INTO episodes (...)
VALUES (?,?,?,?,?,?,?)
''', [
episode['episode_no'],
episode['title'],
# 其他字段...
])
4. 实战技巧与避坑指南
4.1 高频问题解决方案
Q1 获取不到最新数据?
- 检查API URL中的分页参数,有些节目需要修改
&page=参数 - 最新数据可能通过WebSocket推送,可尝试使用Selenium模拟浏览器
Q2 持续时间显示异常?
- 原始数据可能是秒数,需要转换:
python复制f"{duration//3600:02d}:{(duration%3600)//60:02d}:{duration%60:02d}"
Q3 中文乱码问题?
- 确保请求头包含:
python复制headers['Accept-Encoding'] = 'gzip, deflate'
headers['Accept-Language'] = 'zh-CN,zh;q=0.9'
4.2 性能优化建议
- 增量采集:记录最后采集的episode_no,下次只获取新数据
- 连接池优化:使用
sqlite3.connect(..., isolation_level=None)关闭事务 - 批量插入:积累100条数据后执行批量INSERT
4.3 法律风险提示
- 严格遵守robots.txt协议(实测小宇宙未禁止合规爬取)
- 采集频率控制在合理范围(建议≤1请求/秒)
- 数据仅用于个人学习,禁止商用
- 在headers中声明采集者身份:
python复制headers['X-Crawler-Info'] = 'Educational-use-only/1.0'
5. 扩展应用场景
这套方案稍作修改即可适配:
- 多节目批量采集:遍历podcast_id列表
- 评论情感分析:增加comments表存储用户评论
- 更新监控:定期检查新节目并发送通知
- 内容分析:对shownotes做词频统计、主题建模
进阶改进方向:
- 使用Airflow搭建定时爬取任务
- 增加Docker容器化部署
- 开发可视化数据分析面板
关键提示:当需要扩展功能时,建议先完善异常处理机制,特别是网络请求重试和数据库回滚功能
