备选加分项 · 浏览器扩展端

把求职者正在看的那条 JD,变成图谱里的一个新节点

在招聘网站的岗位详情页点一下工具栏图标:抽取 JD 字段 → 走后端同一条治理流水线 → 更新岗位能力图谱 → 同一秒把能力缺口推回用户屏幕。以下是可点击的交互演示、数据流说明与落地排期。

演示区里的招聘页是模拟页面;右侧诊断数字为示例数据,用于说明交互与字段结构,不代表真实匹配结果。

岗位详情 · 猎聘
www.liepin.com / job / 4937xxxx
点这里,捕获这条 JD
科
某智能科技(模拟)
B 轮 · 100–499 人 · 人工智能 / 知识图谱

大模型应用开发工程师(知识图谱方向)

25–40K · 15 薪
发布于 2026-11-05 · 招聘中 · 简历处理率 82%
知识图谱GraphRAGNeo4jLangGraphPython

岗位职责

  1. 负责岗位—技能知识图谱的构建与多跳推理能力设计
  2. 基于 GraphRAG 实现岗位检索与能力诊断问答链路
  3. 参与多源招聘数据治理流水线与抽取效果评估

任职要求

  1. 熟悉 Python,具备 FastAPI 或 Spring 体系后端开发经验
  2. 了解 Neo4j / Cypher,理解图数据建模与索引设计
  3. 有大模型信息抽取、RAG 类项目落地经验者优先
