找回密码
 注册
搜索
查看: 38|回复: 0

开源分享!GPT自动投简历,一周斩获3个offer!

[复制链接]
发表于 4 天前 | 显示全部楼层 |阅读模式
​非常好的一篇文章,转载分享给大家!
原贴:juejin.cn/post/7320949203542409231(crm:不要秃头啊)

一、前言
最近在 GitHub 上发现了一个非常有意思的项目:GitHub链接。

该作者巧妙地结合 GPT 和 RPA 技术,打造了一个自动投简历助手。这是原作者分享的效果展示视频:B站视频链接。

然而,由于原项目存在以下问题:

代码使用 Python 编写,对于前端开发者不够友好。
运行该项目需要充值 OpenAI 账户,而且只支持使用国外的信用卡,国内用户想充钱都没地。
运行该项目还需要配置代理,对一些用户而言可能不太友好。
折腾无果,遂决定使用 Node.js 重新实现该项目,并且完全免费、一键运行,无需设置代理:GitHub项目地址。

在这个寒冷的招聘季,这个脚本能为您提供一些帮助,为您带来一些温暖。如果您觉得这个项目有价值,希望您能帮忙点个赞,将不胜感激。

二、整体思路
首先,我们会使用 selenium-webdriver来模拟用户行为,该库是一个强大的自动化测试工具。它能够通过编程方式控制浏览器交互,通常用于自动化测试、网页抓取以及模拟用户交互等任务。

用 selenium-webdriver模拟用户打开浏览器窗口,并导航至直聘网的主页。
等待页面加载完成,找到登录按钮的 DOM 节点,模拟用户点击触发登录,等待用户扫码操作。
在用户成功扫码登录后,进入招聘信息列表页面。
遍历招聘信息列表,对每一项进行以下操作:
点击招聘信息,找到该项招聘信息的职位描述信息
结合上传的简历信息与招聘信息传递给 GPT,等待 GPT 的响应
在 GPT 响应后,点击“立即沟通”按钮,进入沟通聊天界面
在聊天界面中找到输入框,将 GPT 返回的信息填入聊天框,并触发发送事件
返回招聘信息列表页面,点击下一项招聘信息
重复上述步骤,遍历下一项招聘信息的职位描述信息

<顺便吆喝一句,技术大厂,前后端测试年前捞人,待遇给的还可以,感兴趣可以试试>

三、具体实现
3.1、获取免费的 API Key 并初始化 OpenAI 客户端
做过 GPT 开发的应该知道,调用 GPT 的接口是要付费的,而且充值过程异常繁琐,需要使用境外银行卡。

为了简化这个过程,我在 GitCode 上找到了一个提供免费 API_KEY 的项目,只需使用 GitHub 账户登录即可轻松领取。

这样你就可以用免费的 API_KEY 来初始化 OpenAI 客户端。

  1. // 初始化OpenAI客户端
  2. const openai = new OpenAI({
  3.   // 代理地址,这样国内用户就可以访问了
  4.   baseURL: "https://api.chatanywhere.com.cn",
  5.   apiKey: "你的apiKey",
  6. });
复制代码


