Get Jobs 自动化求职平台深度技术剖析

1. 整体介绍

1.1 项目概览与现状

Get Jobs(工作无忧) 是一个基于 Java Spring Boot 构建的自动化、智能化求职辅助系统。该项目通过浏览器自动化技术模拟真实用户行为,实现在多个主流招聘平台(Boss直聘、猎聘、51job、智联招聘)上的简历批量筛选与智能投递

  • 项目地址
    • GitHub(国际):https://github.com/loks666/get_jobs
    • Gitee(国内镜像):https://gitee.com/lok666/get_jobs
  • 项目热度:截至分析时,项目在GitHub上拥有超过 2500 Stars400+ Forks,位于 Trendshift 趋势榜,显示出其在技术求职者社区中的广泛关注度和实际需求。
  • 项目定位:一个面向开发者的、高度可配置的求职自动化工具,旨在通过技术手段提升求职效率,将求职者从重复、机械的岗位搜索与投递操作中解放出来。

1.2 核心功能与视觉呈现

项目采用 前后端分离 架构,提供基于Web的图形化管理界面(GUI),极大降低了使用门槛。

主要功能模块可视化

  1. 多平台账户管理与持久登录:通过Cookie持久化,实现单次扫码,多日免登。
    在这里插入图片描述

  2. 智能化岗位筛选:基于配置的薪资、经验、公司规模等条件,结合实时爬取与解析的岗位数据进行过滤。
    在这里插入图片描述

  3. AI驱动的个性化沟通:集成大模型API,根据职位描述(JD)自动生成定制化的打招呼语。
    在这里插入图片描述

  4. 自动化投递流水线:从搜索、翻页、点击、弹窗处理到消息发送,实现全流程自动化。
    在这里插入图片描述

  5. 投递管理与数据分析:所有操作记录存入SQLite本地数据库,支持黑名单管理、投递状态追踪。

1.3 问题、人群与场景分析

面临的核心问题

  1. 效率瓶颈:技术岗位海投需要重复执行“搜索-浏览-判断-投递”的循环,耗时耗力。
  2. 信息过载:大量岗位信息中掺杂着不匹配的职位、不活跃的HR、外包或猎头职位,手动筛选成本高。
  3. 沟通同质化:批量投递时难以针对每个JD撰写个性化的开场白,导致回复率低。
  4. 平台规则限制:各招聘平台有反爬机制、每日投递上限、操作频率限制等,手动操作易触及边界。

目标用户与场景

  • 主动求职期的开发者:尤其是需要短期内大量投递简历、寻求面试机会的求职者。
  • 多平台使用者:同时在Boss直聘、猎聘等多个平台维护简历和投递的用户。
  • 追求效率的技术人员:希望通过自动化脚本解决重复性工作问题的工程师。

1.4 解决方案与技术演进

传统方式:求职者手动在各个平台App或网站上进行重复操作。缺点明显:效率低下、易疲劳、难以执行复杂的多条件筛选策略、无法进行数据化管理和复盘。

Get Jobs 的新方式

  1. 流程标准化:将求职投递抽象为可配置、可编程的标准化流水线。
  2. 决策数据化:利用程序实时解析页面数据(薪资、公司信息、HR活跃度),应用过滤规则,使投递决策基于客观数据而非主观粗略判断。
  3. 操作智能化:引入AI为沟通环节赋能,提升初次互动的质量。
  4. 状态持久化:所有投递记录、公司黑名单本地化存储,形成个人的“求职知识库”,避免重复踩坑。

优点

  • 效率提升:程序可7x24小时执行预设的搜索和投递任务,解放用户时间。
  • 精准度提高:通过多维度过滤规则,投递匹配度更高的岗位。
  • 策略可复用:成功的筛选和投递配置可保存并重复使用,或分享给他人。
  • 过程可追溯:所有操作有日志和数据库记录,便于分析和优化投递策略。

1.5 商业价值与成本效益估算

