
后端前端搜索引擎网页爬虫数据工程AI 应用可观测性【免费下载链接】freehirefreehire — the open-source search engine for job seekers项目地址https://gitcode.com/GitHub_Trending/fr/freehire点击查看免费下载本文以 freehire 仓库中已归档的变更提案 proposal.md 为主体结合配套设计文档、规格说明与当前仓库中的真实实现完整讲清 Loxo 适配器这条链路的来龙去脉为什么需要一个能同时覆盖三种主机形态的机构看板爬虫、agency_id-slug稳定去重 ID 如何从 base64 中解出、Hub标志如何做到永不猜测雇主以及cmd/harvest-loxo发现工具如何把搜索引擎足迹变成经人工审核的看板清单。读完你可以理解 freehire 新增一类 ATS 数据源的完整套路适配器、注册、发现工具、测试夹具与取舍记录。背景Loxo 看板长什么样为什么值得单独立项提案的 Why 部分说明了动机Loxo 为许多招聘机构如 FitNext Consulting、Agile Recruiter、LA-Tech托管公开的 careers 看板每个看板都承载 freehire 尚未聚合的技术职位。关键前提是一次 spike 验证——这些看板干净地可抓取且该模式在机构之间、主机形态之间都能泛化一个 Loxo 适配器就能一次性解锁一整个源类别。design.md 的 Context 一节给出了 spike 验证到的具体页面结构这也是全部解析逻辑的事实依据每个机构在host/slug拥有一个公开 careers 看板host是三种形态之一机构子域fitnext.app.loxo.co、裸域app.loxo.co、区域 podpod4.app.loxo.co。careers 页是服务端渲染 HTML每条职位以/job/base64链接出现。详情页的title形如Role | Agency职位描述 HTML 放在内嵌的script typeapplication/json{description: …}块里spike 验证过没有schema.orgJobPostingldjson。base64 职位 ID 解码后是一个稳定的agency_id-slug对。真实雇主从不暴露——看板上只有机构名。freehire 当时已有三个接缝可复用careerspage 风格的HTMLGetterlisting → detail 扇出、huntflow已在使用的CompanyEntry.Hub标志、以及cmd/harvest-boards的实时校验发现模式。因此整个变更是纯增量式的不引入新技术门禁或生命周期机制。设计决策五个关键取舍design.md 的 Decisions 一节记录了五条决策每一条都对应实现中的一个具体约束Board id 去掉协议头的 careers URLhost/slug。备选方案只存 slug在 pod/子域形态下会失效host 无法从 slug 反推给每个条目加 Region 主机提示则会让一个本为 API 数据驻留设计的字段承担额外语义。带上完整 hostslug 让三种主机形态共用一条代码路径适配器按第一个/拆分出 origin 与 slug。详情页解析用title 内嵌 JSON而不是 schema.org。与 careerspage 不同Loxo 没有 JobPosting 结构化数据因此从 title 剥掉| agency后缀从application/json块读description。位置/远程为尽力而为的 DOM 读取读不到就留空——地理信息绝不由适配器猜测留给下游的位置词典裁决。ExternalID 解码后的agency_id-slug。稳定、人类可读机构 ID 前缀保证跨看板的唯一性即使管线尚未按 board 命名空间化。雇主归属复用Hub不发明新机制。hub: true的看板只在标题出现显式分隔符— Client/ Client时解析出客户否则回退到机构名——镜像 huntflow 的部门面包屑解析。实际上回退是常见情况而这恰恰诚实发帖的本来就是机构。发现走搜索足迹不做子域爆破。Loxo 没有公开的机构目录盲猜*.app.loxo.co是一个巨大且低收益的空间一条site:app.loxo.co查询就已经浮现出约 10 个有效机构。发现工具分页收集足迹、提取(host, slug)、逐个实时校验。配套的哲学是harvester 提案人工审核工具输出带技术职位计数的草稿条目操作者只播种真正存活且带技术职位的看板提交进仓库的看板集合始终是项目验证过的事实集。提案的 Non-Goals 同样重要付费的 Loxo Open APIbearer-token 门槛不在范围内——免密钥的 careers 页才是这里的信源跨源去重机构转发 vs 客户自有 ATS 帖是被接受的取舍一次性 Loxo 链接的linksource留作后续一行代码的接缝发现工具绝不自动提交看板。适配器实现internal/ingest/sources/loxo.go提案中写的路径是internal/sources/loxo.go在当前仓库中该实现位于 internal/ingest/sources/loxo.gosources 包已整体迁移到internal/ingest/sources。文件头部的注释完整复述了 spike 结论base64 解码出agency_id-slug去重 ID详情数据来自og:title、og:description的Location: … Salary:片段与内嵌 JSON 块。注册与 Board id 拆分适配器通过NewLoxo(c HTMLGetter)构造在源注册表 registry.go 中登记NewLoxo(c)一行对应提案所说one line insources.AllProvider()返回loxo。Fetch的入口逻辑体现了 board id 的设计func (s loxo) Fetch(ctx context.Context, e CompanyEntry) ([]Job, error) { base, err : url.Parse(https:// e.Board) // e.Board 形如 fitnext.app.loxo.co/fitnext ... root, err : s.http.GetHTML(ctx, base.String()) // 抓 SSR 列表页 locs : loxoJobLinks(base, root) // 提取并解析所有 /job/base64 return fetchDetails(locs, defaultDetailWorkers, ...) }见 loxo.go#L39-L52e.Board直接就是host/slug拼上https://即列表页 URL所有职位链接随后相对同一个 host 解析因此子域、裸域、pod 三种形态共用一条路径。列表页链接提取职位链接由正则捕获并相对 board host 解析var loxoJobIDPattern regexp.MustCompile(/job/([A-Za-z0-9_-])(?:$|[/?#]))loxo.go#L94-L96注意结尾的(?:$|[/?#])是有意为之Loxo 的链接可能带?t…缓存破坏参数或子动作后缀必须把它们排除在 base64 token 之外。测试 loxo_test.go 中的TestLoxoJobLinksHostResolution用同一份真实列表夹具testdata/loxo/listing_fitnext.html分别以fitnext.app.loxo.co、pod4.app.loxo.co、app.loxo.co作为 base 各解析一次断言每次得到 22 条链接且全部解析到https://host/job/前缀——这正是提案Host variant is honored场景的实现级验证。详情页映射title、内嵌 JSON 与尽力而为的地理信息mapLoxoDetail被单独拆出与带 HTTP 的detail分离以便用保存的 HTML 夹具做无网络单测。映射规则loxo.go#L66-L92标题优先读og:title缺失时回退到title并用loxoStripAgencySuffix剥掉尾部的| agency描述loxoDescription遍历所有typeapplication/json的 script 块取第一个含非空description字段的块。注释特别指出块内是原始 HTML 标记非实体转义所以不能做UnescapeString否则会破坏文本节点中的字面实体交给调用方的sanitizeHTML统一处理位置/远程loxoLocation从og:description的roleLocation: locSalary: pay…串中提取Location:与Salary:之间的片段没有该串就返回空。Remote由共享的isRemote推断匹配英文 remote 与俄语 удал…。防御性判断若 ExternalID 提取失败或 title 与 description 同时为空mapLoxoDetail返回okfalse——后者意味着这不是一个已渲染的职位页直接丢弃而不是产出垃圾行。稳定去重 ID四种 base64 编码的容错解码loxoExternalIDloxo.go#L98-L115对提取到的 token 依次尝试StdEncoding、RawStdEncoding、URLEncoding、RawURLEncoding四种解码解码结果必须含-且全部为可打印字符才认定为agency_id-slug否则原样返回 raw token而非报错。这个设计保证同一个职位无论哪次爬取都映射到同一个ExternalID重爬 upsert 到同一目录行对应 loxo-source 规格中Re-crawl dedups to one row场景同时编码细节变化不会让整个看板解析失败。夹具测试断言NDI0NzQtN3RzZTNpa2M2NDJ5emowdg解码为42474-7tse3ikc642yzj0vloxo_test.go#L23-L49。详情扇出有界并发 单帖失败隔离提案明确要求detail 页在有限并发工作池下抓取只丢弃抓取或解析失败的职位单个坏帖绝不中止整块看板的爬取。实现复用了共享的fetchDetails泛型函数helpers.go#L30-L54sem : make(chan struct{}, workers)信号量限流defaultDetailWorkers 8每个 posting 一个 goroutineokfalse的结果置为 nil 后在保序输出时跳过。TestLoxoFetchMapsAndIsolatesFailures用内联 HTML 构造了一个路由到的职位正常、未路由的职位 404的场景断言只返回 1 个 Job 且整次Fetch无错——正是规格Per-posting detail isolation的落地。共享主机的限流包装器当前实现中一个提案未提及、后来加入的细节是loxoThrottleloxo.go#L200-L234裸域app.loxo.co是多租户共享主机很多机构看板共用它连续爬取列表 详情扇出会触发 429而机构专属子域和 pod 不受此限制。NewLoxo因此把传入的HTMLGetter包装为限流器仅对 host精确等于app.loxo.co的请求loxoIsSharedHost明确排除子域避免误伤fitnext.app.loxo.co持锁串行化并按最小间隔loxoSharedHostGap 350ms拉开请求。这是多租户共享主机适配的通用技巧限流粒度绑定到真正共享配额的那个 host而不是整个 provider。Hub 雇主归属显式分隔符否则回退永不猜测loxoCompanyloxo.go#L162-L181实现了提案的 Hub 语义func loxoCompany(title string, e CompanyEntry) (company, roleTitle string) { if e.Hub { for _, sep : range []string{ — , } { if i : strings.LastIndex(title, sep); i 0 { client : strings.TrimSpace(title[ilen(sep):]) role : strings.TrimSpace(title[:i]) if client ! role ! { return client, role } } } } return e.Company, title }实现与提案/规格有几处值得注意的收紧分隔符集合是 em-dash 空格 — 与 strings.LastIndex取最后一次出现避免复合标题误切ASCII 的 - 被刻意排除它与复合职位名Full-Stack和地点后缀- Nashville冲突当作客户标记会猜错。代码注释直书never guess与 freehire 的整体偏向一致非 Hub 条目永远返回配置的机构名标题原样保留。loxo_test.go#L103-L120 的用例表把五种情况钉死em-dash 客户、客户、无分隔符回退、ASCII 连字符不算分隔符、非 Hub 一律回机构名。这组用例就是 loxo-source 规格中Client resolved from an explicit delimiter与Fallback to the agency name两个场景的可执行化。发现工具cmd/harvest-loxo的完整工作流提案宣布cmd/harvest-loxo镜像cmd/harvest-boards从搜索引擎足迹枚举 Loxo 机构看板、逐个实时校验、输出草稿条目供人工审核。当前仓库的实现 cmd/harvest-loxo/main.go 完整落地了这条管线文件头的用法注释给出了三种运行方式go run ./cmd/harvest-loxo footprint-urls.txt go run ./cmd/harvest-loxo footprint-urls.txt go run ./cmd/harvest-loxo --apply footprint-urls.txt需要DATABASE_URL不带--apply时只报告不写库与cmd/add-board同一约定。工作流分为五步足迹读取readLinesmain.go#L142-L160从 stdin 或种子文件逐行读取 Loxo 足迹 URL操作者用site:app.loxo.co之类的查询自行收集——工具本身不爬搜索引擎这正是规格对harvest-boards哲学的说明。候选提取candidateFromURLmain.go#L182-L199每条 URL 变成一个(host, slug)候选。career 页是单段路径/job/base64是两段路径、不携带 slug返回okfalselogin、api、signup等保留词被排除。host 校验用isLoxoHost精确匹配app.loxo.co或以.app.loxo.co结尾——刻意不匹配notapp.loxo.co这类形近域名。结果按host/slug去重且保序。与现有目录去重loadExistingBoards经boardcatalog.LoadForProvider查出目录中已有的loxo看板已知候选不再重复探测。并发实时校验probeAll8 个 workerprobe直接调用 Loxo 适配器的Fetch——能返回至少一个职位才算存活429 回退由共享 HTTP 客户端处理。存活看板的显示名从列表页titleJob Listing | Agency/title用正则loxoTitlePattern提取提取失败回退到 slug。技术相关度计数与输出对每个存活看板用classify.Parse(j.Title).Category分类并统计技术职位数isTechCategory判定类别非空且不在vocab.NonTechCategories黑名单内按host/slug — 公司名 — N jobs, M tech格式打印供操作者跳过无技术职位的机构对应提案mostly non-tech agencies风险的处置。带--apply时addBoards把校验通过的看板以statuspending、Hub: true写入看板目录boardcatalog.InsertInput重复 board 记为 duplicate 而非错误唯一索引在别处兜底。规格中工具只提案、不决定提交集合的约束由这一报告/写入两阶段行为保证。值得对照提案说明一处演进提案时代的落地形态是sources/loxo.yml看板文件所有条目hub: true而当前仓库中harvest-loxo已改为直接写看板目录表boardcatalog见 migrations/0123_boards.sql仓库中不再有sources/loxo.yml文件。从源码看看板集合的持久化从 yml 文件迁移到了数据库目录提案-审核的人机边界保持不变。测试策略真实 HTML 夹具 行为用例loxo-source 规格反复提到fixture-based unit tests that fail loudly if the parse breaksHTML 形态漂移风险的主要防线。仓库中的夹具与测试夹具testdata/loxo/listing_fitnext.html 与 testdata/loxo/detail_fitnext.html——来自 spike 机构的真实页面tasks.md 要求夹具覆盖带 title 分隔符客户与纯机构标题两种详情页仓库当前保存的是后者形态的 fitnext 夹具。行为用例loxo_test.goTestLoxoProviderprovider 名、TestLoxoMapDetailFixture真实详情页 → title 剥后缀、描述来自内嵌 JSON、URL 规范化、ExternalID 解码、Remote (LATAM)位置解析、TestLoxoJobLinksHostResolution三种 host 形态各解析 22 条链接、TestLoxoFetchMapsAndIsolatesFailures坏帖丢弃不致命、TestLoxoCompanyHub 分隔符用例表。这套测试与 tasks.md 中 RED→GREEN 的记录一一对应第 24 节也印证了提案 Impact 部分复用 careerspage 风格HTMLGetter的说法夹具解析走的是golang.org/x/net/html节点树与同包其他 HTML 适配器共享jobLinks、metaProperty、titleText等公共 helperhtml.go。已知取舍与扩展接缝design.md 的 Risks 一节给出了四个风险及处置值得作为接入一类新数据源的决策模板引用跨源重复机构转发某职位、客户又在自家 ATS 直发同一职位会产生两行——freehire 只在单个source内去重。提案将其记为被接受的取舍seam暂不缓解视为合法的两个不同 listing未来按 titlecompany 的跨源模糊去重是已记录的未来接缝。大多数机构非技术发现工具的技术职位计数让操作者跳过空板下游逐职位 non-tech 门禁过滤其余——因此适配器层不需要技术门禁。HTML 形态漂移title 后缀、JSON 块格式可能变。防线是夹具测试大声失败spike 显示该形态今天在机构间是统一的。雇主欠归属宁可记为机构名也不冒险猜错客户与 freehire 的 never guess 偏向一致。迁移计划同样克制纯增量——新适配器文件 注册表一行 新看板条目 新cmd/harvest-loxo无 schema、无迁移部署时按其他看板文件的方式为loxo配置错开的 ingest timer。小结这条归档变更展示了 freehire 扩展数据源的标准动作先用 spike 验证页面形态可泛化再在 openspec 中固化 Why/What/Capabilities/Impact 与逐条 Decisions随后以列表 → 详情扇出 稳定解码 ID 尽力而为的地理信息 永不猜测的雇主归属四件套实现适配器配套一个足迹 → 校验 → 计数 → 提案的发现工具最后用真实 HTML 夹具把解析脆弱性钉死在测试里。对想在同类聚合型仓库中新增抓取适配的读者proposal.md、design.md、tasks.md、loxo-source 规格、loxo-harvest 规格 与 internal/ingest/sources/loxo.go、cmd/harvest-loxo/main.go 构成了一条从设计到代码、可直接对照阅读的完整证据链。赞分享后端前端搜索引擎网页爬虫数据工程AI 应用可观测性【免费下载链接】freehirefreehire — the open-source search engine for job seekers项目地址https://gitcode.com/GitHub_Trending/fr/freehire点击查看免费下载相关推荐freehire 源码解析为 Gem 招聘板实现 GraphQL list→detail 源适配器freehire 源码解析为 Gem 招聘板实现 GraphQL list→detail 源适配器 freehire 是一个面向求职者的开源职位搜索引擎它把后端前端搜索引擎网页爬虫数据工程AI 应用可观测性freehire 源码级实战为 Gem 招聘板实现 GraphQL list→detail 采集适配器freehire 源码级实战为 Gem 招聘板实现 GraphQL list→detail 采集适配器 本文以 freehire 归档变更 add gem s后端前端搜索引擎网页爬虫数据工程AI 应用可观测性3分钟学会ncmdumpGUIWindows下网易云音乐NCM文件解密终极指南3分钟学会ncmdumpGUIWindows下网易云音乐NCM文件解密终极指南 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗ncmdum后端前端搜索引擎网页爬虫数据工程AI 应用可观测性上一篇ComfyUI-WanVideoWrapper企业级部署多节点分布式渲染方案下一篇fastlane frame_screenshots(frameit)实战指南用 Framefile.json 为 iOS/macOS/Android 截图批量套上设备边框创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考