Reddit 内容抓取方案调研报告
任务编号: T-mn5uz39y 调研目标: Reddit 内容抓取技术方案、高赞帖子筛选、戏剧性内容识别、短剧素材提取 目标子版块: r/AmITheAsshole, r/relationship_advice, r/funny 调研日期: 2026-03-25 可信度: 高(多源交叉验证)
1. Reddit API 方案 vs 网页爬虫方案
1.1 方案对比总览
| 维度 | 官方 API (PRAW) | JSON 端点 | Old Reddit | 浏览器自动化 |
|---|---|---|---|---|
| 合规性 | ✅ 完全合规 | ⚠️ 灰色地带 | ⚠️ 可能违反 ToS | ❌ 高风险 |
| 认证要求 | OAuth 2.0 | 无需认证 | 无需认证 | 模拟登录 |
| 速率限制 | 100 QPM (免费) | 无明确限制 | 无明确限制 | 依赖实现 |
| 数据完整度 | 高 | 中 | 高 | 高 |
| 开发成本 | 中 | 低 | 中 | 高 |
| 反爬风险 | 无 | 低 | 中 | 高 |
| 单次请求限制 | 1000 posts | 100 posts | 500 comments | 无限制 |
| 典型耗时(1000条) | ~20分钟 | ~5分钟 | ~3分钟 | ~9分钟 |
1.2 官方 API (PRAW) 详解
适用场景: 需要写操作、长期自动化、合规要求高的项目
认证方式
import praw
reddit = praw.Reddit(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
user_agent="DramaScraper/1.0 by u/username",
redirect_uri="http://localhost:8080"
)
免费版限制
- 100 queries per minute (QPM) - 按 10 分钟窗口平均计算
- 仅限非商业用途 (个人项目、学术研究、开源工具)
- 需要 OAuth 2.0 认证
- 无法访问 NSFW 内容(2023年7月5日后限制)
- 历史数据访问受限
付费版定价
| 级别 | 价格 | 速率限制 |
|---|---|---|
| Free | $0 | 100 QPM (非商业) |
| Commercial | $0.24 / 1000 API calls | 按量计费 |
| Premium | ~$12,000/年起 | 100-1000 QPM |
| Enterprise | 定制 | 1000+ QPM |
Rate Limit Headers
X-Ratelimit-Used: 当前窗口已使用请求数
X-Ratelimit-Remaining: 剩余请求数
X-Ratelimit-Reset: 限制重置倒计时(秒)
1.3 JSON 端点方案(推荐用于只读)
适用场景: 快速原型、小批量数据、预算有限
端点模板
# 帖子列表
https://www.reddit.com/r/{subreddit}/{sort}.json?limit=100&t={time_period}
# 单帖详情
https://www.reddit.com/r/{subreddit}/comments/{post_id}.json?limit=100
# 用户历史
https://www.reddit.com/user/{username}/submitted.json
Sort 参数
hot- 热门(Reddit 热榜算法)new- 最新top- 最高赞rising- 上升中controversial- 争议
Time 参数(仅 top/controversial)
hour- 过去1小时day- 过去24小时week- 过去一周month- 过去一月year- 过去一年all- 全部时间
1.4 Old Reddit 方案(推荐用于评论抓取)
适用场景: 需要大量评论、绕过 API 限制
https://old.reddit.com/r/{subreddit}/comments/{post_id}/?limit=500
优点:
- 单次请求最多 500 条评论
- 无需 JavaScript 渲染
- HTML 结构简单易于解析
1.5 推荐方案
混合策略:
- 主要使用 JSON 端点 - 快速获取帖子列表和基础数据
- Old Reddit 补充 - 当需要深度评论树时使用
- PRAW 作为备选 - 当需要写操作或遇到限制时切换
2. 高赞帖子筛选策略
2.1 Reddit Hot 算法解析
核心公式 (Amir Salihefendic, 2010):
h = log₁₀(max(|u-d|, 1)) + sign(u-d) × (t - t₀)/45000
参数:
h= Hot scoreu= upvotes 数量d= downvotes 数量t= Unix 时间戳t₀= 1134028003 (2005-12-08)45000= 12.5 小时的秒数
关键洞察:
- 对数权重: 前 10 个 upvote = 接下来的 100 个 = 再接下来的 1000 个
- 时间衰减: 每 12.5 小时,帖子自然下沉 1 个单位
- 首小时决定论: 前 10 分钟的 10 个 upvote > 2 小时内的 100 个
2.2 筛选策略矩阵
| 目标 | Sort | Time | 筛选条件 |
|---|---|---|---|
| 爆款内容 | top |
month/year/all |
score > 10000, upvote_ratio > 0.85 |
| 趋势内容 | hot |
- | comment_count > 100, rising 速度 |
| 争议内容 | controversial |
week/month |
upvote_ratio 0.3-0.7, comments > 500 |
| 新热内容 | rising |
- | upvote_velocity > 10/min |
| 长尾优质 | top |
all |
手动按 score 降序 |
2.3 高互动指标阈值
HIGH_ENGAGEMENT_THRESHOLDS = {
"elite_tier": {
"score": 10000,
"comment_count": 2000,
"upvote_ratio": 0.90
},
"high_tier": {
"score": 5000,
"comment_count": 1000,
"upvote_ratio": 0.85
},
"good_tier": {
"score": 1000,
"comment_count": 500,
"upvote_ratio": 0.80
},
"rising_tier": {
"score": 100,
"comment_count": 50,
"growth_rate": 2.0 # 每小时翻倍
}
}
3. 戏剧性内容识别指标
3.1 争议度指标
Upvote Ratio 分类: | 类别 | 范围 | 特征 | |------|------|------| | Consensus | 0.80-1.00 | 广泛认同,争议低 | | Mild Debate | 0.70-0.80 | 轻微分歧 | | Controversial | 0.30-0.70 | 高度争议,两极分化 | | Heavily Downvoted | 0.00-0.30 | 被踩严重,可能是钓鱼或极端观点 |
争议度得分:
def controversy_score(upvotes, downvotes):
"""
越接近 0.5 越具争议
"""
total = upvotes + downvotes
if total == 0:
return 0
ratio = upvotes / total
return 1 - abs(ratio - 0.5) * 2 # 0-1, 1=完全争议
3.2 情绪关键词库
DRAMA_KEYWORDS = {
"conflict_triggers": [
"aita", "asshole", "wrong", "fault", "blame",
"cheated", "betrayed", "lied", "secret",
"divorce", "breakup", "dumped", "ghosted",
"kicked out", "cut off", "disowned",
"inheritance", "will", "money", "debt",
"wedding", "bridezilla", "groomzilla"
],
"emotional_intensity": [
"furious", "devastated", "heartbroken", "livid",
"panic", "anxiety", "depression", "trauma",
"suicidal", "therapy", "counseling",
"cried", "screaming", "yelling", "fight"
],
"relationship_types": [
"boyfriend", "girlfriend", "husband", "wife",
"ex", "mother-in-law", "father-in-law",
"stepmom", "stepdad", "sibling", "sister", "brother",
"best friend", "roommate", "coworker", "boss"
],
"drama_markers": [
"update", "plot twist", "turns out", "found out",
"caught", "evidence", "proof", "messages",
"dna test", "paternity", "surprise"
]
}
3.3 戏剧性评分算法
def calculate_drama_score(post):
"""
综合戏剧性评分 (0-100)
"""
score = 0
# 争议度权重 (25%)
controversy = controversy_score(post.ups, post.downs)
score += controversy * 25
# 互动密度权重 (20%)
engagement = min(post.num_comments / 1000, 1.0)
score += engagement * 20
# 关键词匹配 (30%)
text = f"{post.title} {post.selftext}".lower()
keyword_matches = sum(1 for kw in all_keywords if kw in text)
score += min(keyword_matches / 5, 1.0) * 30
# 更新帖加权 (15%)
if any(marker in post.title.lower() for marker in ["update", "final update"]):
score += 15
# 长度适宜度 (10%) - 太短没内容,太长没人看
word_count = len(post.selftext.split())
if 200 <= word_count <= 2000:
score += 10
elif 100 <= word_count < 200:
score += 5
return min(score, 100)
3.4 AITA 特定指标
针对 r/AmITheAsshole 的特别指标:
| 指标 | 识别方式 | 权重 |
|---|---|---|
| 判定结果 | 标题中的 NTA/YTA/ESH/NAH/INFO | 高 |
| 投票分布 | top_comment 的标签统计 | 中 |
| 情节复杂度 | 人物数量、时间跨度、冲突层数 | 中 |
| 道德困境 | 两难选择描述、价值冲突 | 高 |
4. 短剧素材提取维度
4.1 场景设定 (Scene Setting)
提取字段:
{
"scene_setting": {
"primary_location": "主要发生地点",
"time_context": "时间背景(节日、人生阶段)",
"social_context": "社交背景(婚礼、葬礼、聚会)",
"emotional_atmosphere": "情绪氛围(紧张、尴尬、欢乐)",
"visual_elements": ["可视觉化的关键物件/场景"],
"budget_friendly": "是否适合低成本拍摄"
}
}
常见 AITA 场景:
- 家庭聚会/节日聚餐
- 婚礼/订婚仪式
- 住房相关(室友、房产继承)
- 职场冲突
- 育儿相关
- 医疗费用相关
4.2 人物关系 (Character Relationships)
提取字段:
{
"characters": [
{
"role": "角色身份(OP/当事人/旁观者)",
"relation_to_op": "与主角的关系",
"traits": ["性格特征"],
"agenda": "人物目标/动机",
"arc": "人物弧线(如果有)"
}
],
"relationship_web": {
"central_conflict": "核心冲突关系",
"alliances": ["联盟关系"],
"tensions": ["紧张关系"]
}
}
典型人物配置:
- OP (Original Poster): 故事叙述者,通常是第一视角
- Antagonist: 对立角色(室友、前任、亲戚)
- Enabler/Ally: 支持方(伴侣、朋友)
- Mediator: 调解者(很少出现,但可增加戏剧张力)
4.3 冲突核心 (Core Conflict)
提取字段:
{
"conflict": {
"type": "冲突类型",
"inciting_incident": "导火索事件",
"stakes": "利害关系/后果",
"moral_dilemma": "道德困境描述",
"escalation": "冲突升级过程",
"climax": "高潮点"
}
}
冲突类型分类: | 类型 | 描述 | 示例 | |------|------|------| | 资源争夺 | 金钱、房产、物品归属 | 遗产分配、房租分摊 | | 边界侵犯 | 个人空间/时间被侵占 | 未经同意进入房间 | | 价值观冲突 | 道德观念分歧 | 育儿方式、生活方式 | | 信任破裂 | 欺骗、背叛 | 出轨、隐瞒 | | 角色期望 | 对某人应有行为的期待 | 伴娘义务、父母责任 |
4.4 精彩对话 (Dialogue Highlights)
提取字段:
{
"dialogue": {
"quotable_lines": ["金句/爆点台词"],
"confrontation_exchanges": ["对峙对话片段"],
"emotional_peaks": ["情绪高潮对话"],
"revelation_moments": ["真相揭示时刻"]
}
}
提取规则:
- 直接引用原文的引号内容
- 识别
"You are the asshole"等判定性语句 - 提取评论区的热门引用/模仿
- 记录讽刺、双关、反转语句
4.5 情感转折 (Emotional Turning Points)
提取字段:
{
"emotional_arc": {
"opening_hook": "开篇钩子",
"setup_emotion": "初始情绪状态",
"complications": ["复杂化节点"],
"turning_points": [
{
"trigger": "触发事件",
"emotion_shift": "情绪变化",
"narrative_impact": "叙事影响"
}
],
"resolution_type": "结局类型(和解/决裂/开放)",
"reader_catharsis": "读者情感宣泄点"
}
}
转折类型:
- Revelation (揭示): 发现隐藏信息
- Reversal (反转): 立场/形势逆转
- Escalation (升级): 冲突白热化
- Climax (高潮): 决定性时刻
- Resolution (解决): 最终结局
4.6 AITA 特有维度
{
"aita_specific": {
"judgment": "最终判定 (NTA/YTA/ESH/NAH/INFO)",
"top_comment_consensus": "热评共识",
"vote_distribution": {
"nta_percentage": "NTA 比例",
"yta_percentage": "YTA 比例"
},
"moral_ambiguity": "道德模糊程度",
"entertainment_value": "娱乐价值评分",
"adaptation_potential": "改编潜力评分"
}
}
5. 输出格式规范
5.1 标准化 JSON Schema
{
"metadata": {
"source": "reddit",
"subreddit": "AmITheAsshole",
"scraped_at": "2026-03-25T10:30:00Z",
"scraper_version": "1.0.0"
},
"post": {
"id": "t3_abc123",
"title": "原始标题",
"author": "用户名",
"created_utc": 1711300000,
"url": "https://reddit.com/r/...",
"permalink": "/r/AmITheAsshole/comments/...",
"selftext": "正文内容",
"score": 15420,
"upvote_ratio": 0.87,
"num_comments": 3400,
"is_original_content": true,
"link_flair_text": "判定标签",
"edited": false
},
"analytics": {
"drama_score": 78.5,
"controversy_score": 0.35,
"engagement_tier": "elite",
"keyword_matches": ["冲突", "背叛"],
"sentiment": "negative_intense"
},
"drama_elements": {
"scene_setting": { },
"characters": [ ],
"conflict": { },
"dialogue": { },
"emotional_arc": { },
"aita_specific": { }
},
"adaptation_notes": {
"logline": "一句话故事线",
"target_duration": "3-5分钟",
"genre_tags": ["家庭伦理", "反转"],
"production_difficulty": "低",
"similar_works": ["参考作品"]
}
}
5.2 数据源分类
Elite 数据源 (直接用于创作):
- r/AmITheAsshole Top All Time (score > 10k)
- r/relationship_advice Top Month (score > 5k)
- 带 "UPDATE" 标签的热门帖
Average 数据源 (训练/参考):
- r/AmITheAsshole Top Week (score 1k-10k)
- r/funny 高互动帖
- 有争议判定 (upvote_ratio 0.5-0.7) 的帖子
Broad 数据源 (趋势分析):
- 实时 Rising 内容
- 每日新增帖子的元数据分析
6. 技术实现建议
6.1 Python 代码示例
import requests
import json
import time
from datetime import datetime, timedelta
from typing import List, Dict, Optional
class RedditScraper:
def __init__(self, user_agent: str = "DramaScraper/1.0"):
self.headers = {
"User-Agent": user_agent
}
self.base_url = "https://www.reddit.com"
def fetch_posts(
self,
subreddit: str,
sort: str = "hot",
time_period: Optional[str] = None,
limit: int = 100,
after: Optional[str] = None
) -> List[Dict]:
"""
获取帖子列表
"""
url = f"{self.base_url}/r/{subreddit}/{sort}.json"
params = {"limit": min(limit, 100)}
if time_period and sort in ["top", "controversial"]:
params["t"] = time_period
if after:
params["after"] = after
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
data = response.json()
posts = data["data"]["children"]
return [self._extract_post_data(post["data"]) for post in posts]
def _extract_post_data(self, post: Dict) -> Dict:
"""
提取关键字段
"""
return {
"id": post.get("id"),
"title": post.get("title"),
"author": post.get("author"),
"created_utc": post.get("created_utc"),
"url": post.get("url"),
"permalink": post.get("permalink"),
"selftext": post.get("selftext", ""),
"score": post.get("score"),
"ups": post.get("ups"),
"downs": post.get("downs"),
"upvote_ratio": post.get("upvote_ratio"),
"num_comments": post.get("num_comments"),
"is_original_content": post.get("is_original_content"),
"link_flair_text": post.get("link_flair_text"),
"edited": post.get("edited"),
"awards": post.get("total_awards_received", 0)
}
def calculate_drama_score(self, post: Dict) -> float:
"""
计算戏剧性得分
"""
score = 0.0
# 争议度 (25%)
upvote_ratio = post.get("upvote_ratio", 1.0)
controversy = 1 - abs(upvote_ratio - 0.5) * 2
score += controversy * 25
# 互动密度 (20%)
comments = post.get("num_comments", 0)
score += min(comments / 2000, 1.0) * 20
# 关键词匹配 (30%)
title = post.get("title", "").lower()
text = post.get("selftext", "").lower()
full_text = f"{title} {text}"
drama_keywords = [
"aita", "asshole", "cheated", "betrayed", "divorce",
"wedding", "inheritance", "secret", "lied", "update",
"plot twist", "turns out", "found out", "caught"
]
matches = sum(1 for kw in drama_keywords if kw in full_text)
score += min(matches / 5, 1.0) * 30
# 更新帖加权 (15%)
if "update" in title:
score += 15
# 长度适宜度 (10%)
word_count = len(text.split())
if 200 <= word_count <= 2000:
score += 10
elif 100 <= word_count < 200:
score += 5
return min(score, 100)
def fetch_with_pagination(
self,
subreddit: str,
target_count: int = 1000,
**kwargs
) -> List[Dict]:
"""
分页获取大量帖子
"""
all_posts = []
after = None
while len(all_posts) < target_count:
posts = self.fetch_posts(
subreddit=subreddit,
limit=100,
after=after,
**kwargs
)
if not posts:
break
all_posts.extend(posts)
after = f"t3_{posts[-1]['id']}"
# 礼貌延迟
time.sleep(2)
return all_posts[:target_count]
6.2 反爬策略
# 请求间隔
MIN_DELAY = 2.0 # 秒
MAX_DELAY = 5.0
# User-Agent 轮换
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
]
# 失败重试
MAX_RETRIES = 3
RETRY_DELAY = 30
7. 数据源 URL 模板
7.1 目标子版块
r/AmITheAsshole - 道德困境、人际冲突
r/relationship_advice - 恋爱关系、情感问题
r/funny - 轻松幽默、生活趣事
7.2 API 端点模板
URL_TEMPLATES = {
# JSON 端点
"json_posts": "https://www.reddit.com/r/{subreddit}/{sort}.json",
"json_comments": "https://www.reddit.com/r/{subreddit}/comments/{post_id}.json",
# Old Reddit
"old_reddit_post": "https://old.reddit.com{permalink}",
"old_reddit_comments": "https://old.reddit.com/r/{subreddit}/comments/{post_id}/?limit=500",
# 网页版
"web_post": "https://www.reddit.com{permalink}",
"web_subreddit": "https://www.reddit.com/r/{subreddit}/{sort}",
# 特定筛选
"top_all_time": "https://www.reddit.com/r/{subreddit}/top.json?t=all&limit=100",
"top_year": "https://www.reddit.com/r/{subreddit}/top.json?t=year&limit=100",
"top_month": "https://www.reddit.com/r/{subreddit}/top.json?t=month&limit=100",
"controversial_week": "https://www.reddit.com/r/{subreddit}/controversial.json?t=week&limit=100",
"rising": "https://www.reddit.com/r/{subreddit}/rising.json?limit=100"
}
7.3 字段映射表
| JSON 字段 | 内部名称 | 数据类型 | 描述 |
|---|---|---|---|
id |
post_id |
string | 帖子唯一ID |
title |
title |
string | 标题 |
author |
author |
string | 作者用户名 |
created_utc |
created_at |
int | UTC时间戳 |
selftext |
content |
string | 正文内容 |
score |
score |
int | 净得分(up-down) |
ups |
upvotes |
int | 点赞数 |
downs |
downvotes |
int | 点踩数(通常不显示) |
upvote_ratio |
upvote_ratio |
float | 点赞比例 |
num_comments |
comment_count |
int | 评论数 |
permalink |
permalink |
string | 相对链接 |
url |
url |
string | 外部链接 |
is_original_content |
is_oc |
bool | 是否原创 |
link_flair_text |
flair |
string | 标签/分类 |
edited |
is_edited |
bool/int | 是否编辑过 |
total_awards_received |
awards |
int | 获奖数 |
num_crossposts |
crosspost_count |
int | 转发数 |
8. 数据质量与验证
8.1 数据清洗规则
CLEANING_RULES = {
# 移除内容
"remove_deleted": True, # 删除 [deleted] 作者的内容
"remove_removed": True, # 删除 [removed] 内容
"remove_short": 50, # 移除少于50字的帖子
"remove_low_effort": True, # 移除低质量内容
# 文本处理
"normalize_whitespace": True,
"remove_tracking_params": True, # 移除URL跟踪参数
"decode_html_entities": True,
}
8.2 多源验证策略
| 验证项 | 方法 | 通过标准 |
|---|---|---|
| 数据完整性 | 字段非空检查 | 核心字段100%填充 |
| 时间一致性 | 时间戳范围验证 | 不超过当前时间 |
| 数值合理性 | 范围检查 | score >= 0, ratio 0-1 |
| 重复检测 | ID去重 | 无重复记录 |
| 内容新鲜度 | 时间戳检查 | 根据需求设定阈值 |
9. 参考资源
9.1 数据来源
- Reddit Data API Wiki - 官方 API 文档
- PRAW Documentation - Python Reddit API Wrapper
- Reddit Algorithm Explained - 算法解析
- Reddit Scraping Guide 2025 - 抓取技术指南
9.2 学术参考
- Salihefendic, A. (2010). "How Reddit's Algorithm Works"
- ArXiv 2411.11581 - OASIS: Open Agent Social Interaction Simulations
- ArXiv 2503.03500 - TDA-Based Controversy Detection in Reddit Data
10. 总结与建议
推荐技术栈
首选方案: JSON 端点 + 定时轮询
- 开发成本低
- 无需认证
- 足够满足大部分需求
备选方案: PRAW API
- 需要写操作时启用
- 更高的合规性保障
深度分析: Old Reddit 爬取
- 评论树完整抓取
- 配合代理池使用
核心筛选指标优先级
- upvote_ratio (0.3-0.7) - 识别争议内容
- num_comments (>1000) - 高互动 = 高戏剧性潜力
- keyword_matches - 情绪/冲突关键词
- update_flag - 更新帖通常更有故事性
- score (>5000) - 大众认可度
下一步行动
- 实现基础爬虫 (JSON 端点)
- 建立数据库存储结构
- 开发 drama_score 评分算法
- 构建 AITA 特定的字段提取器
- 测试小规模数据抓取并验证质量
报告生成时间: 2026-03-25 置信度: 高 版本: 1.0