价值生成逻辑

  1. 用户时间价值转换:假设一名开发者时薪为 200元,手动完成100次高质量投递(包含搜索、筛选、沟通)需耗时约 10小时,时间成本为2000元。使用本工具可将此过程压缩至近乎零耗时(仅需配置和启动),工具为用户“创造”了约2000元的时间价值。
  2. 机会成本优化:通过更广的覆盖和更精准的投递,理论上能获得更多的面试机会,缩短求职周期,其带来的潜在收益(更早入职获得薪资)远大于工具的使用成本。
  3. 代码与维护成本:项目代码规模约万行,按市场开发成本估算,初步实现成本在 10-20万元人民币 量级。但作为开源项目,其价值通过社区协作和免费分发被放大。
  4. 覆盖问题空间效益:它解决的是一个普遍存在的、高频的、高痛点的非生产性效率问题。虽然无法直接货币化(项目已采用禁止商业化的开源协议),但其创造的社会效率提升总和是显著的。

结论:Get Jobs 作为一个工具,其商业价值主要体现在为个体使用者节省的时间和提升的求职成功率上,是一种典型的“生产力工具”价值模型。其开源属性限制了直接商业变现,但通过社区口碑和开发者影响力,可间接为作者带来技术声誉等无形价值。

2. 详细功能拆解(产品+技术视角)

功能模块 产品视角(用户价值) 技术视角(实现方案)
1. 统一配置管理 一处配置,多平台生效。降低多平台维护复杂度。 基于YAML(application.yaml)和Java Config的集中化配置管理。各平台配置类(如BossConfig)通过@ConfigurationProperties绑定。前端GUI将配置可视化并持久化至后端。
2. 多平台适配引擎 用户无需关心各平台网页结构差异,使用统一流程。 抽象层设计:各平台(Boss, Liepin等)均实现统一的JobPlatform接口(概念上)。执行器模式:每个平台是一个独立的Component(如Boss类),封装了该平台特有的选择器、API接口和操作逻辑。
3. 智能过滤中枢 自动排除“坏机会”,提升投递质量与心情。 规则引擎:在岗位数据解析后,依次应用多重过滤规则链:
1. 文本黑名单匹配(公司名、职位名、HR头衔)。
2. 薪资范围计算与匹配(解析8-12K并对比期望范围)。
3. HR活跃度分析(解析3月内活跃等文本)。
4. 岗位状态判断(基于接口返回的jobStatusDesc)。
4. AI融合通信 让每次打招呼都“看起来像精心准备的”,提高HR打开率和回复率。 策略模式
- 默认策略:使用配置的固定招呼语。
- AI策略:调用AiService,将JD、个人简介、岗位名称拼接为Prompt,请求大模型API(如GPT),生成个性化文本。在Boss.resumeSubmission()中根据开关选择策略。
5. 状态管理与反爬应对 保证自动化流程的稳定性和账号安全。 状态机与鲁棒性设计
- 登录状态维护:定期检查页面元素判断是否掉线,触发重新扫码流程。
- 操作频率模拟PlaywrightUtil.sleep() 模拟人类操作间隔,添加随机延迟。
- 异常处理:对元素找不到、请求超时等异常有完备的try-catch和降级处理。
- 验证码/滑块处理waitForSliderVerify() 方法检测到验证页面时,暂停程序并等待用户手动处理。
6. 数据持久化与洞察 积累求职数据,用于复盘和策略优化。 本地数据库:使用轻量级SQLite(getjobs.db)。
- boss_job_data:存储所有遇到的岗位详细信息。
- blacklist:存储公司、职位、HR黑名单。
- MyBatis-Plus:作为ORM框架,简化数据库操作。数据为后续分析(如哪些行业回复率高)提供可能。
7. 实时通知与监控 让用户脱离电脑也能掌握投递进度。 观察者模式:核心投递流程中发布关键事件。WebHook通知器监听这些事件,通过HTTP请求将格式化消息(如“已成功投递XX公司XX岗位”)发送至配置的企业微信机器人,实现实时推送。

