
career-ops VC 组合种子扫描实战用--seeds把 YC/a16z 投资组合变成招聘职位发现源【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops导读本文讲解 career-ops 中seeds/模块的设计与使用它绕过「等公司在 ATS 目录里出现」的被动模式直接从 Y Combinator、a16z 等顶级 VC 的公开投资组合页拉取公司名单逐家探测其 Greenhouse / Lever / Ashby 招聘看板把结果汇入与portals.yml追踪公司完全相同的扫描管线。读完本文你将掌握--seeds命令行用法、程序化调用 API、数据转换与安全边界以及如何扩展新的 VC 组合数据源。为什么需要一套组合种子发现路径scan-ats-full.mjs的常规工作方式是反向扫描公开 ATS 目录遍历 Greenhouse、Lever、Ashby、Workday 等招聘板聚合目录从中发现正在招人的公司。这种方式的局限在于——一家公司只有在被目录收录之后才会进入视野。而seeds/层提供了面向初创公司求职者的高信号起点不等待公司出现在 ATS 目录里而是先从知名 VC 的公开投资组合划定候选宇宙。种子公司名单拉取后会被转换成与portals.yml追踪条目一致的PortalEntry再走与scan.mjs中追踪公司完全相同的探测流程因此一次扫描即可覆盖数百家 YC / a16z 被投公司的实时在招岗位。核心文档见 seeds/README.md实现位于 seeds/vc-portfolios.mjs。整体数据流如下VC portfolio API/page ↓ seeds/vc-portfolios.mjs SeedCompany[] ↓ toPortalEntry() PortalEntry (careers_url set to best-guess ATS URL) ↓ provider.detect() (same as portals.yml companies) ATS provider fetches jobs ↓ title_filter / location_filter / dedup data/pipeline.md核心实现拆解种子获取器 vc-portfolios.mjs实现文件顶部注释明确了三条硬性设计约束是理解整个模块的钥匙见 seeds/vc-portfolios.mjs零鉴权只用公开数据源不登录、不要 API key零 LLM 消耗纯 HTTP JSON / HTML 解析不消耗任何模型 token同一套 slug 守卫凡是会拼进 URL 的 slug 都经过SLUG_RE校验伪造或畸形的 payload 永远无法向 URL 注入意外字符。数据类型SeedCompany 与 SeedPortalEntry两个用 JSDoc 声明的核心类型seeds/vc-portfolios.mjsSeedCompany种子输出的统一单位字段包括name展示名如 Stripe、slug必须通过SLUG_RE的 URL 安全段、url官网、可选的atsgreenhouse | lever | ashby、ats_id用于构造招聘板 URL 的组织段、source来自哪个 VC 名单、batch仅 YC如W21SeedPortalEntry与providers/_types.js中PortalEntrytypedef 形状一致、可直接交给 ATSprovider.detect()消费的对象只含name、careers_url尽力而为的 ATS 或官网 URL和可选的source。纯解析函数可测试性的根基解析逻辑被刻意设计为纯函数无网络、无副作用这是整个模块能被 CI 用内联 fixture 无 mock 测试的关键parseYCPayload(payload)seeds/vc-portfolios.mjs解析 YC API 的分页 JSON优先取显式slug缺省时由name小写化派生name.toLowerCase().replace(/[^a-z0-9]/g, -)并剔除两端连字符无效 slug、重复 slug 都会被过滤。url只接受http(s)开头的值parseA16zPayload(html)seeds/vc-portfolios.mjs无 DOM 解析器的三层降级策略——① 优先找页内application/ldjson的Organization/Corporation结构化数据块② 其次匹配 React 渲染的data-company-name/data-company-url属性③ 最后退回到含portfolio|company|cardclass 的a锚文本并对read more、visit、press等导航性文字做过滤。页面结构变化时逐层优雅降级parseSeedEntries(payload, source)seeds/vc-portfolios.mjs统一入口按source分发到上述两个解析器是 issue 验收标准与 test-all.mjs 直接引用的可测试单元。网络抓取超时 分页走查fetchWithTimeout()是本地最小实现刻意不引providers/_http.mjs保证seeds/自包含用AbortController实现默认 20 秒超时并携带 user-agent.mjs 的默认 UA 头非 2xx 响应会抛出带状态码与前 200 字符摘要的错误seeds/vc-portfolios.mjs。两个抓取函数fetchYCCompanies({ timeoutMs, maxPages })逐页走查 YC API。注意实现细节请求里带着per_page1000但服务端会自行限页因此走查终点不是自己猜而是跟随 API 返回的分页信号——若给出整数totalPages则照其停止若缺失则用parseYCNextPage()解析nextPage字段可能是一个裸数字、pageN片段或携带pageN的完整 URL并要求必须前进否则中断seeds/vc-portfolios.mjs。真正的防失控硬顶是导出的YC_MAX_PAGES 500任何显式maxPages哪怕是Infinity都会被钳制在该上限内防止某天 API 不再上报分页元数据时无限空转seeds/vc-portfolios.mjs。第 1 页抓取失败会直接抛错第 2 页起失败则容忍部分数据后跳出fetchA16zCompanies()a16z 无公开 JSON API直接抓取公开组合页 HTML 交给parseA16zPayload()seeds/vc-portfolios.mjs。SEED_SOURCES 注册表导出对象把种子源 ID 映射到抓取函数与可读标签seeds/vc-portfolios.mjs它是scan-ats-full.mjs --seeds与 CLI 工具消费的唯一入口export const SEED_SOURCES { yc: { fetch: fetchYCCompanies, label: Y Combinator Portfolio, }, a16z: { fetch: fetchA16zCompanies, label: Andreessen Horowitz (a16z) Portfolio, }, };命令行使用法通过 scan-ats-full.mjs推荐scan-ats-full.mjs新增了--seeds标志取值是逗号分隔的注册表键见 scan-ats-full.mjs# 从 Y Combinator 投资组合播种只看最近 7 天 node scan-ats-full.mjs --seeds yc --since 7 # 同时播种 YC 和 a16z仅预览dry-run 不出写结果 node scan-ats-full.mjs --seeds yc,a16z --dry-run # 种子 常规 ATS 源混扫 node scan-ats-full.mjs --seeds yc --ats greenhouse,lever --since 5 # npm 快捷命令定义见 package.json npm run scan:seeds # yc a16z npm run scan:yc # 仅 YC几个值得注意的 CLI 语义均可在 scan-ats-full.mjs 源码中确认未知种子源会直接报错退出Error: unknown seed source(s): xxx. Valid: yc, a16z--seeds与--ats的默认值联动当--seeds是唯一的发现标志未给--ats时--ats自动默认为空列表避免在种子扫描之外还无意间全量走一遍常规 ATS 目录反过来不传--seeds时行为不变默认遍历全部 ATS 源Object.keys(SOURCES)--limit对种子同样生效按 slug 截断公司数量配合--shuffle时会先洗牌再取前 N 家--since决定职位新鲜度runSeedScan内部以Date.now() - sinceDays * 86_400_000为截止线早于它的dated职位被标记stale丢弃无日期职位默认丢弃需--include-undated才保留scan-ats-full.mjs。程序化 API不经过 CLI、在自有脚本里直接集成也可以import { fetchYCCompanies, fetchA16zCompanies, toPortalEntry, SEED_SOURCES } from ./seeds/vc-portfolios.mjs; // 抓取 YC 公司 const companies await fetchYCCompanies(); console.log(companies[0]); // → { name: Stripe, slug: stripe, url: https://stripe.com, source: yc, batch: W11 } // 转成可交给 ATS provider.detect() 的 PortalEntry const entry toPortalEntry(companies[0]); // → { name: Stripe, careers_url: https://job-boards.greenhouse.io/stripe, source: yc } // 遍历注册表 for (const [id, source] of Object.entries(SEED_SOURCES)) { const companies await source.fetch(); console.log(${source.label}: ${companies.length} companies); }toPortalEntryATS URL 的猜测优先级toPortalEntry()seeds/vc-portfolios.mjs把SeedCompany转成PortalEntrycareers_url的解析顺序有严格的分层理解它有助于排查为什么某公司没被探测到显式 ATS 提示当company.ats与ats_id同时存在且ats_id通过SLUG_RE时按平台拼出标准看板 URL——Greenhouse 为https://job-boards.greenhouse.io/${atsId}、Lever 为https://jobs.lever.co/${atsId}、Ashby 为https://jobs.ashbyhq.com/${atsId}无提示则按 slug 猜 Greenhousehttps://job-boards.greenhouse.io/${company.slug}Greenhouse 是 YC 公司最常用的 ATS先拿它试兜底回退到公司官网适用于 ATS 在自定义子域的情况交由provider.detect()在扫描时从域名自动识别。最终careers_url是否有效要等扫描期 ATS provider 的detect()确认对不上就跳过并给出告警。对应断言可见 test-all.mjs。runSeedScan种子如何汇入既有扫描管线scan-ats-full.mjs中的runSeedScan(seedId, opts, ctx, seenUrls, label)scan-ats-full.mjs承担了从种子名单到职位 offer 的完整链路其探测顺序固定为// ATS providers that can auto-detect from a careers_url, in probe order. // Workday is excluded: its URL format requires a tenant|instance|site triple // that cant be derived from a portfolio slug alone. const SEED_PROVIDERS [greenhouse, lever, ashby];见 scan-ats-full.mjs。Workday 被排除的原因值得注意其 URL 需要tenant|instance|site三元组仅凭组合页的 slug 无法推导因此种子路径不参与 Workday 探测。逐家公司处理时用parallelEach(capped, CONCURRENCY, ...)以受控并发批量探测并设置了每家公司 5 分钟的公司级超时COMPANY_TIMEOUT_MS见 scan-ats-full.mjs——防止某家公司的 DNS 或 provider 缺陷拖垮整个 worker 槽位这对动辄上千家公司的扫场至关重要依次调用greenhouse → lever → ashby的detect(entry)首个命中者胜出并调用其fetch(entry, ctx)与portals.yml中追踪公司走scan.mjs的路径完全一致返回的每个 offer 打上source \${seedId}-seed如yc-seed区分于常规扫场的{sourcesKey}-full去重沿用与主扫场共享的seenUrls集合与dedupTokenFor()辅助函数由于 Greenhouse/Lever/Ashby 目前都未定义dedupKey种子 offer 实际走normalizeUrlForDedup(url)的纯 URL 去重scan-ats-full.mjs并同时接受titleFilter/locationFilter/contentFilter与--limit的约束。最终新增职位与常规源产出汇入同一data/pipeline.md后续的标题过滤、位置过滤、去重与投递评估全部复用既有链路这也是该功能接入成本极低的原因。数据源一览SourceURLFormatAuthY Combinatorhttps://api.ycombinator.com/v0.1/companiesJSON API无a16zhttps://a16z.com/portfolio/公开 HTML 页无YCfetchYCCompanies()逐页走查公开 API按 slug 全程去重分页跟随服务端返回的totalPages/nextPage信号推进且被 500 页的YC_MAX_PAGES硬顶钳制分页契约有独立测试覆盖见 tests/vc-portfolios-yc-pagination.test.mjs覆盖各公开 YC batcha16z抓公开组合页经 JSON-LD →data-company-name→ 锚文本的三层降级策略解析页面结构变化时可优雅回退。安全设计该模块面对的是不可信的第三方页面内容因此防御是显式设计而非事后补丁slug 白名单校验所有进入 URL 拼接的 slug 先过SLUG_RE /^[A-Za-z0-9._-]$/导出自 seeds/vc-portfolios.mjs任何含/、空格、!等危险字符的条目在解析阶段即被丢弃——测试用内联 fixture 证明了good-co、also.good_123通过而bad/slash、bad space、bad!bang被拒test-all.mjs构造出的 ATS URL 再经过既有的entryOnHost()SSRF 守卫后才到达任何 provider从根上阻止向内网/非预期主机发起请求零凭据、零无头浏览器、零 LLM 调用不引入任何新的攻击面与 token 成本也无需 API key 配置。扩展更多 VC 组合源仓库把加一个新 VC做成了四步流水线其中第 1、4 步的可测性约束是硬性的对应 seeds/README.md 中的指南与 seeds/vc-portfolios.mjs 的注册表注释写一个parseXyzPayload(payload)纯函数不触网——用内联 fixture 即可测试写一个fetchXyzCompanies(opts?)异步函数调用公开端点并返回SeedCompany[]注册进SEED_SOURCESexport const SEED_SOURCES { yc: { fetch: fetchYCCompanies, label: Y Combinator Portfolio }, a16z: { fetch: fetchA16zCompanies, label: Andreessen Horowitz (a16z) Portfolio }, // 新增源 sequoia: { fetch: fetchSequoiaCompanies, label: Sequoia Portfolio }, };在test-all.mjs中为你的parseXyzPayload()补测试用例现有种子相关断言的完整样例位于 test-all.mjs 的 9b 小节覆盖 YC 解析、a16z HTML 解析、parseSeedEntries分发、SLUG_RE 过滤、toPortalEntry三级 fallback、slug 去重与注册表形态。测试与验证除test-all.mjs9b 小节的内联 fixture 测试外tests/vc-portfolios-yc-pagination.test.mjs 单独锁定了分页走查的契约fetchYCCompanies必须走完 API 上报的每一页当totalPages缺失时能跟随nextPage数字、片段、URL 三种形态由parseYCNextPage统一解析即便传入maxPages: Infinity也绝不超过 500 页硬顶——该上限值被独立锁定为 500防止有人悄悄抬高它让测试静默通过。适用前提与注意事项种子扫描依赖对第三方公开接口/页面的实时抓取YC 与 a16z 任何一方改版都可能导致瞬时空结果或报错YC 首页失败抛错、翻页中断后容忍部分数据a16z 走三层降级策略可通过重试或改日再扫缓解种子产出的 offersource是yc-seed/a16z-seed与常规源的-full后缀可明确区分方便在去重与统计层面追踪来源该路径不覆盖 Workday 看板URL 无法由 slug 推导使用 Workday 的公司需经由常规 ATS 目录发现npm run scan:seeds/scan:yc只是 package.json 里对scan-ats-full.mjs --seeds的封装完整参数列表--verbose、--json、--dry-run、--liveness等可在 docs/SCRIPTS.md 中继续查阅。相关先例与来源VC 组合播种的灵感源自早期 companion 参考项目adityachaudhary99/job-hunt的02-seeds/fetch_yc.py与fetch_a16z.py原始 issue #1370 中引用。career-ops 在此基础上重构为纯解析函数 注册表 共享去重/过滤管线的形态既保持了独立可测又无缝接入了既有扫描体系。【免费下载链接】career-opsOpen-source AI job search: scan job portals, evaluate listings into a structured A-H report with a global 1-5 score, tailor your CV, track applications — runs locally in your AI coding CLI (Claude Code, Codex, OpenCode, Antigravity…)项目地址: https://gitcode.com/GitHub_Trending/ca/career-ops创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考