UNPKG

zhilian-auto-hi

Version:

智联招聘自动打招呼工具 - 自动化招聘流程的命令行工具

404 lines (403 loc) 18.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.resumeProcessingService = void 0; exports.readResumeDetailsWithResult = readResumeDetailsWithResult; exports.processResumeWithPagination = processResumeWithPagination; exports.processBrowseWithPagination = processBrowseWithPagination; const age_1 = require("../utils/age"); const keyword_1 = require("../utils/keyword"); const gender_1 = require("../utils/gender"); const automation_1 = require("./automation"); const delay_1 = require("../utils/delay"); const index_1 = __importDefault(require("../logger/index")); const graceful_shutdown_1 = __importDefault(require("./graceful-shutdown")); /** * 简历处理服务 * 实现 ShutdownAware 接口,支持优雅关闭 */ class ResumeProcessingService { /** * 检查是否应该停止当前操作 * @returns 是否应该停止 */ shouldStop() { return graceful_shutdown_1.default.isShuttingDown(); } /** * 执行清理操作 */ async cleanup() { index_1.default.cleanupProgress('简历处理服务清理完成'); } } // 创建服务实例 const resumeProcessingService = new ResumeProcessingService(); exports.resumeProcessingService = resumeProcessingService; /** * 根据pageDelay参数创建动态延迟配置 * @param pageDelay 基准延迟时间(毫秒) * @returns 延迟配置对象 */ function createDynamicDelayConfig(pageDelay) { // 确保pageDelay在合理范围内 const baseDelay = Math.max(500, Math.min(8000, pageDelay)); // 根据延迟时间调整变化范围 let variance; if (baseDelay < 1000) { variance = 0.5; // 短延迟使用较大变化范围 } else if (baseDelay < 3000) { variance = 0.4; // 中等延迟使用中等变化范围 } else { variance = 0.3; // 长延迟使用较小变化范围 } // 计算最小和最大延迟时间 const varianceAmount = baseDelay * variance; const minDelay = Math.max(300, Math.round(baseDelay - varianceAmount)); const maxDelay = Math.min(10000, Math.round(baseDelay + varianceAmount)); return { base: baseDelay, variance: variance, min: minDelay, max: maxDelay }; } /** * 读取简历详情信息并返回筛选结果 * @param page Playwright页面对象 * @param keywords 关键字数组 * @param minAge 最小年龄限制 * @param maxAge 最大年龄限制 * @param genderFilter 性别筛选条件 * @param ignoreKeyWords 忽略关键字数组 * @returns 筛选结果 */ async function readResumeDetailsWithResult(page, keywords, minAge, maxAge, genderFilter, ignoreKeyWords) { try { index_1.default.info('正在查找简历详情页面元素...'); await page.waitForSelector('.new-resume-detail--inner', { timeout: 10000 }); const resumeDetailElement = page.locator('.new-resume-detail--inner'); const resumeText = await resumeDetailElement.textContent() || ''; index_1.default.resumeDetails(resumeText); // 提取姓名和性别 let extractedGender = (0, gender_1.extractGender)(''); let candidateName = '未知'; try { // 查找姓名元素 const nameElement = page.locator('.resume-basic-new__name'); const nameElementCount = await nameElement.count(); if (nameElementCount > 0) { candidateName = await nameElement.textContent() || '未知'; extractedGender = (0, gender_1.extractGender)(candidateName); index_1.default.info(`提取的姓名: ${candidateName}`, '👤'); index_1.default.info(`提取的性别: ${extractedGender === 'male' ? '男性' : extractedGender === 'female' ? '女性' : '未知'}`, '⚧️'); } else { index_1.default.warning('未找到姓名元素 .resume-basic-new__name'); } } catch (nameError) { index_1.default.warning(`提取姓名时出错: ${nameError}`); } // 性别筛选 const genderMatchResult = (0, gender_1.checkGenderMatch)(extractedGender, genderFilter); if (!genderMatchResult.isMatch) { const filterDesc = genderFilter === 'male' ? '仅男性' : genderFilter === 'female' ? '仅女性' : '不限制'; const genderDesc = extractedGender === 'male' ? '男性' : extractedGender === 'female' ? '女性' : '未知'; index_1.default.error(`性别筛选: 不符合要求 (要求: ${filterDesc}, 候选人: ${genderDesc})`); return { isMatch: false, matchedKeywords: [], age: undefined, gender: extractedGender }; } if (genderFilter) { const filterDesc = genderFilter === 'male' ? '仅男性' : '仅女性'; index_1.default.success(`性别筛选: 符合要求 (要求: ${filterDesc})`); } // 提取年龄 const extractedAge = (0, age_1.extractAge)(resumeText); index_1.default.info(`提取的年龄: ${extractedAge !== null ? extractedAge + '岁' : '未找到'}`, '📅'); // 年龄筛选 const ageMatch = (0, age_1.checkAgeMatch)(extractedAge, minAge, maxAge); if (!ageMatch) { index_1.default.error(`年龄筛选: 不符合要求 (要求: ${minAge || '无限制'}-${maxAge || '无限制'}岁)`); return { isMatch: false, matchedKeywords: [], age: extractedAge || undefined, gender: extractedGender }; } index_1.default.success(`年龄筛选: 符合要求 (要求: ${minAge || '无限制'}-${maxAge || '无限制'}岁)`); // 关键字匹配 const matchResult = (0, keyword_1.checkKeywordMatch)(resumeText, keywords); // 如果关键字匹配成功,再检查忽略关键字 if (matchResult.isMatch && ignoreKeyWords && ignoreKeyWords.length > 0) { const ignoreMatchResult = (0, keyword_1.checkIgnoreKeywordMatch)(resumeText, ignoreKeyWords); if (ignoreMatchResult.isMatch) { index_1.default.error(`忽略关键字筛选: 包含忽略关键字 [${ignoreMatchResult.matchedKeywords.join(', ')}]`); return { isMatch: false, matchedKeywords: matchResult.matchedKeywords, age: extractedAge || undefined, gender: extractedGender }; } index_1.default.success(`忽略关键字筛选: 未包含忽略关键字`); } // 显示筛选结果 index_1.default.resumeResult(matchResult.isMatch, matchResult.matchedKeywords, extractedAge || undefined, extractedGender); return { isMatch: matchResult.isMatch, matchedKeywords: matchResult.matchedKeywords, age: extractedAge || undefined, gender: extractedGender }; } catch (error) { index_1.default.error(`读取简历详情时出错: ${error}`); return { isMatch: false, matchedKeywords: [], age: undefined, gender: 'unknown' }; } } /** * 自动翻页和筛选处理函数 * @param page Playwright页面对象 * @param keywords 关键字数组 * @param maxPages 最大处理页数 * @param pageDelay 翻页延迟时间 * @param minAge 最小年龄限制 * @param maxAge 最大年龄限制 * @param genderFilter 性别筛选条件 * @param ignoreKeyWords 忽略关键字数组 * @returns 处理统计信息 */ async function processResumeWithPagination(page, keywords, maxPages = 50, pageDelay = 2000, minAge, maxAge, genderFilter, ignoreKeyWords) { const stats = { totalProcessed: 0, matchedCount: 0, greetedCount: 0, currentPage: 1, isCompleted: false }; index_1.default.title('开始自动翻页和筛选处理'); for (let pageNum = 1; pageNum <= maxPages; pageNum++) { // 检查是否需要停止处理 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止简历处理...', '🛑'); stats.shutdownReason = '用户中断'; stats.isCompleted = false; break; } index_1.default.pageInfo(pageNum); stats.currentPage = pageNum; try { // 在读取简历详情前再次检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止简历处理...', '🛑'); stats.shutdownReason = '用户中断'; stats.isCompleted = false; break; } const result = await readResumeDetailsWithResult(page, keywords, minAge, maxAge, genderFilter, ignoreKeyWords); stats.totalProcessed++; if (result.isMatch) { stats.matchedCount++; index_1.default.highlight('简历匹配成功,开始自动打招呼...', '🤖'); // 在打招呼前检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止简历处理...', '🛑'); stats.shutdownReason = '用户中断'; stats.isCompleted = false; break; } // 打招呼 const greetSuccess = await (0, automation_1.autoGreet)(page); if (greetSuccess) { stats.greetedCount++; index_1.default.success('自动打招呼成功!', '🎉'); } } else { index_1.default.info('简历不匹配,跳过打招呼', '📝'); } // 在延迟前检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止简历处理...', '🛑'); stats.shutdownReason = '用户中断'; stats.isCompleted = false; break; } // 翻页延迟 - 使用基于pageDelay的随机延迟 const delayConfig = createDynamicDelayConfig(pageDelay); index_1.default.info(`翻页延迟配置: ${delay_1.DelayUtils.getConfigDescription(delayConfig)}`, '⏱️'); await delay_1.DelayUtils.customDelay(page, delayConfig); // 在翻页前检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止简历处理...', '🛑'); stats.shutdownReason = '用户中断'; stats.isCompleted = false; break; } // 翻页 index_1.default.info('尝试翻到下一页...', '📖'); await (0, automation_1.clickNextPageButton)(page); index_1.default.success(`第 ${pageNum} 页处理完成`); } catch (error) { index_1.default.error(`处理第 ${pageNum} 页时出错: ${error}`); // 即使出错也检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止简历处理...', '🛑'); stats.shutdownReason = '用户中断'; stats.isCompleted = false; break; } try { await (0, automation_1.clickNextPageButton)(page); } catch (nextPageError) { index_1.default.error(`翻页操作失败: ${nextPageError}`); } } } // 如果没有被中断,标记处理完成 if (!stats.shutdownReason) { stats.isCompleted = true; } // 输出最终统计信息 index_1.default.title('翻页处理完成!'); index_1.default.stats(stats); return stats; } /** * 纯浏览翻页处理函数 - 用于刷浏览模式 * @param page Playwright页面对象 * @param maxPages 最大处理页数 * @param pageDelay 翻页延迟时间 * @param keywords 关键字数组(可选,用于筛选) * @param minAge 最小年龄限制(可选,用于筛选) * @param maxAge 最大年龄限制(可选,用于筛选) * @param genderFilter 性别筛选条件(可选,用于筛选) * @param ignoreKeyWords 忽略关键字数组(可选,用于筛选) * @returns 浏览统计信息 */ async function processBrowseWithPagination(page, maxPages = 50, pageDelay = 2000, keywords, minAge, maxAge, genderFilter, ignoreKeyWords) { const stats = { totalBrowsed: 0, currentPage: 1, startTime: new Date(), totalFiltered: 0, matchedCount: 0 }; // 判断是否启用筛选功能 const enableFiltering = keywords && keywords.length > 0; if (enableFiltering) { index_1.default.title('开始带筛选的浏览模式翻页处理'); index_1.default.info('将对每个简历执行筛选检查,但不执行打招呼操作', '🔍'); } else { index_1.default.title('开始纯浏览模式翻页处理'); } for (let pageNum = 1; pageNum <= maxPages; pageNum++) { // 检查是否需要停止浏览 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止浏览模式...', '🛑'); break; } index_1.default.pageInfo(pageNum); stats.currentPage = pageNum; try { // 在等待页面加载前检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止浏览模式...', '🛑'); break; } // 等待页面加载完成 index_1.default.info('等待页面加载完成...'); await page.waitForSelector('.new-resume-detail--inner', { timeout: 10000 }); // 增加浏览计数 stats.totalBrowsed++; // 如果启用筛选功能,执行筛选检查 if (enableFiltering && keywords) { try { const filterResult = await readResumeDetailsWithResult(page, keywords, minAge, maxAge, genderFilter, ignoreKeyWords); stats.totalFiltered++; if (filterResult.isMatch) { stats.matchedCount++; } // 使用专门的浏览模式筛选结果显示函数 index_1.default.browseFilterResult(pageNum, filterResult.isMatch, filterResult.matchedKeywords, filterResult.age, filterResult.gender); } catch (filterError) { index_1.default.warning(`第 ${pageNum} 页筛选检查出错: ${filterError}`); stats.totalFiltered++; } } else { index_1.default.info(`已浏览第 ${pageNum} 页`, '👀'); } // 在延迟前检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止浏览模式...', '🛑'); break; } // 翻页延迟 - 使用基于pageDelay的随机延迟 const delayConfig = createDynamicDelayConfig(pageDelay); index_1.default.info(`浏览模式翻页延迟配置: ${delay_1.DelayUtils.getConfigDescription(delayConfig)}`, '⏱️'); await delay_1.DelayUtils.customDelay(page, delayConfig); // 在翻页前检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止浏览模式...', '🛑'); break; } // 翻页操作 index_1.default.info('尝试翻到下一页...', '📖'); await (0, automation_1.clickNextPageButton)(page); index_1.default.success(`第 ${pageNum} 页浏览完成`); } catch (error) { index_1.default.error(`浏览第 ${pageNum} 页时出错: ${error}`); // 即使出错也检查关闭状态 if (resumeProcessingService.shouldStop()) { index_1.default.info('检测到关闭信号,停止浏览模式...', '🛑'); break; } // 即使出错也尝试翻页继续 try { await (0, automation_1.clickNextPageButton)(page); } catch (nextPageError) { index_1.default.error(`翻页操作失败: ${nextPageError}`); // 如果翻页也失败,继续下一次循环 } } } // 设置结束时间 stats.endTime = new Date(); // 输出最终浏览统计信息 if (enableFiltering) { index_1.default.title('带筛选的浏览模式处理完成!'); index_1.default.info(`总浏览页面数: ${stats.totalBrowsed}`, '📊'); index_1.default.info(`总筛选简历数: ${stats.totalFiltered}`, '🔍'); index_1.default.info(`匹配简历数: ${stats.matchedCount}`, '✅'); if (stats.totalFiltered > 0) { const matchRate = ((stats.matchedCount / stats.totalFiltered) * 100).toFixed(1); index_1.default.info(`匹配率: ${matchRate}%`, '📈'); } } else { index_1.default.title('纯浏览模式处理完成!'); index_1.default.info(`总浏览页面数: ${stats.totalBrowsed}`, '📊'); } index_1.default.info(`开始时间: ${stats.startTime.toLocaleString()}`, '⏰'); index_1.default.info(`结束时间: ${stats.endTime.toLocaleString()}`, '⏰'); const duration = stats.endTime.getTime() - stats.startTime.getTime(); index_1.default.info(`总耗时: ${Math.round(duration / 1000)} 秒`, '⏱️'); return stats; }