3. 核心技术难点挖掘

  1. 动态网页的反爬与逆向工程

    • 难点:招聘平台普遍使用JavaScript动态渲染内容,数据可能通过Ajax接口加载,并伴有字体反爬(如Boss直聘的薪资数字字体加密)、请求签名等保护措施。
    • 解决方案:项目选用 Playwright 而非Selenium,因其能更好地模拟真实浏览器环境,自动等待动态加载,并可直接拦截和 Mock 网络请求(page.onResponse),从而精准获取API返回的JSON数据,绕过字体反爬问题。Boss.decodeSalary()方法展示了早期应对字体反爬的方案。
  2. 多平台抽象与稳定适配

    • 难点:各招聘平台UI设计、交互流程、接口规范差异巨大,且任何一方的页面改版都可能导致脚本失效。
    • 解决方案:采用 平台独立类封装 而非强行抽象统一接口。每个平台类内部高度自治,负责自身的登录、搜索、解析、投递全链路。通过PlaywrightUtil提供公共工具方法(如智能等待、截图)。这种“高内聚、松耦合”的设计降低了平台间变更的相互影响。
  3. 自动化流程的鲁棒性设计

    • 难点:网络波动、元素加载延迟、意外弹窗、平台交互流程变更都会导致自动化中断。
    • 解决方案
      • 显式等待与重试机制:大量使用waitForSelector和循环检查,而非固定sleep
      • 异常恢复:关键步骤被try-catch包裹,发生异常时记录日志并尝试跳过当前岗位或执行降级方案,而非整体崩溃。
      • 状态检测:如isLoginRequired()方法,在关键操作前检查登录状态。
  4. AI集成的内容安全与效果把控

    • 难点:如何设计Prompt使AI生成的招呼语既个性化又不突兀?如何控制API调用成本?
    • 解决方案:提供可配置的Prompt模板(AiEntity.prompt),让用户可以将自己的简历亮点变量化插入。在generateAiMessage()方法中,如果AI调用失败或返回无意义内容,则自动降级到配置的默认招呼语,保证流程可用性。
  5. 账号安全与风控平衡

    • 难点:过于频繁或规律的自动化操作极易触发平台风控,导致账号被限制甚至封禁。
    • 解决方案:项目在README和代码中多次强调辅助定位(“不要依赖程序投递Boss!!!”)。技术手段上,通过shouldStopCallback支持用户随时中断,模拟人类操作间隔,并在检测到异常(如跳转至验证页面)时主动暂停等待人工干预。

4. 详细设计图

4.1 系统架构图

基础设施层

数据访问层

业务逻辑层

应用核心层

用户交互层

Web图形界面/Vue/React

配置文件 .env, application.yaml

Spring Boot 应用

定时任务/立即执行触发器

企业微信通知服务

Boss直聘执行器

猎聘执行器

51job执行器

智联执行器

智能过滤引擎

AI服务

MyBatis-Plus ORM

SQLite数据库

Playwright 驱动

Chromium 浏览器实例

HTTP Client API调用

4.2 Boss直聘单次投递核心序列图

WebHook AI服务 数据库 网络监听 浏览器 Playwright Page Boss执行器 用户/调度器 WebHook AI服务 数据库 网络监听 浏览器 Playwright Page Boss执行器 用户/调度器 opt [配置了图片简历] opt [通过过滤] execute() prepare() //加载黑名单 navigate(搜索URL) 加载页面,滚动触底 拦截岗位列表API 获取岗位数据 点击第i个岗位卡片 触发点击,加载详情 拦截岗位详情API (job/detail.json) 返回详情JSON 解析并入库岗位数据 应用过滤规则 打开岗位详情页新Tab 加载详情页 点击"立即沟通" generateAiMessage(JD) 返回个性化招呼语 输入招呼语并发送 定位并上传 resume.jpg updateDeliveryStatus("已投递") 发送成功通知 返回本次投递结果

4.3 Boss类核心协作简图

配置依赖

数据操作

AI调用

工具使用

生成/传递

Boss

-Page page

-BossConfig config

-BossService bossService

-AiService aiService

-Set<String> blackCompanies

-ProgressCallback progressCallback

+prepare()

+execute() : int

+updateBlacklistFromChats() : Map

-postJobByCity(String cityCode)

-resumeSubmission(String keyword, Job job)

-generateAiMessage(...) : String

BossConfig

+List<String> cityCode

+List<String> keywords

+String sayHi

+Boolean enableAI

+Boolean filterDeadHR

+List<Integer> expectedSalary

// ... 其他配置字段

BossService

+ensureBossDataColumnOrder()

+getBlackCompanies() : Set<String>

+addBlacklist(String type, String value)

+insertBossJob(BossJobDataEntity entity)

+updateDeliveryStatus(...)

AiService

+getAiConfig() : AiEntity

