如何用 scan-ats-full --seeds 把 YC 与 a16z 投资组合公司作为种子做 ATS 扫描
【免费下载链接】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 里的scan-ats-full.mjs默认是"反向 ATS 扫描":它遍历 Greenhouse、Lever、Ashby、Workday、iCIMS 的公开公司目录,找出符合你portals.yml过滤条件的新职位。但目录遍历慢、且只覆盖"已经在这些目录里"的公司。--seeds参数提供了另一条发现路径:直接从 Y Combinator 和 Andreessen Horowitz (a16z) 的公开投资组合拉出公司清单,逐家探测它们的 ATS 招聘页,把命中的新职位写入data/pipeline.md。这条路径由 seeds/vc-portfolios.mjs 实现,全程不需要登录、API key 或 LLM token,只依赖两个公开数据源。
读完本文,你能完成一次"以 VC 投资组合为种子的 ATS 扫描",知道结果写到哪里、如何验证,以及哪些公司会被静默跳过。
扫描链路:从投资组合到 pipeline
整条链路在 seeds/README.md 中描述如下:
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几个关键环节的实际行为(来自 scan-ats-full.mjs 源码):
- 两个数据源,均无鉴权:YC 走公开 JSON API
https://api.ycombinator.com/v0.1/companies;a16z 解析公开页面https://a16z.com/portfolio/的 HTML,页面结构变化时会优雅降级而不是报错。 - ATS 探测顺序固定:每个种子公司经
toPortalEntry()转成PortalEntry后,按 greenhouse → lever → ashby 的顺序调用各 provider 的detect(),第一个命中者胜出。没有命中任何 provider 的公司被静默跳过。Workday 不在探测列表里,因为它需要tenant|instance|site三元组,无法从投资组合 slug 推导。 - 过滤与去重和常规扫描一致:
portals.yml里的title_filter(本扫描器可用title_filter_full覆盖)、location_filter、content_filter同样生效;职位 URL 先在本轮内去重,再与data/scan-history.tsv的历史记录去重,所以重复运行不会灌入重复条目。
前提条件:portals.yml 必须已存在
scan-ats-full.mjs启动时会检查portals.yml,找不到则直接退出:
Error: portals.yml not found. Run onboarding first — the reverse scan reuses its title_filter/location_filter.也就是说,种子扫描并不独立于 onboarding——它复用 onboarding 生成的过滤条件。如果你的portals.yml还没有title_filter(或title_filter_full)的positive关键词,脚本会打印警告"every fresh posting on every board will match",扫描本身会继续,但你大概率会被无关职位淹没。建议先按 templates/portals.example.yml 的样子补齐关键词再跑。
另外注意日期门控:反向扫描只针对新职位,默认窗口是--since 3(最近 3 天)。带日期但早于窗口的职位直接丢弃;没有发布日期的职位默认丢弃(计入Undated dropped),加--include-undated才会保留。
执行种子扫描
在仓库根目录执行。最短主路径是先 dry-run 预览,确认命中符合预期后再正式写入:
# 预览:只打印匹配,不写任何文件 node scan-ats-full.mjs --seeds yc,a16z --dry-run # 正式运行:YC + a16z 两个种子源 node scan-ats-full.mjs --seeds yc,a16z--seeds的值是逗号分隔的小写源 id,目前SEED_SOURCES注册表只登记了yc和a16z两个;传入其他值会报错退出:
Error: unknown seed source(s): xxx. Valid: yc, a16zpackage.json 提供了两个 npm 快捷方式,等价于上面两条命令:
npm run scan:seeds # 即 node scan-ats-full.mjs --seeds yc,a16z npm run scan:yc # 即 node scan-ats-full.mjs --seeds yc一个值得注意的行为:当--seeds是唯一的发现参数(没有--ats)时,--ats默认为空,即不会顺带遍历完整的 ATS 目录——这是源码中显式避免的"意外全量扫描"。所以--seeds yc,a16z只探测投资组合公司,扫描范围就是这两份组合。
控制扫描范围
常用参数及在种子扫描中的实际作用:
# 扩大时间窗口到最近 7 天(--seeds yc 单源示例) node scan-ats-full.mjs --seeds yc --since 7 # 限制每个种子源探测的公司数上限 node scan-ats-full.mjs --seeds yc,a16z --limit 200 # 组合种子源与常规 ATS 目录 node scan-ats-full.mjs --seeds yc --ats greenhouse,lever --since 5--since <天数>:职位新鲜度窗口,缺省 3 天。--limit <N>:对种子源同样生效,按 slug 顺序截取前 N 家;配--shuffle则随机抽样 N 家。适合先用小样本试跑。--ats <list>:想同时走常规目录扫描时才加;种子源与目录源的结果汇到同一次运行的同一份输出去重。--verbose:打印每个公司的抓取失败原因(默认静默跳过);--liveness:写入前用 Playwright 逐一验证职位 URL 存活;--md-out <dir>:额外写一份按日期命名的 Markdown 摘要;--json:stdout 只输出机器可读 JSON(进度信息走 stderr)。
结果验证:看哪里、看什么
正式运行(非 dry-run)结束时,stdout 会给出汇总段:
Companies scanned: <总数> Unreachable boards: <失败数> Undated dropped: <无日期丢弃数> (若有) New matches: <命中数> New offers: + [yc-seed] 2026-09-05 | Stripe | Senior Backend Engineer | Remote https://...有命中时的落盘行为:
- 职位追加到
data/pipeline.md的## Pendientes段,同时记录到data/scan-history.tsv;日志会打印Results saved to data/pipeline.md and data/scan-history.tsv。 - 最后提示下一步:
→ Run /career-ops pipeline to evaluate new offers.(进入 career-ops 的评估流程,为这些新职位生成 A-H 报告)。
没有新命中时输出Nothing new.,这是正常结果——窗口内确实没有新职位,或全部被过滤/去重挡掉,而不是故障。dry-run 结尾则是(dry run — run without --dry-run to save results),确认预览无误后去掉--dry-run重跑即可。
排查"为什么命中很少"时,先区分两种情况:某家公司没被探测到(ATS 探测未命中或拉取失败,后者计入Unreachable boards,加--verbose才能看到逐家原因),与探测到了但职位被title_filter/location_filter/日期窗口过滤掉(后者不会逐条打印)。--json输出里的postingsDroppedNoDate、unreachableBoards等字段可以把这两类分开。
边界与限制
- 只覆盖 Greenhouse / Lever / Ashby:种子探测的 provider 列表就是这三个(
SEED_PROVIDERS)。用 Workday、iCIMS 或其他 ATS 的组合公司会被跳过。 - YC 抓取规模:代码按 API 返回的
totalPages/nextPage逐页翻页,YC_MAX_PAGES = 500只作为防失控的硬上限(见 seeds/vc-portfolios.mjs)。seeds/README.md 中"最多 3 页 × 1000 家公司"的描述与这段实现不完全一致,实际抓取量以 API 分页信号和日志里的Companies scanned计数为准。 - 安全设计:所有 slug 在拼 URL 前都过
SLUG_RE = /^[A-Za-z0-9._-]+$/校验,构造出的 ATS URL 还要过entryOnHost()的 SSRF 防护;全程无 auth token、无 headless 浏览器、无 LLM API 调用。 - 并发:探测以并发 20(
CONCURRENCY)跑,单家公司整段抓取有 5 分钟超时,一家卡死不会拖住整个 worker。
下一步
扫描成功后按提示运行/career-ops pipeline,让 career-ops 对data/pipeline.md里的新职位做 A-H 评估与 1-5 分打分。如果你的过滤结果长期偏噪,回到portals.yml调整title_filter_full(该字段只影响本扫描器,不干扰scan.mjs的既有过滤)再跑一轮--dry-run验证。
程序化复用的话,seeds/README.md 给出了直接 import 的写法(文档示例):
import { fetchYCCompanies, toPortalEntry } from './seeds/vc-portfolios.mjs'; const companies = await fetchYCCompanies(); const entry = toPortalEntry(companies[0]); // → { name: 'Stripe', careers_url: 'https://job-boards.greenhouse.io/stripe', source: 'yc' }新增其他 VC 组合时,按 seeds/README.md 的"Adding more VC portfolios"一节:写纯函数解析器 +fetchXyzCompanies(),注册进SEED_SOURCES,--seeds即自动支持新 id。
【免费下载链接】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),仅供参考