SurfSense Reddit 专业子代理:结构化帖子、评论与社区舆情数据的实时抓取与对比分析
2026/9/15 16:53:57 网站建设 项目流程

SurfSense Reddit 专业子代理:结构化帖子、评论与社区舆情数据的实时抓取与对比分析

【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense

导读

SurfSense 的多智能体聊天系统内置了多位"专业子代理"(builtin sub-agent),其中reddit子代理专门负责从 Reddit 抓取结构化数据:帖子(标题、正文、评分、点赞率、评论数、所属 subreddit、作者、flair、时间戳)、评论线程以及社区/用户元数据。它既支持按关键词搜索发现讨论(可选限定单一 subreddit),也支持直接抓取已知的帖子、subreddit 或用户 URL,并提供排序与时间窗口控制,还能把当前抓取结果与本次对话中早前获得的 Reddit 结果做对比。读完本文,你将掌握该子代理的触发条件、三种数据获取路径、全部抓取参数、底层源码调用链以及输出契约。

定位与职责边界:Reddit 讨论的专属入口

根据 description.md 的定义,这个子代理的职责非常聚焦——"pulls structured Reddit data",即拉取结构化的 Reddit 数据,包括:

  • 帖子(posts):标题、正文、score(评分)、upvote ratio(点赞率)、评论数、subreddit、作者、flair、时间戳;
  • 评论线程(comment threads):每条帖子的评论树;
  • 社区与用户元数据(community/user metadata):subreddit 与用户维度的信息。

与之配套的 system_prompt.md 明确说明了它何时被调用:只要任务是了解"人们在 Reddit 上对一个话题说了什么",包括从某个 subreddit 或用户收集帖子/评论、发现讨论线程、读取社区情绪(community sentiment),都交给它。典型触发语包括:

  • "search Reddit for X"(在 Reddit 搜索 X)
  • "what does r/X say about Y"(r/X 社区对 Y 怎么看)
  • "find Reddit posts/threads about X"(查找关于 X 的 Reddit 帖子/线程)
  • "top posts in r/X"(r/X 的置顶热帖)
  • 以及对比本对话中早前的 Reddit 结果

同时,它被明确限制为只管 Reddit

  • 普通网页内容 → 交给 web 爬虫子代理(web crawling specialist);
  • Google 搜索结果 → 交给 Google Search 子代理;
  • YouTube 内容 → 交给 YouTube 子代理。

这种职责划分保证了 supervisor 代理在委派任务时不会出现能力重叠,也让每个子代理的提示词(prompt)可以高度专业化。

三种数据获取路径:搜索、URL 直抓与社区浏览

从 schemas.py 的ScrapeInput可以看到,reddit子代理暴露了三种互不排斥的数据获取方式(三者至少提供其一,校验逻辑见_require_a_source模型校验器):

  1. 关键词搜索(search_queries:运行 Reddit 站内搜索,每个查询最多返回max_items条结果;配合community参数可把搜索限定到单一 subreddit(例如"python",不带r/前缀)。
  2. URL 直抓(urls:直接抓取一个帖子 URL、subreddit URL(/r/<name>)、用户 URL(/user/<name>)甚至 Reddit 搜索 URL,原样作为抓取目标。
  3. 社区浏览(community单独使用):只传community而不传search_queries时,抓取该 subreddit 的帖子列表(listing)。

单次调用的来源上限为MAX_REDDIT_SOURCES = 20(即urlssearch_queries总数不超过 20),以约束同步请求的扇出规模;单次返回的条目硬上限为MAX_REDDIT_ITEMS = 100max_itemsle=100约束)。

抓取参数全解析

下表汇总了ScrapeInput的全部字段及其语义(来自 schemas.py):

参数类型/默认值说明
urlslist[HttpUrlStr],默认[]要抓取的 Reddit URL:帖子、/r/<name>/user/<name>或搜索 URL。与search_queries/community至少提供其一
search_querieslist[str],默认[]在 Reddit 上运行的搜索词,每个查询最多返回max_items条结果;可用community限定 subreddit
communitystr \| None,默认Nonesubreddit 名称(不带r/),用于限定search_queries;不传search_queries时抓取其列表
sortRedditSort,默认"new"结果排序:relevancehottopnewrisingcomments
time_filterRedditTime \| None,默认None仅对top/controversial排序生效的时间窗口:hourdayweekmonthyearall
include_nsfwbool,默认True是否包含标记为 18+(NSFW)的帖子
skip_commentsbool,默认False跳过评论树抓取(更快,只取帖子/列表)
max_itemsint,默认10,范围1~100所有来源合计返回的最大条目总数
max_postsint,默认10,范围>=0每个 subreddit/用户/搜索目标最多抓取的帖子数
max_commentsint,默认10,范围>=0每篇帖子最多抓取的评论数(0= 不抓评论)
post_date_limitstr \| None,默认NoneISO 日期;只返回晚于该日期的帖子(用于增量抓取)
comment_date_limitstr \| None,默认NoneISO 日期;只返回晚于该日期的评论(用于增量抓取)

值得注意的是max_items的默认值只有 10,而它是跨所有来源的硬性总上限。如果任务是"找出 N 篇帖子",就必须把max_itemsmax_posts都调到大于 N 的值(并预留出离题命中的余量),否则"上限小于目标"的调用永远不可能满足任务要求。estimated_units属性把它作为预检(pre-flight)计费闸门的最大可计费条目数返回。

排序与时间窗口:控制结果的口径

sorttime_filter是控制抓取口径的关键参数:

  • sort的六个取值RedditSort枚举):relevance(相关度)、hot(热门)、top(最高分)、new(最新)、rising(上升中)、comments(评论最多)。默认是new
  • time_filter的六个窗口RedditTime枚举):hourdayweekmonthyearall。注意它只对topcontroversial排序有意义——这就是为什么 system_prompt.md 里的 playbook 会说"抓取某个 subreddit 的列表时,用sort(hot/top/new/rising)并配合time_filter取 top/controversial"。

