Récemment j'ai découvert un projet très intéressant sur GitHub : GitHub Link.
L'auteur a intelligemment combiné les technologies GPT
et RPA
pour créer un assistant de soumission automatique de CV. Il s'agit de la vidéo de démonstration de l'effet partagée par l'auteur original : lien vidéo de la station B.
Cependant, le projet initial présentait les problèmes suivants :
Après avoir lutté en vain, j'ai décidé d'utiliser Node.js pour réimplémenter le projet. Il est entièrement gratuit et peut être exécuté en un clic sans configurer d'agent : adresse du projet GitHub.
En cette froide saison de recrutement, ce script peut vous apporter de l'aide et vous apporter un peu de chaleur. Si vous pensez que ce projet est précieux, j'espère que vous pourrez nous aider en lui attribuant une étoile, ce sera grandement apprécié.
Tout d'abord, nous utiliserons Selenium-webdriver pour simuler le comportement des utilisateurs. Cette bibliothèque est un puissant outil de test automatisé. Il permet un contrôle programmatique des interactions du navigateur et est couramment utilisé pour des tâches telles que les tests automatisés, le web scraping et la simulation des interactions des utilisateurs.
DOM
du bouton de connexion, simulez le clic de l'utilisateur pour déclencher la connexion et attendez que l'utilisateur scanne le code QR.GPT
, en attendant la réponse de GPT
GPT
a répondu, cliquez sur le bouton « Communiquer maintenant » pour accéder à l'interface de discussion de communication.GPT
dans la zone de discussion et déclenchez l'événement d'envoi.Ceux qui ont développé GPT doivent savoir que l'appel de l'interface GPT nécessite un paiement et que le processus de recharge est extrêmement lourd et nécessite l'utilisation de cartes bancaires étrangères.
Afin de simplifier ce processus, j'ai trouvé un projet sur GitCode qui fournit une API_KEY gratuite, qui peut être facilement obtenue en vous connectant avec un compte GitHub.
De cette façon, vous pouvez initialiser le client OpenAI avec l' API_KEY
gratuite.
// 初始化OpenAI客户端
const openai = new OpenAI ( {
// 代理地址,这样国内用户就可以访问了
baseURL : "https://api.chatanywhere.com.cn" ,
apiKey : "你的apiKey" ,
} ) ;
Dans cette étape, ce que nous voulons réaliser est d’ouvrir le navigateur et d’accéder à l’URL spécifiée. L'opération spécifique consiste à appeler l'API de selenium-webdriver et à saisir directement le code :
const { Builder , By , until } = require ( "selenium-webdriver" ) ;
const chrome = require ( "selenium-webdriver/chrome" ) ;
// 全局 WebDriver 实例
let driver ;
// 使用指定的选项打开浏览器
async function openBrowserWithOptions ( url , browser ) {
const options = new chrome . Options ( ) ;
options . addArguments ( "--detach" ) ;
if ( browser === "chrome" ) {
// 初始化一个谷歌浏览器客户端
driver = await new Builder ( )
. forBrowser ( "chrome" )
. setChromeOptions ( options )
. build ( ) ;
// 全屏打开浏览器
await driver . manage ( ) . window ( ) . maximize ( ) ;
} else {
throw new Error ( "不支持的浏览器类型" ) ;
}
await driver . get ( url ) ;
// 等待直到页面包含登录按钮dom
const loginDom = By . xpath ( "//*[@id='header']/div[1]/div[3]/div/a" ) ;
await driver . wait ( until . elementLocated ( loginDom ) , 10000 ) ;
}
// 主函数
async function main ( url , browserType ) {
try {
// 打开浏览器
await openBrowserWithOptions ( url , browserType ) ;
} catch ( error ) {
console . error ( `发生错误: ${ error } ` ) ;
}
}
const url =
"https://www.zhipin.com/web/geek/job-recommend?ka=header-job-recommend" ;
const browserType = "chrome" ;
main ( url , browserType ) ;
Dans cette étape, nous devons trouver le nœud DOM du bouton de connexion , puis simuler un clic pour nous connecter.
// 省略上一步的代码
// 点击登录按钮,并等待登录成功
async function logIn ( ) {
// 点击登录
const loginButton = await driver . findElement (
By . xpath ( "//*[@id='header']/div[1]/div[3]/div/a" )
) ;
await loginButton . click ( ) ;
// 等待微信登录按钮出现
const xpathLocatorWechatLogin =
"//*[@id='wrap']/div/div[2]/div[2]/div[2]/div[1]/div[4]/a" ;
await driver . wait (
until . elementLocated ( By . xpath ( xpathLocatorWechatLogin ) ) ,
10000
) ;
const wechatButton = await driver . findElement (
By . xpath ( "//*[@id='wrap']/div/div[2]/div[2]/div[2]/div[1]/div[4]/a" )
) ;
// 选择微信扫码登录
await wechatButton . click ( ) ;
const xpathLocatorWechatLogo =
"//*[@id='wrap']/div/div[2]/div[2]/div[1]/div[2]/div[1]/img" ;
await driver . wait (
until . elementLocated ( By . xpath ( xpathLocatorWechatLogo ) ) ,
10000
) ;
// 等待用户扫码,登录成功
const xpathLocatorLoginSuccess = "//*[@id='header']/div[1]/div[3]/ul/li[2]/a" ;
await driver . wait (
until . elementLocated ( By . xpath ( xpathLocatorLoginSuccess ) ) ,
60000
) ;
}
// 主函数
async function main ( url , browserType ) {
try {
// 打开浏览器
// 点击登录按钮,并等待登录成功
+ await logIn ( ) ;
} catch ( error ) {
console . error ( `发生错误: ${ error } ` ) ;
}
}
Après vous être connecté avec succès, vous entrerez dans la page de liste des informations de recrutement. Dans cette étape, nous devons parcourir les informations de recrutement et cliquer pour trouver les informations de description de poste de chaque information de recrutement, comme le montre la figure :
// 省略上一步的代码
// 根据索引获取职位描述
async function getJobDescriptionByIndex ( index ) {
try {
const jobSelector = `//*[@id='wrap']/div[2]/div[2]/div/div/div[1]/ul/li[ ${ index } ]` ;
const jobElement = await driver . findElement ( By . xpath ( jobSelector ) ) ;
// 点击招聘信息列表中的项
await jobElement . click ( ) ;
// 找到描述信息节点并获取文字
const descriptionSelector =
"//*[@id='wrap']/div[2]/div[2]/div/div/div[2]/div/div[2]/p" ;
await driver . wait (
until . elementLocated ( By . xpath ( descriptionSelector ) ) ,
10000
) ;
const jobDescriptionElement = await driver . findElement (
By . xpath ( descriptionSelector )
) ;
return jobDescriptionElement . getText ( ) ;
} catch ( error ) {
console . log ( `在索引 ${ index } 处找不到工作。` ) ;
return null ;
}
}
// 主函数
async function main ( url , browserType ) {
try {
// 打开浏览器
// 点击登录按钮,并等待登录成功
// 开始的索引
+ let jobIndex = 1 ;
+ while ( true ) {
+ // 获取对应下标的职位描述
+ const jobDescription = await getJobDescriptionByIndex ( jobIndex ) ;
+ console . log ( `职位描述信息/n: ${ jobDescription } ` ) ;
+ if ( jobDescription ) {
+ //
+ }
+ jobIndex += 1 ;
}
} catch ( error ) {
console . error ( `发生错误: ${ error } ` ) ;
}
}
Combinez ensuite les informations de CV téléchargées et les informations de recrutement et transmettez-les à GPT
, en attendant la réponse de GPT
:
// 省略上一步的代码
// 读取简历信息
const getResumeInfo = ( ) => {
fs . readFile ( "./简历基本信息.txt" , "utf8" , ( err , data ) => {
if ( err ) {
console . error ( "读取文件时出错:" , err ) ;
return ;
}
// 输出文件内容
return data ;
} ) ;
} ;
// 与GPT进行聊天的函数
async function chat ( jobDescription ) {
// 获取简历信息
const resumeInfo = getResumeInfo ( ) ;
const askMessage = `你好,这是我的简历: ${ resumeInfo } ,这是我所应聘公司的要求: ${ jobDescription } 。我希望您能帮我直接给HR写一个礼貌专业的求职新消息,要求能够用专业的语言将简历中的技能结合应聘工作的描述,来阐述自己的优势,尽最大可能打动招聘者。并且请您始终使用中文来进行消息的编写,开头是招聘负责人。这是一封完整的求职信,不要包含求职信内容以外的东西,例如“根据您上传的求职要求和个人简历,我来帮您起草一封求职邮件:”这一类的内容,以便于我直接自动化复制粘贴发送,字数控制在80字左右为宜` ;
try {
const completion = await openai . chat . completions . create ( {
messages : [
{
role : "system" ,
content : askMessage ,
} ,
] ,
model : "gpt-3.5-turbo" ,
} ) ;
// 获取gpt返回的信息
const formattedMessage = completion . choices [ 0 ] . message . content . replace (
/ n / g ,
" "
) ;
return formattedMessage ;
} catch ( error ) {
console . error ( `gpt返回时发生错误: ${ error } ` ) ;
const errorResponse = JSON . stringify ( { error : String ( error ) } ) ;
return errorResponse ;
}
}
// 主函数
async function main ( url , browserType ) {
try {
// 打开浏览器
// 点击登录按钮,并等待登录成功
// 开始的索引
while ( true ) {
// 获取对应下标的职位描述
if ( jobDescription ) {
// 发送描述到聊天并打印响应
+ const response = await chat ( jobDescription ) ;
+ console . log ( "gpt给的回复" , response ) ;
}
jobIndex += 1 ;
}
} catch ( error ) {
console . error ( `发生错误: ${ error } ` ) ;
}
}
Une fois la réponse GPT terminée, recherchez le bouton Communiquer maintenant et simulez un clic dessus. À ce moment, vous entrerez dans l'interface de communication et de discussion, comme indiqué dans la figure :
// 省略上一步的代码
// 主函数
async function main ( url , browserType ) {
try {
// 打开浏览器
// 点击登录按钮,并等待登录成功
// 开始的索引
while ( true ) {
// 获取对应下标的职位描述
if ( jobDescription ) {
// 发送描述到聊天并打印响应
// 点击沟通按钮
+ const contactButton = await driver . findElement (
+ By . xpath (
+ "//*[@id='wrap']/div[2]/div[2]/div/div/div[2]/div/div[1]/div[2]/a[2]"
+ )
+ ) ;
+ await contactButton . click ( ) ;
}
jobIndex += 1 ;
}
} catch ( error ) {
console . error ( `发生错误: ${ error } ` ) ;
}
}
À ce stade, entrez dans l'interface de discussion, remplissez les informations de retour GPT dans la zone de saisie et déclenchez l'événement d'envoi.
// 省略上一步的代码
// 发送响应到聊天框
async function sendResponseToChatBox ( driver , response ) {
try {
// 请找到聊天输入框
const chatBox = await driver . findElement ( By . xpath ( "//*[@id='chat-input']" ) ) ;
// 清除输入框中可能存在的任何文本
await chatBox . clear ( ) ;
// 将响应粘贴到输入框
await chatBox . sendKeys ( response ) ;
await sleep ( 1000 ) ;
// 模拟按下回车键来发送消息
await chatBox . sendKeys ( Key . RETURN ) ;
await sleep ( 2000 ) ; // 模拟等待2秒
} catch ( error ) {
console . error ( `发送响应到聊天框时发生错误: ${ error } ` ) ;
}
}
// 主函数
async function main ( url , browserType ) {
try {
// 打开浏览器
// 点击登录按钮,并等待登录成功
// 开始的索引
while ( true ) {
// 获取对应下标的职位描述
if ( jobDescription ) {
// 发送描述到聊天并打印响应
// 点击沟通按钮
// 等待回复框出现
+ const chatBox = await driver . wait (
+ until . elementLocated ( By . xpath ( "//*[@id='chat-input']" ) ) ,
+ 10000
+ ) ;
+ // 调用函数发送响应
+ await sendResponseToChatBox ( driver , response ) ;
+ // 返回到上一个页面
+ await driver . navigate ( ) . back ( ) ;
+ await sleep ( 2000 ) ; // 模拟等待3秒
}
jobIndex += 1 ;
}
} catch ( error ) {
console . error ( `发生错误: ${ error } ` ) ;
}
}
Après l'envoi, revenez à la page de la liste de recrutement et répétez le processus.
Ce projet envoie simplement des informations de CV combinées à des informations sur l'emploi à GPT, puis utilise la réponse de GPT pour les envoyer au recruteur. En fait, ce n'est pas difficile et vise à inspirer les autres.
Il existe en fait des moyens plus élégants ici, comme transmettre votre CV à GPT et laisser GPT extraire des informations efficaces (c'est ce qu'a fait l'auteur d'origine). Cependant, étant donné que le projet sans GPT-API ne fournit pas de services d'assistance, un paiement est requis pour y parvenir. Les amis disposant de canaux de recharge peuvent l'essayer.
De plus, pour ceux que cela intéresse, vous pouvez approfondir, comme :
Enfin, je réitère le point de vue de l’auteur original :
J'espère que personne n'utilisera mon script pour couper des poireaux. Ils ont déjà été obligés d'utiliser ce script pour soumettre des CV. Il n'y a plus rien à extraire.