把求职者正在看的那条 JD,变成图谱里的一个新节点
在招聘网站的岗位详情页点一下工具栏图标:抽取 JD 字段 → 走后端同一条治理流水线 → 更新岗位能力图谱 → 同一秒把能力缺口推回用户屏幕。以下是可点击的交互演示、数据流说明与落地排期。
演示区里的招聘页是模拟页面;右侧诊断数字为示例数据,用于说明交互与字段结构,不代表真实匹配结果。
大模型应用开发工程师(知识图谱方向)
岗位职责
- 负责岗位—技能知识图谱的构建与多跳推理能力设计
- 基于 GraphRAG 实现岗位检索与能力诊断问答链路
- 参与多源招聘数据治理流水线与抽取效果评估
任职要求
- 熟悉 Python,具备 FastAPI 或 Spring 体系后端开发经验
- 了解 Neo4j / Cypher,理解图数据建模与索引设计
- 有大模型信息抽取、RAG 类项目落地经验者优先
它不是一个小玩具,是补数据源的第三条腿
后端爬虫解决“量”,插件解决“第一时间”和“现场体验”。两者共用一条治理流水线和一张图谱,不是两套系统。
补上时效性缺口
批量爬虫受调度周期约束,通常隔天甚至隔周才回扫一次;而新兴岗位的生命周期很短,很多 JD 上线一周就改词、下架。用户浏览那一刻上报,是 T+0 的自然采样。
赛题痛点一:新兴岗位难以第一时间发现把诊断搬到求职现场
竞品的浏览器生态基本只做简历填充。你的能力缺口诊断出现在用户正在看的那条 JD 旁边,不需要跳到另一个系统,也不用先上传简历再等结果。
终端平台章节:唯一能和竞品同页并排的能力一次捕获,三处回流
同一次点击同时写图谱(新岗位/新技能边)、写个人档案(收藏与诊断记录)、写小程序订阅(该赛道岗位更新提醒)。用户越用,图谱越新,诊断越准。
三端同账号:Web · 小程序 · 扩展两条通道,谁覆盖谁
不要把它写成“替代爬虫”,评委一问就露。正确表述是补位:广度靠爬虫,时效与人机上下文靠插件。
| 维度 | 后端批量爬虫(已有) | 浏览器插件上报(新增) |
|---|---|---|
| 覆盖广度 | 高:按站点批量翻页,单日可抓数千条 | 低:只覆盖用户实际浏览到的页面 |
| 时效 | 受调度周期约束,一般 T+1 及以上 | T+0,JD 刚上线即可捕获 |
| 反爬成本 | 高:频控、验证码、IP 封禁都要处理 | 低:一次点击一个页面,等同真人浏览 |
| 数据质量 | 纯 JD 文本,缺少浏览上下文 | 附带来源 URL、命中关键词、停留时长等弱信号 |
| 合规姿态 | 需遵守站点 robots 与频控,做限速 | 用户显式点击、可见、可撤回、可删除 |
| 运维成本 | 站点改版需要改解析规则 | 同样要维护选择器,但单站失效不影响主流程 |
插件不调用 Playwright,也不靠截图 OCR
三个最常见的误解一次说清:采集发生在哪一层、谁调用谁、什么时候才降级到 OCR。架构上只有两个采集入口,一个是浏览器里的插件,一个是服务器上的 Playwright 定时任务,它们不互相调用,只在最后汇到同一个 ingest 函数。
为什么截图 + OCR 不能当主方案
- 会带误差。JD 里恰恰全是技术名词——GraphRAG、Neo4j、LangGraph,OCR 最容易在字母与数字交界处切错,错一个字符就直接污染图谱节点。
- 会带延迟。截图编码上传加上 OCR 识别,正常也要 1–3 秒;用户点完图标要盯着空白侧栏等,体验比 DOM 直取差一个量级。
- 会带误解。答辩时讲“截图识别文字”,评委会默认你在绕开反爬或者没有正经解析能力;而“DOM 里本来就有文本”是一句站得住的技术陈述。
结论:DOM 是主路径——页面能显示内容,就一定有 DOM 文本。OCR 只在 DOM 被混淆到取不到文字时才顶上。
DOM 抽取失败 → chrome.tabCapture 取当前标签页截图 → base64 传后端 → PaddleOCR 识别 → 识别文本同样进 pipeline.ingest(),后面的去重、抽取、交叉校验、写图完全不变。 这条路径的命中率与速度都低于主路径,因此只作为 fallback 存在,并在文档里明确标注,不伪装成技术亮点。
| 层 | 技术 | 职责 |
|---|---|---|
| 浏览器扩展 | Manifest V3 + 原生 JS(content script / side panel) | 手动触发、读 DOM 取文本、渲染诊断结果 |
| 服务端接口 | FastAPI(Python) | 接收 JD、返回诊断,鉴权走 JWT |
| 治理与抽取 | 现有流水线 + 大模型抽取 + 交叉校验 | 与爬虫共用同一套函数与阈值,不另起一套 |
| 图谱 | Neo4j + Cypher | 岗位/技能节点与边的 upsert、版本记录与回滚 |
| 批量采集 | Playwright + APScheduler(离线定时) | 存量岗位回扫,不参与用户请求链路 |
| 兜底识别 | PaddleOCR | 仅 DOM 不可用时启用,非主方案 |
- 插件是前端,Playwright 是后端离线任务。两者不互相调用:插件里不会出现任何 Playwright 代码,Playwright 也不会去开一个扩展。
- 两条通道最终只有一张图谱、一条流水线。不存在“插件专用库”或“爬虫专用库”,否则口径会打架,交叉校验也就失效了。
- 采集必须由用户点击触发。不做定时、不做静默、不翻页——这是插件通道能被称作“人机协同”而不是“自动化抓取”的唯一分界线。
插件这条链路:一次点击走了 8 步
前 3 步是插件专属;第 4 步起全部与 Playwright 通道共用同一套函数与阈值。Playwright 自己的前 3 步是「调度 → 列表页翻页 → 逐个详情抓取」,抓完直接调第 4 步的 ingest,所以下面的流程只画一遍。
另一条关键点:插件上报的数据不享受免检待遇——同一原始池、同一流水线、同一置信阈值,低于阈值一样进人工复核队列。
页面采集
content script 在 activeTab 上运行,只读当前页 DOM,不额外发请求、不改页面内容。用户不点图标,脚本不会主动跑。扩展端
字段抽取
岗位名、公司、薪资、标签、职责、任职要求,加来源 URL 与抓取时间戳,落到统一字段结构,共 9 个必填项。扩展端
入队落库
带用户 token 调 POST /api/jd/capture,写入原始数据池,标记 source=extension,方便日后单独评估这条通道的质量。服务端
治理
降噪、SimHash 去重、抄袭比对、时滞过滤。与爬虫数据共用同一套阈值配置,避免出现“人上来的就松一点”的双标准。治理流水线
结构化抽取
大模型抽出岗位—技能—证书—经验年限,输出严格 JSON,字段缺失时置 null 而不是猜。LLM
交叉校验
与其他来源的同岗位做比对,置信度低于 0.80 的一律进人工复核中台,通过后才允许写图。这是赛题“抑制幻觉”的直接落点。质控
写入图谱
岗位节点 upsert、技能边 MERGE,记录版本号并支持回滚。重复捕获只更新不新增,避免图谱被同一条 JD 撑胖。Neo4j
回流
侧边栏诊断、Web 端完整报告、小程序赛道订阅提醒、Agent 的图谱问答,全部读同一份图谱,口径一致。三端
只做五个功能,多一个都是负担
五个功能全部复用已有后端能力,扩展端本身不写算法。每个功能都对应一个已经在系统里存在的模块。
一键捕获当前 JD
数据源支持 BOSS 直聘、猎聘、智联、拉勾的岗位详情页。按域名匹配站点适配器,匹配失败走通用回退(标题取 h1、薪资用正则、职责取“岗位职责”段),保证换版也不至于完全抽不到。
侧栏即时人岗诊断
C 端体验捕获后立刻拉一次轻量诊断:匹配分数、已具备技能、缺口与权重、三步补学顺序。复用已有的细粒度差距分析,只把输出裁剪成侧栏能放下的粒度。
图谱轻问答
Agent侧栏底部一个输入框,问“这个岗位的技能前置依赖是什么”“这个方向还会衍生哪些岗位”。走 GraphRAG 多跳推理,回答附图谱链路证据,可解释性直接落到界面上。
收藏与档案同步
增长一键收藏写回平台个人档案,Web 与小程序同步可见。用户下次打开小程序时,订阅的赛道已经有更新提醒,形成一次点击之后的长期回访理由。
脏数据标注上报
质控用户可标记“已过期 / 重复抄袭 / 信息残缺”。标注只作为弱信号回流治理模块,权重低于爬虫源,不能单独决定一条数据的去留。
5 天能跑通 Demo,但只在主线有空档时才动
按“如果最后还有时间”的前提排的:先 P0 跑通单站闭环,能演示就停手;P1/P2 是锦上添花,不达标不影响主线交付。
单站闭环
MV3 骨架、猎聘单站选择器、capture 接口、侧栏静态诊断。验收标准:点图标 → 侧栏出分数与缺口,图谱能查到这条岗位。
体验补齐
图谱问答、收藏同步、BOSS 直聘适配。验收标准:两个站点都能捕获,问答能返回带链路的结果。
打磨与埋点
标注上报、其余两站、通道质量统计(扩展来源的抽取准确率单独出报表)。验收标准:能回答“这条通道到底贡献了多少新岗位”。
// MV3 清单:最小权限,只申请演示需要的三项 { "manifest_version": 3, "name": "岗位能力助手 · JD 捕获", "version": "0.1.0", "action": { "default_title": "捕获当前岗位" }, // 不写 popup,才能触发 onClicked "permissions": ["activeTab", "scripting", "storage", "sidePanel"], "host_permissions": [ // 只声明要适配的站点 "https://*.liepin.com/*", "https://*.zhipin.com/*" ], "background": { "service_worker": "background.js" }, "side_panel": { "default_path": "sidepanel.html" } }
// content.js —— 只做一件事:把页面上的字段取出来 // 站点适配器:每个站一套选择器;抽不到的字段返回 null,不猜 const ADAPTERS = { "www.liepin.com": () => ({ title: q(".job-title-box h1, h1"), company: q(".company-name, .comp-name"), salary: q(".job-salary, .salary"), tags: qa(".labels span, .tag-list span"), duty: block("岗位职责"), require: block("任职要求") }), "www.zhipin.com": () => ({ /* 同上,选择器换成 BOSS 的 */ }) }; // 通用回退:站点改版时至少能拿到标题和薪资 function fallback() { return { title: q("h1"), salary: (document.body.innerText.match(/\\d+[-–]\\d+K/i) || [""])[0], duty: block("岗位职责"), require: block("任职要求") }; } // 监听后台的采集指令,并把结果回传 chrome.runtime.onMessage.addListener((msg, _sender, reply) => { if (msg.type !== "CAPTURE") return; const host = location.hostname.replace("www.", ""); const pick = ADAPTERS[location.hostname] || fallback; // 没适配就走回退 const data = pick(); reply({ ...data, url: location.href, capturedAt: Date.now(), site: host }); });
// background.js —— 打开侧栏、拿数据、提交服务端 const API = "https://your-domain.com"; // 1. 用户点工具栏图标:开侧栏 + 发起采集 chrome.action.onClicked.addListener(async (tab) => { await chrome.sidePanel.open({ tabId: tab.id }); const data = await chrome.tabs.sendMessage(tab.id, { type: "CAPTURE" }); await submit(data); // 2. 提交治理流水线 }); // 3. 提交:带用户 token,服务端校验身份后落原始池 async function submit(data) { const { token } = await chrome.storage.local.get("token"); // 复用 Web 端 JWT if (!token) return; // 没登录就不提交,不静默续期 const res = await fetch(`${API}/api/jd/capture`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify(data) }); const { jd_id, confidence } = await res.json(); // 回给侧栏展示 chrome.storage.session.set({ lastJd: { jd_id, confidence } }); }
# api.py —— 两个接口,复用已有的治理与诊断能力 @router.post("/api/jd/capture") def capture(payload: JdIn, user=Depends(current_user)): # 1. 字段完整性校验:9 个必填项,缺关键字段直接拒绝 if not payload.title or not payload.require: raise HTTPException(400, "字段不足,未进入流水线") # 2. SimHash 去重:命中已有文档就只更新 last_seen,不新增节点 h = simhash(payload.text()) if exist = store.find_by_simhash(h, threshold=3): exist.touch(source="extension") return { "jd_id": exist.id, "dedup": True } # 3. 和其他来源走同一条流水线,同一套阈值,不搞双标准 jd = pipeline.ingest(payload, source="extension") # 4. 置信度不足的进人工复核队列,通过后才允许写图 if jd.confidence < CONFIDENCE_GATE: review_queue.push(jd) return { "jd_id": jd.id, "confidence": jd.confidence, "need_review": True } graph.upsert_job(jd) # 5. 岗位节点 upsert,技能边 MERGE return { "jd_id": jd.id, "confidence": jd.confidence } @router.get("/api/match/quick") def quick_match(jd_id: str, user=Depends(current_user)): # 侧栏专用:把完整诊断裁成侧栏能放下的粒度 profile = talent.get_profile(user.id) return diagnoser.quick(profile, graph.get_job(jd_id))
# crawler.py —— 后端批量通道,和插件完全独立 # 它不开扩展、不调扩展;扩展也不调它。两者只在 pipeline.ingest 汇合 from playwright.async_api import async_playwright from apscheduler.schedulers.asyncio import AsyncIOScheduler async def crawl_site(site, pages=5): async with async_playwright() as p: browser = await p.chromium.launch(headless=True) # 服务器上无头跑 page = await browser.new_page() for n in range(pages): await page.goto(f"{site.base_url}?page={n}") await page.wait_for_selector(site.list_selector) # 等列表渲染完再取 links = await page.eval_on_selector_all( site.item_selector, "els => els.map(e => e.href)" ) for url in links: jd = await fetch_detail(page, url, site) # 进详情页再抓一次 pipeline.ingest(jd, source=f"crawler:{site.name}") # ← 与插件同一个入口 await asyncio.sleep(site.delay) # 限速:不触发频控、不绕验证码 await browser.close() # 定时:每天 02:00 回扫一轮存量岗位,产出写进同一张图谱 scheduler = AsyncIOScheduler() scheduler.add_job(crawl_site, "cron", hour=2, args=[liepin])
评委最可能问的四个问题
点开看答案。这四条如果答不上来,建议这个加分项先别上。
这和爬虫有什么区别?会不会被判成违规采集?
用户乱点、错标会不会污染图谱?
为什么不直接做 H5 或小程序?
会不会拖垮主线开发?
四件绝对不做的事
这几条只要碰一条,加分项就变成扣分项,而且很难在答辩里圆回来。
一句话写进文档
新增浏览器扩展作为交互式数据采集终端,构建“自动化批量爬虫 + 用户主动增量上报”双数据源,提升新兴岗位发现的时效性,同时为求职者提供沉浸式的 JD 解读与人岗缺口诊断。
这个加分项在备选池里的位置
下面这张表建议你直接在文档里维护,用于答辩前决定“还剩多少时间、上哪一项”。
| 备选加分项 | 工作量 | 评委感知度 | 风险与前置条件 |
|---|---|---|---|
| 浏览器扩展 JD 捕获 | 约 5 天 | 高(现场可点) | 站点选择器易变;不上架商店,本地加载演示 |
| 数字人模拟面试 | 约 4 天 | 高 | 依赖语音链路稳定性,网络抖动影响观感 |
| 产业趋势监测报表 | 约 3 天 | 中(偏 G 端) | 数据口径要能自证,别用截图凑数 |
| 学习打卡与成长台账 | 约 2 天 | 中 | 需要小程序端配合,跨端联调成本 |