playbook 还给出了一条重要的检索经验:主题发现要用宽查询而非精确短语。当目标是"发现求 X 推荐/替代方案的帖子"时,应该用不带引号的宽泛查询加上多种措辞(例如"X alternative""alternative to X""app like X"),并使用sort=relevance;而带引号的精确短语与sort=new是"精度工具",会漏掉绝大多数匹配。这直接呼应了sort参数中relevancenew的定位差异。

评论阅读与体积控制

评论是 Reddit 舆情分析的核心。子代理在读取评论情绪时有明确的策略:

  • 需要评论时:保持skip_commentsfalse(默认),并调高max_comments
  • 只要帖子、追求速度时:把skip_comments设为true
  • 控制总量时:用max_items做总上限、max_posts控制每个目标的帖子数、max_comments控制每篇帖子的评论数。

由于每个返回条目都对应一个计费单位(见下文 billing 部分),体积控制同时也是成本控制——这正是estimated_unitsbillable_units两个属性存在的意义。

与早前结果的对比分析

description.md特别强调该子代理"compares fresh Reddit results against earlier findings in this chat"。这一能力在 system_prompt.md 中被实现为对比 playbook:

  1. 拉取当前结果;
  2. 与本次对话中早前工具调用已返回的 Reddit 结果对照;
  3. 报告具体的增量(delta):新增(added)、移除(removed)、评分/排名变化(score/rank changes)

这类对比需求来自诸如"和上次相比有什么变化""哪些帖子是新的""热度走势如何"之类的委派任务。需要说明的是,该机制依赖的是同一对话上下文中早前工具结果的留存(run reader 可读取已存储的抓取输出),因此对比的"基准线"就是本对话历史中已出现的 Reddit 结果。

源码级实现:从子代理构建到能力执行

子代理的装配

agent.py 中的build_subagent()展示了内置子代理的统一装配模式:

  • 通过load_tools()加载工具,并可与外部 MCP 工具合并;
  • 通过read_md_file(__package__, "description")读取本目录的description.md作为子代理的路由描述(供 supervisor 判断何时委派);
  • 通过read_md_file(__package__, "system_prompt")读取system_prompt.md作为系统提示词;
  • 最终用pack_subagent(...)打包成SurfSenseSubagentSpec交给 deepagents 框架运行。

也就是说,description.mdsystem_prompt.md不只是文档,它们分别是 supervisor 的路由决策依据和子代理的行为准则,属于运行时代码的一部分——这就是本仓库"以文档驱动子代理行为"的设计。

工具与权限

tools/index.py 定义了NAME = "reddit"RULESET = Ruleset(origin=NAME, rules=[]),并通过build_capability_tools()REDDIT_SCRAPE这一个能力动词(_CI_VERBS)暴露为 LangChain 工具。当前rules为空,即该子代理不附加额外的权限规则,只依赖能力注册系统。

能力的注册与计费

