资讯动态

SurfSense Indeed 职位子代理深度解析:多智能体架构下的实时职位抓取与结构化输出

发布时间:2026/9/15 20:29:00 来源:尧图企业网站定制
SurfSense Indeed 职位子代理深度解析多智能体架构下的实时职位抓取与结构化输出【免费下载链接】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 开源仓库中的 Indeed 子代理说明文档 展开剖析这个负责从 Indeed 实时采集职位数据的专用子代理它如何在多智能体聊天系统中被注册、通过indeed.scrape能力动词完成抓取、如何把搜索结果/公司页/单职位 URL 归一化为结构化职位条目以及其系统提示词中内置的成本模型与输出契约。读完本文你将掌握该子代理的触发边界、全部搜索过滤参数、底层抓取编排原理以及如何通过公开接口复现它的能力。一、子代理定位多智能体聊天中的 Indeed 专家在 SurfSense 的后端中聊天系统采用监督代理supervisor 子代理subagent的协作架构。每个子代理都是一个SurfSenseSubagentSpec由 agent.py 中的build_subagent()函数构建。其中值得注意的实现细节是该子代理的描述description与系统提示词system prompt直接以 Markdown 文件形式存放通过read_md_file(__package__, description)与read_md_file(__package__, system_prompt)加载并以加载内容作为回退兜底这正是仓库把 description.md 作为子代理说明书的原因——模型路由依赖它来决定何时委派任务。从 description.md 原文可知它的职责被精确定义为Indeed jobs specialist: pulls structured job postings — title, company, location, salary (range, currency, period), job types, benefits, remote/hybrid flag, posting age, apply URL, and the full job description.即提取的字段涵盖职位标题、公司、地点、薪资范围、币种、计薪周期、职位类型、福利、远程/混合办公标记、发布时间、申请 URL以及完整的职位描述。这些字段与底层数据模型IndeedItem一一对应详见第五节。1.1 触发与拒绝条件说明文档同时划定了明确的职责边界应该触发任何寻找或收集 Indeed 职位列表的任务例如找 X 的职位、谁在招 X、Y 地点的数据分析师职位、X 领域的远程职位以及抓取某个具体的 Indeed URL。不该触发普通网页抓取应交给 web crawling specialist、Google 搜索结果应交给 Google Search specialist、以及其他招聘网站超出本子代理范围。这套边界在 system_prompt.md 的out_of_scope段落中再次被强制约束并从构建侧落地为实际的工具白名单——子代理的工具集由 tools/index.py 加载仅包含INDEED_SCRAPE这一个能力动词外加只读的read_run/search_run两个结果读取工具。二、能力注册indeed.scrape 动词与按职位计费子代理本身不直接写抓取逻辑而是通过能力capability机制调用底层实现。indeed.scrape在 definition.py 中注册INDEED_SCRAPE Capability( nameindeed.scrape, description( Scrape public Indeed job postings, including title, company, location, salary, and description. Use urls or search_queries. ), input_schemaScrapeInput, output_schemaScrapeOutput, executorbuild_scrape_executor(), billing_unitBillingUnit.INDEED_JOB, docs_url/docs/connectors/native/indeed, )这里有三点值得注意的架构事实输入输出用 Pydantic 契约ScrapeInput/ScrapeOutput定义在 schemas.py成为面向 Agent 的轻量接口而真正的抓取输入模型IndeedScrapeInput位于专有实现层app/proprietary/platforms/indeed_jobs。按职位计费billing_unitBillingUnit.INDEED_JOB即每个返回的职位计一个计费单位费率由配置项INDEED_SCRAPE_MICROS_PER_JOB控制见 definition.py 模块 docstring。ScrapeOutput.billable_units直接返回len(self.items)即一个职位 一个计费单位。执行器可注入build_scrape_executor()默认绑定专有的scrape_indeed但也接受外部scrape_fn注入便于测试替身。三、输入契约搜索过滤参数的完整说明indeed.scrape的输入由 schemas.py 中的ScrapeInput定义是 Agent 需要掌握的核心参数面。下表完整罗列默认值取自源码Field定义参数类型默认值说明urlslist[HttpUrlStr][]直接抓取的 Indeed URL搜索页/jobs?ql、公司职位页/cmp/slug/jobs或单职位页/viewjobsearch_querieslist[str][]职位搜索词每个查询返回最多max_items_per_query条结果countrystrus国家代码决定 Indeed 域名如us、gb、delocationstr \| NoneNone搜索地点如Remote、New York, NYradiusint \| NoneNone以地点为中心的搜索半径英里/公里job_typeIndeedJobType \| NoneNone雇佣类型过滤见下方枚举levelIndeedLevel \| NoneNone经验级别过滤entry_level、mid_level、senior_levelremoteIndeedRemote \| NoneNone工作模式过滤remote或hybridfrom_daysint \| NoneNone仅返回最近 N 天内发布的职位sortIndeedSortrelevance排序方式relevance相关度或date按日期scrape_job_detailsboolFalse是否抓取每个职位的详情页以获取完整描述更慢每个职位多一次页面加载max_itemsint25所有来源合计返回的最大职位数范围 1–100max_items_per_queryint25每个搜索/公司目标最多拉取的职位数范围 ≥0job_type的取值在底层 schemas.py 中定义为 8 种字面量fulltime、parttime、contract、internship、temporary、permanent、seasonal、freelance。契约还内置了两条硬性约束model_validator与Field校验至少提供一个来源urls与search_queries不能同时为空否则直接校验失败Provide at least one of urls or search_queries.。扇出上限MAX_INDEED_SOURCES 20即单次调用urls search_queries总数不超过 20用于限制同步请求的扇出MAX_INDEED_ITEMS 100是单次调用返回职位的硬顶。另外ScrapeInput.estimated_units返回max_items作为预检门禁中最坏情况的计费职位数——由于max_items ≤ 100任何单次调用都不可能超过该硬顶。四、执行器输入映射、进度事件与访问被拒处理executor.py 把能力契约翻译成专有抓取器的输入actor_input IndeedScrapeInput( startUrls[{url: url} for url in payload.urls], queriespayload.search_queries, countrypayload.country, locationpayload.location, radiuspayload.radius, jobTypepayload.job_type, levelpayload.level, remotepayload.remote, fromDayspayload.from_days, sortpayload.sort, scrapeJobDetailspayload.scrape_job_details, maxItemspayload.max_items, maxItemsPerQuerypayload.max_items_per_query, )随后调用scrape_fn(actor_input, limitpayload.max_items)并围绕它做了两件事进度事件启动时emit_progress(starting, ..., totalpayload.max_items, unitjob)完成时emit_progress(done, fScraped {len(items)} job(s), ...)让上层聊天界面能实时看到正在解析目标 / 已抓取 N 个职位的状态。异常归一化专有抓取器抛出的IndeedAccessBlockedError被转换为ForbiddenError错误码INDEED_ACCESS_BLOCKED。原因是该抓取器是仅匿名访问的实现没有认证字段一旦 Indeed 拒绝匿名访问便无法用凭据重试只能以权限错误形式上报见 executor.py。五、输出契约IndeedItem 的结构化职位字段抓取结果以ScrapeOutput返回内部元素直接复用专有层的IndeedItem模型定义于 schemas.py这是说明文档中结构化职位发布structured job postings的落地实现。主要字段分六组身份与申请jobKey职位唯一键用于跨目标去重title职位标题jobUrl/applyUrl职位页与申请 URL公司与评价company/companyUrlcompanyRating/companyReviewCount公司在 Indeed 上的评分与评价数地点与办公模式formattedLocation/city/state/postalCode/countryisRemote/remoteType远程标记与远程类型职位属性jobTypes职位类型列表salary薪资块见下方Salarybenefits福利列表描述与状态descriptionText/descriptionHtml职位描述列表页可能缺失需详情抓取填充sponsored、isNew、urgentlyHiring、expired、indeedApplyEnabled赞助/新发布/急招/已过期/支持 Indeed Apply 等标记时间戳age发布年龄文本、datePublished、createdAt、scrapedAt抓取时间Salary子模型结构如下字段在 Indeed 未披露薪资时保持Noneclass Salary(BaseModel): salaryText: str | None None # 原始薪资文本如 $80K - $100K a year salaryMin: float | None None # 最低值 salaryMax: float | None None # 最高值 currency: str | None None # 币种 period: SalaryPeriod | None None # hour | day | week | month | year isEstimated: bool | None None # 是否为估算值这正是说明文档中 salary (range, currency, period) 的精确对应。需要留意的是源自 schemas.py 的模块说明匿名抓取时列表页缺失的字段完整描述、福利会保持None/[]直到详情页抓取才被填充——这与下一节的详情补充机制直接相关。六、底层抓取编排会话复用、去重与每查询一页限制indeed.scrape最终由专有抓取器scrape_indeedscraper.py执行。这个编排器揭示了大量重要的实现事实也是 system_prompt.md 中成本模型论述的源码依据6.1 目标解析_targets若提供了startUrls优先处理 URL每个 URL 经 url_resolver.py 的resolve_url解析无法识别的 URL 会被跳过并记录 warning解析结果区分job类型/viewjob单职位与search类型搜索页/公司页。若只提供search_queries则由build_search_url依据country、location、radius、job_type、level、remote、from_days、sort拼装 Indeed 搜索 URL。6.2 会话复用与顺序执行scrape_indeed通过open_session()来自 fetch.py打开一个预热好的会话warmed session所有目标按顺序复用该会话。这是典型的成本优化Indeed 对匿名访问有 Cloudflare 防护冷启动会花费数分钟因此一次调用内的多个查询共享一个已过验证的会话远比多次冷启动划算。6.3 每查询第一页限制关键性能事实_search_items的注释明确写道ponytail: caps a query at its first page (~15 jobs) — anonymous Indeed gatesstart10; deeper depth needs an authenticated session or Indeeds API.即匿名模式下 Indeed 限制翻页start 10被门禁每个查询实际只能拿到第一页约 15 个职位。因此max_items_per_query设得再高也买不到更多深度。这解释了系统提示词中的忠告max_itemsabove ~15/query buys nothing每个查询超过约 15 个职位的设置没有意义。6.4 全局去重与详情补充iter_indeed维护一个跨所有目标的全局jobKey去重集合global_seen同一职位即使在不同查询中重复出现也只会输出一次单页内部同样有seen去重。当scrapeJobDetailsTrue时每个条目还会调用_enrich抓取其/viewjob详情页合并完整描述与福利等字段。_enrich是**尽力而为best-effort**的详情页被门禁或格式异常时只记录 warning不中断整个抓取且详情请求使用max_rotations0避免为了单个职位消耗过多代理 IP 资源。七、系统提示词Agent 侧的 playbook 与输出契约system_prompt.md 定义了子代理的行为准则其中最值得开发者借鉴的是**成本模型Cost model**部分Indeed 是延迟受限latency-bound场景冷会话要花数分钟通过 Cloudflare 验证。默认只发一个聚焦查询ONE focused query只有当角色确实需要多样性时才添加措辞变体且最多 2–3 个放进同一次调用的search_queries共享一个预热会话绝不拆成多次indeed_scrape调用——那会重复支付冷启动成本。优先把第一页的切题结果及时返回而非追求穷尽覆盖若单次调用无法满足较大的 N返回statuspartial并说明每查询约一页的上限不要追加更多调用。输出契约要求子代理只返回一个 JSON 对象不得夹带 Markdown 或散文结构如下{ status: success | partial | blocked | error, action_summary: string, evidence: { findings: [string], sources: [string], confidence: high | medium | low }, next_step: string | null, missing_fields: [string] | null, assumptions: [string] | null }路由专属规则包括evidence.findings中每个不同职位或差异占一条、每条一句话、不粘贴原始载荷最多 10 条除非委派任务明确要求 N 条evidence.sources对应每条发现给出一个 Indeed 职位 URL同样有上限且每个 URL 只出现一次。此外还有明确的失败策略请求含糊无可用查询或 URL时返回statusblocked并列出缺失字段工具失败返回statuserror并附简短恢复建议next_step无有效证据返回statusblocked并给出更窄的查询建议。八、实战要点与延伸阅读综合说明文档、系统提示词与源码实现使用 Indeed 子代理的实践要点可归纳为查询优先、URL 兜底优先用search_queries加过滤参数location、country、job_type、level、remote、radius、from_days、sort已知确切目标时直接用urls搜索页、公司职位页/cmp/slug/jobs或单职位/viewjoburls优先级高于查询。控制数量预期记住每查询约一页~15 条的匿名上限max_items的合理档位以 15 的整数倍估算超出部分需要更换查询词或改走认证/官方 API。按需开启详情抓取scrape_job_detailstrue会为每个职位多一次页面加载只在你确实需要完整描述和福利时才开启。一次调用承载全部变体多措辞变体合并进同一次调用的search_queries2–3 个共享预热会话避免重复冷启动。如果想继续深挖推荐按以下路径阅读当前仓库源码子代理装配见 agent.py 与 tools/index.py能力契约见 definition.py、schemas.py 与 executor.py底层抓取实现见 scraper.py 及其同目录下的 schemas.py、parsers.py、url_resolver.py端到端验证脚本见 e2e_indeed_scraper.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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价