+sendRequest(String prompt) : String

PlaywrightUtil

// ... 其他工具方法

+sleep(int seconds)

Job

+String jobName

+String salary

+String companyName

+String jobInfo

// ...

5. 核心函数解析

5.1 Boss.execute() - 主投递流程控制器

public int execute() {
    // 遍历用户配置的所有目标城市
    for (String cityCode : config.getCityCode()) {
        // 关键点1:支持用户随时取消,提升可控性
        if (shouldStopCallback != null && Boolean.TRUE.equals(shouldStopCallback.get())) {
            progressCallback.accept("用户取消投递", 0, 0);
            break; // 中断循环
        }
        // 针对每个城市执行投递
        postJobByCity(cityCode);
        // 再次检查,确保及时响应取消指令
        if (shouldStopCallback != null && Boolean.TRUE.equals(shouldStopCallback.get())) {
            progressCallback.accept("用户取消投递", 0, 0);
            break;
        }
    }
    // 返回总投递数量,用于结果展示
    return resultList.size();
}

技术要点

  • 流程编排:该方法作为顶层协调者,按城市->关键词的维度组织投递任务。
  • 异步控制:通过shouldStopCallback这个Supplier<Boolean>函数式接口,接收来自GUI或外部的停止信号,实现优雅中断,避免强制终止导致的状态不一致。
  • 进度反馈:通过ProgressCallback接口实时回传进度信息到前端,实现可视化监控。

5.2 resumeSubmission() - 单岗位投递核心

这是整个项目最复杂、最核心的函数之一,实现了从岗位详情页到完成沟通的全流程。

private void resumeSubmission(String keyword, Job job) {
    // 1. 前置检查:停止信号与调试模式
    if (shouldStopCallback.get()) { return; }
    if (config.getDebugger()) { log.info(...); return; }

    // 2. 导航到详情页(通过新Tab打开,隔离主页面状态)
    String href = page.locator("a.more-job-btn").first().getAttribute("href");
    String detailUrl = "https://www.zhipin.com" + href;
    Page detailPage = page.context().newPage(); // 创建新页面上下文
    detailPage.navigate(detailUrl);
    PlaywrightUtil.sleep(1); // 必要等待,等待页面框架加载

    // 3. 定位并点击“立即沟通”按钮(需处理动态加载)
    Locator chatBtn = detailPage.locator("a.btn-startchat, a.op-btn-chat");
    // 关键点:循环等待,增加鲁棒性
    for (int i = 0; i < 5; i++) {
        if (shouldStopCallback.get()) { detailPage.close(); return; }
        if (chatBtn.count() > 0 && chatBtn.first().textContent().contains("立即沟通")) {
            break;
        }
        PlaywrightUtil.sleep(1); // 重试间隔
    }
    chatBtn.first().click();

    // 4. 等待聊天输入框就绪
    Locator inputLocator = detailPage.locator("div#chat-input.chat-input[contenteditable='true'], textarea.input-area");
    // ... 类似的重试等待逻辑 ...

    // 5. 生成并输入消息(AI或默认)
    String message = config.getEnableAI() ? generateAiMessage(keyword, job.getJobName(), job.getJobInfo()) : config.getSayHi();
    // 关键点:区分 contenteditable div 和 textarea 两种输入框的处理方式
    if (input.evaluate("el => el.tagName.toLowerCase()").equals("textarea")) {
        input.fill(message); // 标准textarea
    } else {
        // 对可编辑div,需设置innerText并触发input事件
        input.evaluate("(el, msg) => { el.innerText = msg; el.dispatchEvent(new Event('input')); }", message);
    }

    // 6. 点击发送按钮
    detailPage.locator("div.send-message, button.btn-send").first().click();

    // 7. (可选)发送图片简历
    if (Boolean.TRUE.equals(config.getSendImgResume())) {
        sendImageResume(detailPage);
    }

    // 8. 资源清理与状态更新
    detailPage.close(); // 关闭详情Tab,避免资源泄漏
    bossService.updateDeliveryStatus(encryptId, encryptUserId, "已投递"); // 更新数据库
    resultList.add(job); // 记录本次成功投递
}