definition.py 将reddit.scrape注册为一个正式能力(Capability):

  • 输入/输出模式分别为ScrapeInput/ScrapeOutput
  • 执行器由build_scrape_executor()构建;
  • 计费单位为BillingUnit.REDDIT_ITEM,按抓取条目数计费(配置项REDDIT_SCRAPE_MICROS_PER_ITEM控制每条的微信用额度);
  • docs_url指向/docs/connectors/native/reddit

ScrapeOutput.billable_units明确"一个返回条目 = 一个计费单位"(len(self.items))。

执行器与错误处理

executor.py 中的execute()是参数映射的核心:它把ScrapeInput逐字段映射到底层专有爬虫的RedditScrapeInputstartUrlssearchessearchCommunityNamesorttimeincludeNSFWskipCommentsmaxItemsmaxPostCountmaxCommentspostDateLimitcommentDateLimit),调用专有平台的scrape_reddit,并在执行前后通过emit_progress发出进度事件("Resolving Reddit targets" → "Scraped N item(s)"),供前端实时展示。

错误处理上有一个值得注意的设计:Reddit 是仅匿名访问的爬虫,一旦被拒绝访问抛出RedditAccessBlockedError,由于没有凭据可重试,执行器会将其映射为ForbiddenError(错误码REDDIT_ACCESS_BLOCKED),与 google_maps 能力的SignInRequiredError → ForbiddenError映射方式保持一致,让上层 API 层可以统一处理权限类错误。

输出契约与失败策略

system_prompt.md 为子代理规定了严格的输出契约(output contract),要求只返回一个 JSON 对象,字段包括:

{ "status": "success" | "partial" | "blocked" | "error", "action_summary": "string", "evidence": { "findings": ["每条发现一句话,最多10条"], "sources": ["每个发现对应的 Reddit URL,每个URL只列一次"], "confidence": "high" | "medium" | "low" }, "next_step": "string | null", "missing_fields": "string[] | null", "assumptions": "string[] | null" }

其中findings按"每条帖子/评论/增量一条"组织,禁止粘贴原始载荷,且必须由真实抓取结果支撑、绝不补位;sourcesfindings同数量上限,每个 Reddit URL 只列出一次。

对应的失败策略(failure policy)同样严格:

  • 请求信息不足(没有可用的查询词、community 或 URL)→ 返回status=blocked并列出缺失字段;
  • 工具失败→ 返回status=error并附简洁的恢复建议next_step
  • 没有可用证据→ 返回status=blocked,同时给出更窄的查询或仍需的抓取范围。

此外,安全策略要求:证据不完整或互相冲突时明确报告不确定性,绝不把未经验证的说法当作事实陈述;工具策略要求只使用可用工具列表内的工具,且只报告工具输出中实际存在的结果——严禁编造标题、URL、分数、作者或评论文本。这种"宁可 blocked 也不虚构"的约束,保证了 supervisor 合成最终答案时的证据可信度。

实战要点速查

  1. 发现讨论:用search_queries,需要限定社区时加community(如"python");做主题发现用宽查询 +sort=relevance的多种措辞。
  2. 抓取社区列表:只传community,调sort(hot/top/new/rising),需要历史窗口时配time_filter
  3. 抓取已知目标:把帖子/subreddit/用户 URL 放进urls直抓。
  4. 读评论情绪:保持skip_comments=false并调高max_comments;只要帖子就把skip_comments=true提速。
  5. 控制体积与成本max_items是跨来源硬上限(默认仅 10),按目标数任务需同时调大max_itemsmax_posts;每个返回条目计费一个REDDIT_ITEM单位。
  6. 对比增量:对比基准来自同一对话早前 Reddit 工具结果,报告 added/removed/score 变化。
  7. 遵守边界:网页内容走 web 爬虫子代理、Google 结果走 Google Search 子代理、YouTube 走 YouTube 子代理,reddit子代理只处理 Reddit 本身。

延伸阅读

  • 子代理路由描述与系统提示词:description.md、system_prompt.md
  • 子代理装配实现:agent.py、tools/index.py
  • 能力注册与 I/O 契约:definition.py、schemas.py
  • 执行器与错误映射:executor.py

【免费下载链接】SurfSenseOpen-source NotebookLM alternative. Research the open web with live data(Reddit, YT, IG, TikTok, Indeed, Google Search, Maps etc) through one platform, API or MCP server. Join our Discord: https://discord.gg/ejRNvftDp9项目地址: https://gitcode.com/GitHub_Trending/su/SurfSense

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询