治理流水线 · 与后端爬虫同一套阈值
    价值论证

    它不是一个小玩具,是补数据源的第三条腿

    后端爬虫解决“量”,插件解决“第一时间”和“现场体验”。两者共用一条治理流水线和一张图谱,不是两套系统。

    补上时效性缺口

    批量爬虫受调度周期约束,通常隔天甚至隔周才回扫一次;而新兴岗位的生命周期很短,很多 JD 上线一周就改词、下架。用户浏览那一刻上报,是 T+0 的自然采样。

    赛题痛点一:新兴岗位难以第一时间发现

    把诊断搬到求职现场

    竞品的浏览器生态基本只做简历填充。你的能力缺口诊断出现在用户正在看的那条 JD 旁边,不需要跳到另一个系统,也不用先上传简历再等结果。

    终端平台章节:唯一能和竞品同页并排的能力

    一次捕获,三处回流

    同一次点击同时写图谱(新岗位/新技能边)、写个人档案(收藏与诊断记录)、写小程序订阅(该赛道岗位更新提醒)。用户越用,图谱越新,诊断越准。

    三端同账号:Web · 小程序 · 扩展
    数据通道

    两条通道,谁覆盖谁

    不要把它写成“替代爬虫”,评委一问就露。正确表述是补位:广度靠爬虫,时效与人机上下文靠插件。

    维度 后端批量爬虫(已有) 浏览器插件上报(新增)
    覆盖广度 高:按站点批量翻页,单日可抓数千条 低:只覆盖用户实际浏览到的页面
    时效 受调度周期约束,一般 T+1 及以上 T+0,JD 刚上线即可捕获
    反爬成本 高:频控、验证码、IP 封禁都要处理 低:一次点击一个页面,等同真人浏览
    数据质量 纯 JD 文本,缺少浏览上下文 附带来源 URL、命中关键词、停留时长等弱信号
    合规姿态 需遵守站点 robots 与频控,做限速 用户显式点击、可见、可撤回、可删除
    运维成本 站点改版需要改解析规则 同样要维护选择器,但单站失效不影响主流程
    实现策略

    插件不调用 Playwright,也不靠截图 OCR

    三个最常见的误解一次说清:采集发生在哪一层、谁调用谁、什么时候才降级到 OCR。架构上只有两个采集入口,一个是浏览器里的插件,一个是服务器上的 Playwright 定时任务,它们不互相调用,只在最后汇到同一个 ingest 函数。

    端侧 浏览器内 · 用户正在浏览的那个标签页
    工具栏图标手动点击才触发
    content.js读 DOM,直接取岗位文本
    sidepanel.html渲染分数、缺口、补学顺序
    chrome.storage存 Web 端 JWT,不静默续期
    服务端 FastAPI · 用户请求的同步链路,必须毫秒级回包
    POST /api/jd/capture鉴权 → 落原始池
    治理流水线去重 / 抄袭比对 / 时滞过滤
    置信门低置信 → 人工复核队列
    Neo4j 写入节点 upsert + 边 MERGE
    GET /api/match/quick画像 × 岗位 → 缺口
    PaddleOCR仅 DOM 不可用时启用
    离线 后端定时任务 · 不进用户请求链路,挂了不影响插件
    Playwright无头浏览器,列表 + 详情两级抓取
    APScheduler每天凌晨回扫存量岗位
    站点适配 + 限速遵守 robots 与频控,不绕验证码
    直接 ingest走和插件完全相同的下游

    为什么截图 + OCR 不能当主方案

    1. 会带误差。JD 里恰恰全是技术名词——GraphRAG、Neo4j、LangGraph,OCR 最容易在字母与数字交界处切错,错一个字符就直接污染图谱节点。
    2. 会带延迟。截图编码上传加上 OCR 识别,正常也要 1–3 秒;用户点完图标要盯着空白侧栏等,体验比 DOM 直取差一个量级。
    3. 会带误解。答辩时讲“截图识别文字”,评委会默认你在绕开反爬或者没有正经解析能力;而“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 不可用时启用,非主方案
    数据流

    插件这条链路:一次点击走了 8 步

    前 3 步是插件专属;第 4 步起全部与 Playwright 通道共用同一套函数与阈值。Playwright 自己的前 3 步是「调度 → 列表页翻页 → 逐个详情抓取」,抓完直接调第 4 步的 ingest,所以下面的流程只画一遍。
    另一条关键点:插件上报的数据不享受免检待遇——同一原始池、同一流水线、同一置信阈值,低于阈值一样进人工复核队列。

    1

    页面采集

    content script 在 activeTab 上运行,只读当前页 DOM,不额外发请求、不改页面内容。用户不点图标,脚本不会主动跑。扩展端

    2

    字段抽取

    岗位名、公司、薪资、标签、职责、任职要求,加来源 URL 与抓取时间戳,落到统一字段结构,共 9 个必填项。扩展端

    3

    入队落库

    带用户 token 调 POST /api/jd/capture,写入原始数据池,标记 source=extension,方便日后单独评估这条通道的质量。服务端

    4

    治理

    降噪、SimHash 去重、抄袭比对、时滞过滤。与爬虫数据共用同一套阈值配置,避免出现“人上来的就松一点”的双标准。治理流水线

    5

    结构化抽取

    大模型抽出岗位—技能—证书—经验年限,输出严格 JSON,字段缺失时置 null 而不是猜。LLM

    6

    交叉校验

    与其他来源的同岗位做比对,置信度低于 0.80 的一律进人工复核中台,通过后才允许写图。这是赛题“抑制幻觉”的直接落点。质控

    7

    写入图谱

    岗位节点 upsert、技能边 MERGE,记录版本号并支持回滚。重复捕获只更新不新增,避免图谱被同一条 JD 撑胖。Neo4j

    8

    回流

    侧边栏诊断、Web 端完整报告、小程序赛道订阅提醒、Agent 的图谱问答,全部读同一份图谱,口径一致。三端

    功能设计

    只做五个功能,多一个都是负担

    五个功能全部复用已有后端能力,扩展端本身不写算法。每个功能都对应一个已经在系统里存在的模块。

    一键捕获当前 JD

    数据源

    支持 BOSS 直聘、猎聘、智联、拉勾的岗位详情页。按域名匹配站点适配器,匹配失败走通用回退(标题取 h1、薪资用正则、职责取“岗位职责”段),保证换版也不至于完全抽不到。

    侧栏即时人岗诊断

    C 端体验

    捕获后立刻拉一次轻量诊断:匹配分数、已具备技能、缺口与权重、三步补学顺序。复用已有的细粒度差距分析,只把输出裁剪成侧栏能放下的粒度。

    图谱轻问答

    Agent

    侧栏底部一个输入框,问“这个岗位的技能前置依赖是什么”“这个方向还会衍生哪些岗位”。走 GraphRAG 多跳推理,回答附图谱链路证据,可解释性直接落到界面上。

    收藏与档案同步

    增长

    一键收藏写回平台个人档案,Web 与小程序同步可见。用户下次打开小程序时,订阅的赛道已经有更新提醒,形成一次点击之后的长期回访理由。

    脏数据标注上报

    质控

    用户可标记“已过期 / 重复抄袭 / 信息残缺”。标注只作为弱信号回流治理模块,权重低于爬虫源,不能单独决定一条数据的去留。

    落地排期

    5 天能跑通 Demo,但只在主线有空档时才动

    按“如果最后还有时间”的前提排的:先 P0 跑通单站闭环,能演示就停手;P1/P2 是锦上添花,不达标不影响主线交付。

    第 1–2 天 · P0

    单站闭环

    MV3 骨架、猎聘单站选择器、capture 接口、侧栏静态诊断。验收标准:点图标 → 侧栏出分数与缺口,图谱能查到这条岗位。

    第 3–4 天 · P1

    体验补齐

    图谱问答、收藏同步、BOSS 直聘适配。验收标准:两个站点都能捕获,问答能返回带链路的结果。

    第 5 天 · P2

    打磨与埋点

    标注上报、其余两站、通道质量统计(扩展来源的抽取准确率单独出报表)。验收标准:能回答“这条通道到底贡献了多少新岗位”。

    // 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" }
    }
    activeTab 的语义是:只有用户点了图标,才能读当前标签页;换一个标签页权限自动失效。这也是答辩时讲合规最好用的一条。
    // 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 });
    });
    q / qa / block 是三个小工具函数:取单个文本、取一组文本、按小标题取一段正文。真实实现里 block 用“找到标题节点 → 收集后续兄弟节点文本”,比固定 class 更抗改版。
    // 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 } });
    }
    三个动作全部由 onClicked 触发:没有定时器、没有后台轮询、没有启动即抓取。这是“用户主动授权”与“静默采集”的分界线。
    # 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])
    注意 pipeline.ingest 的参数只差 source:插件传 source=extension,爬虫传 source=crawler:站点名。有这个标记,才能单独统计“插件通道到底贡献了多少新岗位”。
    答辩预案

    评委最可能问的四个问题

    点开看答案。这四条如果答不上来,建议这个加分项先别上。

    这和爬虫有什么区别?会不会被判成违规采集?
    三条边界必须当场说清:用户显式点击才触发(activeTab 权限,换标签页即失效);只读当前这一个页面,不翻页、不批量、不绕频控和验证码;数据用途与删除入口写进隐私说明,用户可撤回。后端爬虫本身按 robots 与限速跑,插件是“人机协同采样”,两者不是同一类行为。
    用户乱点、错标会不会污染图谱?
    用户只能提交当前页面,不能改字段,所以污染面被限制在“这条 JD 本身”。之后它仍要过同一套去重、交叉校验与置信门槛;标注反馈只作为弱信号,权重低于爬虫源。等于把不可控的输入挡在图谱之外。
    为什么不直接做 H5 或小程序?
    场景在浏览器里:用户正看着一条 JD 做判断,跳转就是打断。更关键的是技术上做不到——H5 读不到别家页面的 DOM,只有扩展在同一上下文里才能拿到字段。这不是偏好问题,是能力边界。
    会不会拖垮主线开发?
    插件只新增两个接口和三个前端文件,算法全部复用。P0 两天可跑通单站闭环,跑不通就当没做,主线交付不受影响。这也是它被放在“备选加分项”而不是核心功能里的原因。
    边界

    四件绝对不做的事

    这几条只要碰一条,加分项就变成扣分项,而且很难在答辩里圆回来。

    01自动投递简历、自动打招呼、自动私信 —— 触及平台规则与账号安全,且属于代替用户做决定。
    02后台静默采集、定时翻页、启动即抓 —— 一旦被认定自动化抓取,整个项目的合规叙事就崩了。
    03采集简历、联系方式等个人信息 —— 超出场景必要范围,最小化原则不成立。
    04为绕过登录、验证码、频控做任何变通 —— 这条没有任何解释空间。

    一句话写进文档

    新增浏览器扩展作为交互式数据采集终端,构建“自动化批量爬虫 + 用户主动增量上报”双数据源,提升新兴岗位发现的时效性,同时为求职者提供沉浸式的 JD 解读与人岗缺口诊断。

    建议插入位置:服务支持终端平台(作为第三个终端)· 核心功能列表(五条里各挂一句)· 竞品对比(对标 BOSS / 猎聘的插件生态)· 任务安排与时间规划(排在 P2,注明“视主线进度启用”)。
    备选池

    这个加分项在备选池里的位置

    下面这张表建议你直接在文档里维护,用于答辩前决定“还剩多少时间、上哪一项”。

    备选加分项工作量评委感知度风险与前置条件
    浏览器扩展 JD 捕获约 5 天高(现场可点)站点选择器易变;不上架商店,本地加载演示
    数字人模拟面试约 4 天高依赖语音链路稳定性,网络抖动影响观感
    产业趋势监测报表约 3 天中(偏 G 端)数据口径要能自证,别用截图凑数
    学习打卡与成长台账约 2 天中需要小程序端配合,跨端联调成本