knowhow_raw.md 22.2 KB

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 推荐方案

混合策略:

  1. 主要使用 JSON 端点 - 快速获取帖子列表和基础数据
  2. Old Reddit 补充 - 当需要深度评论树时使用
  3. PRAW 作为备选 - 当需要写操作或遇到限制时切换

2. 高赞帖子筛选策略

2.1 Reddit Hot 算法解析

核心公式 (Amir Salihefendic, 2010):

h = log₁₀(max(|u-d|, 1)) + sign(u-d) × (t - t₀)/45000

参数:

  • h = Hot score
  • u = 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": "读者情感宣泄点"
  }
}

转折类型:

  1. Revelation (揭示): 发现隐藏信息
  2. Reversal (反转): 立场/形势逆转
  3. Escalation (升级): 冲突白热化
  4. Climax (高潮): 决定性时刻
  5. 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 数据来源

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. 总结与建议

推荐技术栈

  1. 首选方案: JSON 端点 + 定时轮询

    • 开发成本低
    • 无需认证
    • 足够满足大部分需求
  2. 备选方案: PRAW API

    • 需要写操作时启用
    • 更高的合规性保障
  3. 深度分析: Old Reddit 爬取

    • 评论树完整抓取
    • 配合代理池使用

核心筛选指标优先级

  1. upvote_ratio (0.3-0.7) - 识别争议内容
  2. num_comments (>1000) - 高互动 = 高戏剧性潜力
  3. keyword_matches - 情绪/冲突关键词
  4. update_flag - 更新帖通常更有故事性
  5. score (>5000) - 大众认可度

下一步行动

  1. 实现基础爬虫 (JSON 端点)
  2. 建立数据库存储结构
  3. 开发 drama_score 评分算法
  4. 构建 AITA 特定的字段提取器
  5. 测试小规模数据抓取并验证质量

报告生成时间: 2026-03-25 置信度: 高 版本: 1.0