3.2、模拟用户打开浏览器并前往主页
在这一步中,我们要实现的是打开浏览器并导航至指定的 URL。具体操作就是调用 ​​​​​​​selenium-webdriver 的 API,直接上代码:

  1. const { Builder, By, until } = require("selenium-webdriver");
  2. const chrome = require("selenium-webdriver/chrome");

  3. // 全局 WebDriver 实例
  4. let driver;

  5. // 使用指定的选项打开浏览器
  6. async function openBrowserWithOptions(url, browser) {
  7.   const options = new chrome.Options();
  8.   options.addArguments("--detach");

  9.   if (browser === "chrome") {
  10.     // 初始化一个谷歌浏览器客户端
  11.     driver = await new Builder()
  12.       .forBrowser("chrome")
  13.       .setChromeOptions(options)
  14.       .build();
  15.     // 全屏打开浏览器
  16.     await driver.manage().window().maximize();
  17.   } else {
  18.     throw new Error("不支持的浏览器类型");
  19.   }

  20.   await driver.get(url);

  21.   // 等待直到页面包含登录按钮dom
  22.   const loginDom = By.xpath("//*[@id='header']/div[1]/div[3]/div/a");
  23.   await driver.wait(until.elementLocated(loginDom), 10000);
  24. }

  25. // 主函数
  26. async function main(url, browserType) {
  27.   try {
  28.     // 打开浏览器
  29.     await openBrowserWithOptions(url, browserType);

  30.   } catch (error) {
  31.     console.error(`发生错误: ${error}`);
  32.   }
  33. }

  34. const url =
  35.   "https://www.zhipin.com/web/geek/job-recommend?ka=header-job-recommend";
  36. const browserType = "chrome";

  37. main(url, browserType);
复制代码


3.3、找到登录按钮的DOM节点并点击
这一步中我们需要找到 登录按钮 的 DOM 节点,然后模拟点击登录。



  1. // 省略上一步的代码

  2. // 点击登录按钮,并等待登录成功
  3. async function logIn() {
  4.   // 点击登录
  5.   const loginButton = await driver.findElement(
  6.     By.xpath("//*[@id='header']/div[1]/div[3]/div/a")
  7.   );
  8.   await loginButton.click();

  9.   // 等待微信登录按钮出现
  10.   const xpathLocatorWechatLogin =
  11.     "//*[@id='wrap']/div/div[2]/div[2]/div[2]/div[1]/div[4]/a";
  12.   await driver.wait(
  13.     until.elementLocated(By.xpath(xpathLocatorWechatLogin)),
  14.     10000
  15.   );

  16.   const wechatButton = await driver.findElement(
  17.     By.xpath("//*[@id='wrap']/div/div[2]/div[2]/div[2]/div[1]/div[4]/a")
  18.   );
  19.   // 选择微信扫码登录
  20.   await wechatButton.click();

  21.   const xpathLocatorWechatLogo =
  22.     "//*[@id='wrap']/div/div[2]/div[2]/div[1]/div[2]/div[1]/img";
  23.   await driver.wait(
  24.     until.elementLocated(By.xpath(xpathLocatorWechatLogo)),
  25.     10000
  26.   );

  27.   // 等待用户扫码,登录成功
  28.   const xpathLocatorLoginSuccess = "//*[@id='header']/div[1]/div[3]/ul/li[2]/a";
  29.   await driver.wait(
  30.     until.elementLocated(By.xpath(xpathLocatorLoginSuccess)),
  31.     60000
  32.   );
  33. }

  34. // 主函数
  35. async function main(url, browserType) {
  36.   try {
  37.     // 打开浏览器
  38.     // 点击登录按钮,并等待登录成功
  39. +   await logIn();

  40.   } catch (error) {
  41.     console.error(`发生错误: ${error}`);
  42.   }
  43. }
复制代码

