최근 저는 GitHub에서 매우 흥미로운 프로젝트인 GitHub Link를 발견했습니다.
저자는 GPT
와 RPA
기술을 교묘하게 결합하여 자동 이력서 제출 도우미를 만들었습니다. 원저자가 공유한 효과 시연 영상입니다: B 스테이션 영상 링크.
그러나 원래 프로젝트에는 다음과 같은 문제가 있었습니다.
아무 소용이 없던 끝에 Node.js를 사용하여 프로젝트를 다시 구현하기로 결정했습니다. 이는 완전히 무료이며 에이전트: GitHub 프로젝트 주소를 설정하지 않고도 한 번의 클릭으로 실행할 수 있습니다.
추운 채용 시즌에 이 스크립트는 여러분에게 도움과 따뜻함을 선사할 수 있습니다. 이 프로젝트가 가치 있다고 생각하신다면 별점을 주시면 정말 감사하겠습니다.
먼저 Selenium-webdriver를 사용하여 사용자 행동을 시뮬레이션합니다. 이 라이브러리는 강력한 자동화 테스트 도구입니다. 브라우저 상호 작용을 프로그래밍 방식으로 제어할 수 있으며 일반적으로 자동화된 테스트, 웹 스크래핑 및 사용자 상호 작용 시뮬레이션과 같은 작업에 사용됩니다.
DOM
노드를 찾고, 로그인을 트리거하는 사용자의 클릭을 시뮬레이션하고, 사용자가 QR 코드를 스캔할 때까지 기다립니다.GPT
에 전달하고, GPT
의 응답을 기다립니다.GPT
응답한 후 '지금 커뮤니케이션' 버튼을 클릭하여 커뮤니케이션 채팅 인터페이스로 들어갑니다.GPT
에서 반환한 정보를 채팅 상자에 입력하고 전송 이벤트를 트리거하세요.GPT 개발을 해보신 분들은 GPT 인터페이스를 호출하려면 결제가 필요하고, 충전 과정이 엄청나게 번거롭고 해외 은행 카드를 사용해야 한다는 점을 아실 겁니다.
이 과정을 단순화하기 위해 GitHub 계정으로 로그인하면 쉽게 얻을 수 있는 무료 API_KEY를 제공하는 프로젝트를 GitCode에서 찾았습니다.
이렇게 하면 무료 API_KEY
사용하여 OpenAI 클라이언트를 초기화할 수 있습니다.
// 初始化OpenAI客户端
const openai = new OpenAI ( {
// 代理地址,这样国内用户就可以访问了
baseURL : "https://api.chatanywhere.com.cn" ,
apiKey : "你的apiKey" ,
} ) ;
이 단계에서 우리가 원하는 것은 브라우저를 열고 지정된 URL로 이동하는 것입니다. 구체적인 작업은 selenium-webdriver의 API를 호출하고 코드를 직접 입력하는 것입니다.
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 ) ;
이 단계에서는 로그인 버튼 의 DOM 노드를 찾은 다음 로그인 클릭을 시뮬레이션해야 합니다.
// 省略上一步的代码
// 点击登录按钮,并等待登录成功
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 } ` ) ;
}
}
성공적으로 로그인한 후 채용 정보 목록 페이지로 들어가게 됩니다. 이 단계에서는 그림과 같이 채용 정보를 탐색하고 클릭하여 각 채용 정보의 직무 설명 정보를 찾아야 합니다.
// 省略上一步的代码
// 根据索引获取职位描述
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 } ` ) ;
}
}
그런 다음 업로드된 이력서 정보와 채용 정보를 결합하여 GPT
에 전달하고 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 } ` ) ;
}
}
GPT 응답이 완료되면 지금 통신 버튼을 찾아 클릭을 시뮬레이션합니다. 이때 그림과 같이 통신 및 채팅 인터페이스로 들어갑니다.
// 省略上一步的代码
// 主函数
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 } ` ) ;
}
}
이 시점에서 채팅 인터페이스에 들어가서 입력 상자에 GPT 반환 정보를 입력하고 전송 이벤트를 트리거합니다.
// 省略上一步的代码
// 发送响应到聊天框
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 } ` ) ;
}
}
전송 후 모집 목록 페이지로 돌아가서 과정을 반복하세요.
이 프로젝트는 단순히 채용 정보와 결합된 이력서 정보를 GPT로 보낸 다음 GPT의 답변을 사용하여 채용 담당자에게 보내는 것입니다. 실제로는 어렵지 않으며 다른 사람들에게 영감을 주기 위한 것입니다.
실제로 이력서를 GPT에 전달하고 GPT에서 효과적인 정보를 추출하도록 하는 등의 더 우아한 방법이 있습니다(원작자가 했던 작업입니다). 하지만 GPT-API가 없는 프로젝트는 보조 서비스를 제공하지 않기 때문에 이를 위해서는 충전 채널이 있는 친구가 사용해 볼 수 있습니다.
또한 관심 있는 분들을 위해 다음과 같이 더 자세히 알아볼 수 있습니다.
마지막으로 원저자의 관점을 다시 한 번 강조합니다.
아무도 내 스크립트를 사용하여 부추를 자르지 않기를 바랍니다. 그들은 이미 이력서를 제출하기 위해 이 스크립트를 사용하도록 강요받았습니다.