您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动批量投递职位,处理弹窗,支持关键词跳过
// ==UserScript== // @name BOSS直聘批量投递助手 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 自动批量投递职位,处理弹窗,支持关键词跳过 // @author chenni666 // @match https://www.zhipin.com/web/geek/jobs?* // @icon https://www.zhipin.com/favicon.ico // @grant GM_addStyle // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @license GNU AGPLv3 // ==/UserScript== (function() { 'use strict'; // 添加自定义样式 GM_addStyle(` .batch-apply-container { position: fixed; top: 20px; right: 20px; z-index: 9999; background: white; border: 1px solid #e0e0e0; border-radius: 8px; padding: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; max-height: 85vh; overflow-y: auto; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif; } .batch-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #eee; } .batch-title { font-size: 16px; font-weight: bold; color: #1a1a1a; } .batch-controls { display: flex; gap: 8px; margin-bottom: 10px; } .batch-btn { flex: 1; padding: 6px 10px; border-radius: 4px; border: none; cursor: pointer; font-weight: 500; transition: all 0.2s; font-size: 13px; } .start-btn { background: #00a6ff; color: white; } .start-btn:hover { background: #0088cc; } .start-btn:disabled { background: #b3e0ff; cursor: not-allowed; } .pause-btn { background: #ff9500; color: white; } .pause-btn:hover { background: #e08600; } .pause-btn:disabled { background: #ffd699; cursor: not-allowed; } .stop-btn { background: #ff3b30; color: white; } .stop-btn:hover { background: #d63026; } .stop-btn:disabled { background: #ffb3b0; cursor: not-allowed; } .progress-container { margin-bottom: 10px; } .progress-bar { height: 8px; background: #e0e0e0; border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, #00a6ff, #4cc9f0); width: 0%; transition: width 0.3s; } .progress-text { font-size: 12px; color: #666; margin-top: 4px; text-align: center; } .status-log { height: 100px; overflow-y: auto; border: 1px solid #eee; border-radius: 4px; padding: 8px; font-size: 12px; background: #f9f9f9; margin-bottom: 8px; } .log-entry { margin-bottom: 3px; padding-bottom: 3px; border-bottom: 1px dashed #eee; line-height: 1.4; } .log-entry:last-child { border-bottom: none; margin-bottom: 0; } .log-success { color: #28a745; } .log-error { color: #dc3545; } .log-info { color: #17a2b8; } .log-warning { color: #ff9500; } .stats-container { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 12px; color: #666; } .stats-item { display: flex; flex-direction: column; align-items: center; } .stats-value { font-weight: bold; font-size: 14px; color: #00a6ff; } .settings-toggle { color: #00a6ff; cursor: pointer; font-size: 13px; text-align: right; margin-bottom: 5px; } .settings-panel { padding-top: 8px; border-top: 1px solid #eee; max-height: 200px; overflow-y: auto; } .setting-item { margin-bottom: 8px; } .setting-label { display: block; margin-bottom: 3px; font-size: 12px; color: #666; } .setting-input { width: 100%; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px; box-sizing: border-box; } .setting-checkbox { margin-right: 8px; } .save-notification { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #28a745; color: white; padding: 12px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; opacity: 0; transition: opacity 0.3s ease; } .save-notification.show { opacity: 1; } `); // 创建控制面板 const container = document.createElement('div'); container.className = 'batch-apply-container'; container.innerHTML = ` <div class="batch-header"> <div class="batch-title">批量投递助手</div> </div> <div class="batch-controls"> <button class="batch-btn start-btn" id="start-btn">开始投递</button> <button class="batch-btn pause-btn" id="pause-btn" disabled>暂停</button> <button class="batch-btn stop-btn" id="stop-btn" disabled>停止</button> </div> <div class="progress-container"> <div class="progress-bar"> <div class="progress-fill" id="progress-fill"></div> </div> <div class="progress-text" id="progress-text">准备就绪</div> </div> <div class="status-log" id="status-log"></div> <div class="stats-container"> <div class="stats-item"> <div>总职位</div> <div class="stats-value" id="total-jobs">0</div> </div> <div class="stats-item"> <div>已投递</div> <div class="stats-value" id="applied-jobs">0</div> </div> <div class="stats-item"> <div>成功率</div> <div class="stats-value" id="success-rate">0%</div> </div> </div> <div class="settings-toggle" id="settings-toggle">▼ 高级设置</div> <div class="settings-panel" id="settings-panel" style="display:none;"> <div class="setting-item"> <label class="setting-label">职位间隔时间(毫秒)</label> <input type="number" class="setting-input" id="interval-time" value="2000"> </div> <div class="setting-item"> <label class="setting-label">弹窗等待时间(毫秒)</label> <input type="number" class="setting-input" id="popup-wait" value="1500"> </div> <div class="setting-item"> <label class="setting-label">最大重试次数</label> <input type="number" class="setting-input" id="max-retry" value="3"> </div> <div class="setting-item"> <label> <input type="checkbox" class="setting-checkbox" id="auto-close-popup" checked> 自动关闭弹窗 </label> </div> <div class="setting-item"> <label> <input type="checkbox" class="setting-checkbox" id="skip-applied" checked> 跳过已投递职位 </label> </div> <!-- 新增跳过关键词设置 --> <div class="setting-item"> <label class="setting-label">职位名称跳过词(逗号分隔)</label> <input type="text" class="setting-input" id="title-keywords" placeholder="例如:外包,驻场,销售"> </div> <div class="setting-item"> <label class="setting-label">职业描述跳过词(逗号分隔)</label> <textarea class="setting-input" id="skip-keywords" rows="2" placeholder="例如:外包,驻场,销售,兼职"></textarea> </div> </div> `; document.body.appendChild(container); // 状态变量 let jobItems = []; let currentIndex = 0; let isProcessing = false; let isPaused = false; let totalJobs = 0; let appliedCount = 0; let successCount = 0; let retryCount = 0; let currentTimeoutId = null; // 当前延时任务ID const statusLog = document.getElementById('status-log'); const progressFill = document.getElementById('progress-fill'); const progressText = document.getElementById('progress-text'); const totalJobsEl = document.getElementById('total-jobs'); const appliedJobsEl = document.getElementById('applied-jobs'); const successRateEl = document.getElementById('success-rate'); const startBtn = document.getElementById('start-btn'); const pauseBtn = document.getElementById('pause-btn'); const stopBtn = document.getElementById('stop-btn'); const settingsToggle = document.getElementById('settings-toggle'); const settingsPanel = document.getElementById('settings-panel'); const intervalTimeInput = document.getElementById('interval-time'); const popupWaitInput = document.getElementById('popup-wait'); const maxRetryInput = document.getElementById('max-retry'); const autoClosePopupCheck = document.getElementById('auto-close-popup'); const skipAppliedCheck = document.getElementById('skip-applied'); // 新增DOM元素引用 const skipKeywordsInput = document.getElementById('skip-keywords'); const titleKeywordsInput = document.getElementById('title-keywords'); // 加载设置 - 增加关键词设置 function loadSettings() { intervalTimeInput.value = GM_getValue('intervalTime', 2000); popupWaitInput.value = GM_getValue('popupWait', 1500); maxRetryInput.value = GM_getValue('maxRetry', 3); autoClosePopupCheck.checked = GM_getValue('autoClosePopup', true); skipAppliedCheck.checked = GM_getValue('skipApplied', true); skipKeywordsInput.value = GM_getValue('skipKeywords', ''); titleKeywordsInput.value = GM_getValue('titleKeywords', ''); } // 保存设置 - 增加关键词设置 function saveSettings() { GM_setValue('intervalTime', parseInt(intervalTimeInput.value)); GM_setValue('popupWait', parseInt(popupWaitInput.value)); GM_setValue('maxRetry', parseInt(maxRetryInput.value)); GM_setValue('autoClosePopup', autoClosePopupCheck.checked); GM_setValue('skipApplied', skipAppliedCheck.checked); GM_setValue('skipKeywords', skipKeywordsInput.value); GM_setValue('titleKeywords', titleKeywordsInput.value); // 显示保存成功提醒 showSaveNotification(); } // 显示保存设置提醒 function showSaveNotification() { // 移除已存在的提醒 const existingNotification = document.querySelector('.save-notification'); if (existingNotification) { existingNotification.remove(); } // 创建新的提醒 const notification = document.createElement('div'); notification.className = 'save-notification'; notification.textContent = '设置已保存'; document.body.appendChild(notification); // 显示动画 setTimeout(() => { notification.classList.add('show'); }, 10); // 2秒后隐藏并移除 setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); }, 2000); } // 添加日志 function addLog(message, type = 'info') { const logEntry = document.createElement('div'); logEntry.className = `log-entry log-${type}`; logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`; statusLog.appendChild(logEntry); statusLog.scrollTop = statusLog.scrollHeight; } // 更新统计信息 function updateStats() { totalJobsEl.textContent = totalJobs; appliedJobsEl.textContent = appliedCount; const rate = totalJobs > 0 ? Math.round((successCount / totalJobs) * 100) : 0; successRateEl.textContent = `${rate}%`; } // 更新进度 function updateProgress() { const percentage = totalJobs > 0 ? Math.round((appliedCount / totalJobs) * 100) : 0; progressFill.style.width = `${percentage}%`; progressText.textContent = totalJobs > 0 ? `${appliedCount}/${totalJobs} (${percentage}%)` : '准备就绪'; } // 获取职位列表 function getJobList() { return Array.from(document.querySelectorAll('.job-card-box')); } // 检查职位是否已投递 function isJobApplied(jobItem) { const appliedIndicator = jobItem.querySelector('.job-applied'); return appliedIndicator !== null; } // 检查职位是否包含关键词 function containsKeywords(jobItem, jobName = null) { // 获取设置的关键词列表 const skipKeywords = GM_getValue('skipKeywords', '').split(',').map(k => k.trim()).filter(k => k); const titleKeywords = GM_getValue('titleKeywords', '').split(',').map(k => k.trim()).filter(k => k); // 检查职位名称中的关键词 const jobTitleElem = jobItem.querySelector('.job-title'); const jobNameText = jobTitleElem ? jobTitleElem.textContent : ''; if (titleKeywords.length > 0 && jobNameText) { const lowerJobName = jobNameText.toLowerCase(); for (const keyword of titleKeywords) { if (keyword && lowerJobName.includes(keyword.toLowerCase())) { return { match: true, keyword: keyword, type: '职位名称' }; } } } // 检查职位描述中的关键词(需要加载详细页面) if (!jobName) return {match: false}; const jobDescElem = document.querySelector('.job-detail-body .desc'); if (jobDescElem && skipKeywords.length > 0) { const jobDescText = jobDescElem.textContent.toLowerCase(); for (const keyword of skipKeywords) { if (keyword && jobDescText.includes(keyword.toLowerCase())) { return { match: true, keyword: keyword, type: '职位描述' }; } } } return {match: false}; } // 点击职位项 - 增加关键词检查 function clickJobItem(index) { if (index >= jobItems.length) { addLog('所有职位已处理完毕', 'success'); isProcessing = false; resetButtons(); return; } const jobItem = jobItems[index]; // 检查是否跳过已投递职位 if (skipAppliedCheck.checked && isJobApplied(jobItem)) { addLog(`跳过已投递职位 ${index + 1}`, 'info'); appliedCount++; updateProgress(); updateStats(); currentTimeoutId = setTimeout(() => processNext(), 500); return; } jobItem.click(); addLog(`已选择职位 ${index + 1}/${jobItems.length}`); // 获取职位名称用于检查 const jobNameElem = document.querySelector('.job-detail-info .job-name'); const jobName = jobNameElem ? jobNameElem.textContent.trim() : null; // 设置延迟检查关键词 currentTimeoutId = setTimeout(() => { // 执行关键词检查 const keywordCheck = containsKeywords(jobItem, jobName); if (keywordCheck.match) { addLog(`跳过职位 ${index + 1} (包含${keywordCheck.type}关键词: ${keywordCheck.keyword})`, 'warning'); appliedCount++; updateProgress(); updateStats(); processNext(); return; } // 如果没有关键词匹配,继续投递流程 tryApply(index); }, parseInt(popupWaitInput.value) / 2); // 提前一半时间检查关键词 } // 尝试投递 function tryApply(index) { // 查找立即沟通按钮 const applyBtn = document.querySelector('.op-btn-chat'); if (applyBtn && applyBtn.textContent.includes('立即沟通')) { applyBtn.click(); addLog(`正在投递职位 ${index + 1}...`, 'info'); // 处理弹窗 currentTimeoutId = setTimeout(() => handlePopup(index), parseInt(popupWaitInput.value)); } else { addLog(`职位 ${index + 1} 无法投递(按钮不存在或已投递)`, 'warning'); appliedCount++; updateProgress(); updateStats(); processNext(); } } // 处理弹窗 function handlePopup(index) { const popup = document.querySelector('.greet-boss-container'); const stayButton = document.querySelector('.cancel-btn'); if (popup && stayButton && autoClosePopupCheck.checked) { stayButton.click(); addLog(`成功投递职位 ${index + 1},已关闭弹窗`, 'success'); successCount++; } else if (popup) { addLog(`职位 ${index + 1} 投递成功,但未关闭弹窗`, 'warning'); successCount++; } else { // 未检测到弹窗,可能是投递失败 if (retryCount < parseInt(maxRetryInput.value)) { retryCount++; addLog(`职位 ${index + 1} 投递未确认,重试中 (${retryCount}/${maxRetryInput.value})`, 'warning'); currentTimeoutId = setTimeout(() => tryApply(index), 1000); return; } else { addLog(`职位 ${index + 1} 投递失败,达到最大重试次数`, 'error'); } } appliedCount++; updateProgress(); updateStats(); retryCount = 0; processNext(); } // 处理下一个职位 function processNext() { if (isPaused) return; currentIndex++; if (currentIndex < jobItems.length) { currentTimeoutId = setTimeout(() => clickJobItem(currentIndex), parseInt(intervalTimeInput.value)); } else { addLog('批量投递完成!', 'success'); isProcessing = false; resetButtons(); GM_notification({ title: 'BOSS直聘批量投递完成', text: `成功投递 ${successCount}/${totalJobs} 个职位`, timeout: 5000 }); } } // 开始批量投递 function startBatchApply() { if (isProcessing) return; // 清除可能存在的延时任务 if (currentTimeoutId) { clearTimeout(currentTimeoutId); currentTimeoutId = null; } jobItems = getJobList(); if (jobItems.length === 0) { addLog('未找到职位列表,请刷新页面重试', 'error'); return; } totalJobs = jobItems.length; currentIndex = 0; appliedCount = 0; successCount = 0; retryCount = 0; isProcessing = true; isPaused = false; addLog(`开始批量投递,共 ${jobItems.length} 个职位`); updateProgress(); updateStats(); // 更新按钮状态 startBtn.disabled = true; pauseBtn.disabled = false; stopBtn.disabled = false; clickJobItem(currentIndex); } // 暂停投递 function pauseBatchApply() { if (!isProcessing) return; isPaused = true; addLog('投递已暂停'); pauseBtn.textContent = '继续'; pauseBtn.removeEventListener('click', pauseBatchApply); pauseBtn.addEventListener('click', resumeBatchApply); } // 继续投递 function resumeBatchApply() { if (!isProcessing) return; isPaused = false; addLog('继续投递'); pauseBtn.textContent = '暂停'; pauseBtn.removeEventListener('click', resumeBatchApply); pauseBtn.addEventListener('click', pauseBatchApply); processNext(); } // 停止投递 function stopBatchApply() { if (!isProcessing) return; // 清除当前延时任务 if (currentTimeoutId) { clearTimeout(currentTimeoutId); currentTimeoutId = null; } isProcessing = false; isPaused = false; addLog('投递已停止'); addLog(`本次投递统计: 处理了 ${appliedCount}/${totalJobs} 个职位,成功 ${successCount} 个`); resetButtons(); } // 重置按钮状态 function resetButtons() { startBtn.disabled = false; pauseBtn.disabled = true; stopBtn.disabled = true; pauseBtn.textContent = '暂停'; pauseBtn.removeEventListener('click', resumeBatchApply); pauseBtn.addEventListener('click', pauseBatchApply); // 清除延时任务 if (currentTimeoutId) { clearTimeout(currentTimeoutId); currentTimeoutId = null; } } // 初始化 function init() { // 加载设置 loadSettings(); // 添加事件监听 startBtn.addEventListener('click', startBatchApply); pauseBtn.addEventListener('click', pauseBatchApply); stopBtn.addEventListener('click', stopBatchApply); // 设置面板切换 settingsToggle.addEventListener('click', () => { if (settingsPanel.style.display === 'none') { settingsPanel.style.display = 'block'; settingsToggle.textContent = '▲ 收起设置'; } else { settingsPanel.style.display = 'none'; settingsToggle.textContent = '▼ 高级设置'; } }); // 设置变更保存 - 增加关键词设置 intervalTimeInput.addEventListener('change', saveSettings); popupWaitInput.addEventListener('change', saveSettings); maxRetryInput.addEventListener('change', saveSettings); autoClosePopupCheck.addEventListener('change', saveSettings); skipAppliedCheck.addEventListener('change', saveSettings); skipKeywordsInput.addEventListener('change', saveSettings); titleKeywordsInput.addEventListener('change', saveSettings); // 初始日志 addLog('脚本已加载,准备就绪'); addLog('点击"开始投递"按钮开始批量投递'); addLog('注意:请确保在BOSS直聘职位列表页面使用本脚本'); } // 启动初始化 init(); })();