3.4、遍历招聘信息列表
登录成功后进入到招聘信息列表页面,这一步中我们需要遍历招聘信息并依次点击,找到每一项招聘信息的职位描述信息,如图所示:



  1. [code]// 省略上一步的代码

  2. // 根据索引获取职位描述
  3. async function getJobDescriptionByIndex(index) {
  4.   try {
  5.     const jobSelector = `//*[@id='wrap']/div[2]/div[2]/div/div/div[1]/ul/li[${index}]`;
  6.     const jobElement = await driver.findElement(By.xpath(jobSelector));
  7.     // 点击招聘信息列表中的项
  8.     await jobElement.click();

  9.     // 找到描述信息节点并获取文字
  10.     const descriptionSelector =
  11.       "//*[@id='wrap']/div[2]/div[2]/div/div/div[2]/div/div[2]/p";
  12.     await driver.wait(
  13.       until.elementLocated(By.xpath(descriptionSelector)),
  14.       10000
  15.     );
  16.     const jobDescriptionElement = await driver.findElement(
  17.       By.xpath(descriptionSelector)
  18.     );
  19.     return jobDescriptionElement.getText();
  20.   } catch (error) {
  21.     console.log(`在索引 ${index} 处找不到工作。`);
  22.     return null;
  23.   }
  24. }

  25. // 主函数
  26. async function main(url, browserType) {
  27.   try {
  28.     // 打开浏览器
  29.     // 点击登录按钮,并等待登录成功
  30.     // 开始的索引
  31. +   let jobIndex = 1;

  32. +   while (true) {
  33. +     // 获取对应下标的职位描述
  34. +     const jobDescription = await getJobDescriptionByIndex(jobIndex);
  35. +     console.log(`职位描述信息/n:${jobDescription}`);
  36. +     if (jobDescription) {
  37. +        //
  38. +     }
  39. +     jobIndex += 1;
  40.     }
  41.   } catch (error) {
  42.     console.error(`发生错误: ${error}`);
  43.   }
  44. }
复制代码


接着结合上传的简历信息与招聘信息传递给 GPT,等待 GPT 的响应:

  1. // 省略上一步的代码

  2. // 读取简历信息
  3. const getResumeInfo = () => {
  4.   fs.readFile("./简历基本信息.txt", "utf8", (err, data) => {
  5.     if (err) {
  6.       console.error("读取文件时出错:", err);
  7.       return;
  8.     }
  9.     // 输出文件内容
  10.     return data;
  11.   });
  12. };

  13. // 与GPT进行聊天的函数
  14. async function chat(jobDescription) {
  15.   // 获取简历信息
  16.   const resumeInfo = getResumeInfo();
  17.   const askMessage = `你好,这是我的简历:${resumeInfo},这是我所应聘公司的要求:${jobDescription}。我希望您能帮我直接给HR写一个礼貌专业的求职新消息,要求能够用专业的语言将简历中的技能结合应聘工作的描述,来阐述自己的优势,尽最大可能打动招聘者。并且请您始终使用中文来进行消息的编写,开头是招聘负责人。这是一封完整的求职信,不要包含求职信内容以外的东西,例如“根据您上传的求职要求和个人简历,我来帮您起草一封求职邮件:”这一类的内容,以便于我直接自动化复制粘贴发送,字数控制在80字左右为宜`;
  18.   try {
  19.     const completion = await openai.chat.completions.create({
  20.       messages: [
  21.         {
  22.           role: "system",
  23.           content: askMessage,
  24.         },
  25.       ],
  26.       model: "gpt-3.5-turbo",
  27.     });

  28.     // 获取gpt返回的信息
  29.     const formattedMessage = completion.choices[0].message.content.replace(
  30.       /\n/g,
  31.       " "
  32.     );
  33.     return formattedMessage;
  34.   } catch (error) {
  35.     console.error(`gpt返回时发生错误: ${error}`);
  36.     const errorResponse = JSON.stringify({ error: String(error) });
  37.     return errorResponse;
  38.   }
  39. }

  40. // 主函数
  41. async function main(url, browserType) {
  42.   try {
  43.     // 打开浏览器
  44.     // 点击登录按钮,并等待登录成功
  45.     // 开始的索引
  46.     while (true) {
  47.       // 获取对应下标的职位描述
  48.       if (jobDescription) {
  49.         // 发送描述到聊天并打印响应
  50. +      const response = await chat(jobDescription);
  51. +      console.log("gpt给的回复", response);
  52.       }
  53.       jobIndex += 1;
  54.     }
  55.   } catch (error) {
  56.     console.error(`发生错误: ${error}`);
  57.   }
  58. }
复制代码