设计亮点与难点

  • 多Tab管理:通过newPage()在新Tab中操作,不影响主搜索列表页的状态,是处理此类单页应用(SPA)交互的常见模式。
  • 元素等待策略:采用“循环检查+超时”而非绝对等待,提高了脚本的适应性和速度。
  • 输入框兼容性处理:识别并兼容了Web应用中两种常见的输入框实现方式,展示了应对网页多样性的能力。
  • 资源生命周期管理:显式close()掉打开的detailPage,防止浏览器上下文资源堆积,这对长时间运行的任务至关重要。

5.3 generateAiMessage() - AI集成策略

private String generateAiMessage(String keyword, String jobName, String jd) {
    // 1. 获取AI配置(API地址、Key、Prompt模板)
    AiEntity aiConfig = aiService.getAiConfig();
    String introduce = aiConfig.getIntroduce(); // 用户预设的个人简介
    String promptTemplate = aiConfig.getPrompt(); // 可配置的Prompt模板

    // 2. 构建最终请求大模型的Prompt
    String requestMessage;
    if (promptTemplate != null) {
        // 使用用户自定义模板,注入变量
        requestMessage = String.format(promptTemplate, introduce, keyword, jobName, jd, config.getSayHi());
    } else {
        // 使用默认的、相对简单的Prompt
        requestMessage = buildDefaultPrompt(introduce, keyword, jobName, jd);
    }

    // 3. 调用AI服务并处理结果
    try {
        String result = aiService.sendRequest(requestMessage);
        // 关键点:对AI返回结果进行兜底判断。
        // 例如,某些情况下AI可能返回"False"或无效内容,此时降级为默认招呼语。
        if (result == null || result.toLowerCase().contains("false")) {
            return config.getSayHi();
        }
        return result;
    } catch (Exception e) {
        // 网络或API错误,确保流程不中断,降级处理
        log.warn("AI请求失败,使用原有打招呼语: {}", e.getMessage());
        return config.getSayHi();
    }
}

架构思想

  • 策略模式与模板方法:将AI生成逻辑抽象为一个可替换的策略。promptTemplate的存在使得生成逻辑高度可定制,而不需要修改代码。
  • 容错与降级:整个AI调用环节被视作一个“增强功能”而非核心路径。任何失败(超时、返回异常、API限额)都会无缝降级到预设的默认招呼语,保证了核心投递流程的可用性
  • 关注点分离AiService专门负责与AI API的通信(身份验证、请求构造、错误重试),Boss类只负责业务逻辑的组装和结果的消费,符合单一职责原则。

5.4 总结与同类方案对比

Get Jobs 在同类自动化求职工具中,其技术实现较为成熟和完整

对比维度 Get Jobs 其他常见方案(如Python脚本+Selenium)
架构完整性 完整的Spring Boot后端 + 前端GUI + 本地数据库,产品化程度高。 通常是单脚本,缺乏配置管理、状态持久化和用户界面。
技术选型 Playwright (优于传统Selenium),抗反爬能力强;MyBatis-Plus管理数据。 多使用Selenium,应对动态页面和反爬较为吃力。
功能深度 多平台支持、智能过滤、AI集成、黑名单管理、实时通知,功能矩阵丰富。 功能相对单一,主要集中在“自动投递”基础环节。
可维护性 代码结构清晰,模块化好,配置驱动,易于扩展新平台或修改现有逻辑。 脚本往往结构松散,逻辑与配置混杂,维护成本高。
使用门槛 提供GUI,对非开发者友好。但环境配置(JDK21, Gradle)仍有技术要求。 需要用户具备一定的编程和命令行操作能力。
风险控制 在代码和文档中多次强调风险,提供了停止回调、调试模式等控制机制。 风险控制依赖开发者自身意识,脚本可能因平台改版而突然失效。

结论:Get Jobs 代表了此类工具向产品化、工程化、智能化发展的趋势。它不仅仅是一个脚本,而是一个考虑了用户体验、系统稳定性、数据沉淀和长期可维护性的软件工程作品。虽然使用自动化工具存在固有的平台规则风险,但项目在技术实现上的严谨性和对风险的大量提示,为有意愿通过技术提升效率的求职者提供了一个强大且相对可靠的选项。它的成功也印证了,即使在高度动态和受限的Web环境中,通过合理的技术选型和架构设计,依然可以构建出稳定、高效的自动化解决方案。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