GPT 响应完成后,找到 立即沟通按钮 并模拟点击,此时进入沟通聊天界面,如图所示:


  1. // 省略上一步的代码

  2. // 主函数
  3. async function main(url, browserType) {
  4.   try {
  5.     // 打开浏览器
  6.     // 点击登录按钮,并等待登录成功
  7.     // 开始的索引
  8.     while (true) {
  9.       // 获取对应下标的职位描述
  10.       if (jobDescription) {
  11.         // 发送描述到聊天并打印响应
  12.         // 点击沟通按钮
  13. +       const contactButton = await driver.findElement(
  14. +         By.xpath(
  15. +           "//*[@id='wrap']/div[2]/div[2]/div/div/div[2]/div/div[1]/div[2]/a[2]"
  16. +         )
  17. +       );
  18. +       await contactButton.click();
  19.       }
  20.       jobIndex += 1;
  21.     }
  22.   } catch (error) {
  23.     console.error(`发生错误: ${error}`);
  24.   }
  25. }

  26. 此时进入到聊天界面,将 GPT 的返回信息填入到输入框中,触发发送事件。



  27. // 省略上一步的代码

  28. // 发送响应到聊天框
  29. async function sendResponseToChatBox(driver, response) {
  30.   try {
  31.     // 请找到聊天输入框
  32.     const chatBox = await driver.findElement(By.xpath("//*[@id='chat-input']"));

  33.     // 清除输入框中可能存在的任何文本
  34.     await chatBox.clear();

  35.     // 将响应粘贴到输入框
  36.     await chatBox.sendKeys(response);
  37.     await sleep(1000);

  38.     // 模拟按下回车键来发送消息
  39.     await chatBox.sendKeys(Key.RETURN);
  40.     await sleep(2000); // 模拟等待2秒
  41.   } catch (error) {
  42.     console.error(`发送响应到聊天框时发生错误: ${error}`);
  43.   }
  44. }

  45. // 主函数
  46. async function main(url, browserType) {
  47.   try {
  48.     // 打开浏览器
  49.     // 点击登录按钮,并等待登录成功
  50.     // 开始的索引
  51.     while (true) {
  52.       // 获取对应下标的职位描述
  53.       if (jobDescription) {
  54.         // 发送描述到聊天并打印响应
  55.         // 点击沟通按钮
  56.         // 等待回复框出现
  57. +       const chatBox = await driver.wait(
  58. +         until.elementLocated(By.xpath("//*[@id='chat-input']")),
  59. +         10000
  60. +       );

  61. +       // 调用函数发送响应
  62. +       await sendResponseToChatBox(driver, response);

  63. +       // 返回到上一个页面
  64. +       await driver.navigate().back();
  65. +       await sleep(2000); // 模拟等待3秒
  66.       }
  67.       jobIndex += 1;
  68.     }
  69.   } catch (error) {
  70.     console.error(`发生错误: ${error}`);
  71.   }
  72. }
复制代码

发送完成后返回招聘列表页面,以此往复。

四、最后
该项目只是简单的将简历信息结合职位信息发送给 GPT,然后用 GPT 的回复发送给招聘者,实际上并没有什么难度,意在抛砖引玉。

这里其实还有更优雅的做法,比如将个人简历传给 GPT,让 GPT 去提炼有效信息(原作者就是这么做的)。但由于 GPT-API-free 项目 并没有提供 assistant服务,实现这一点需要付费,有充值渠道的朋友可以尝试一下。

此外,对于有兴趣的朋友,还可以进一步深挖,例如:

根据职位详情进行分词权重分析,生成岗位热点词汇云图,帮助分析简历匹配度
自动过滤掉最近未活跃的 Boss 发布的信息,以免浪费每天的 100 次机会
设置过滤薪资范围,防止无效投递
自动检测上下文,排除【外派、驻场】等字眼的职位信息
...


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
高级模式
B Color Image Link Quote Code Smilies

本版积分规则

Archiver|手机版|小黑屋|52RD我爱研发网 ( 沪ICP备2022007804号-2 )

GMT+8, 2025-1-10 02:05 , Processed in 0.048090 second(s), 18 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表