// ==UserScript== // @name 智联招聘自动投递简历 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 自动搜索职位并批量投递简历,支持设置岗位关键词、薪资范围、地区,每日投递限制 // @author StudentHelper // @match https://www.zhaopin.com/sou/* // @match https://www.zhaopin.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_notification // @run-at document-idle // @noframes // @icon data:image/svg+xml, // ==/UserScript== (function () { 'use strict'; try { // ==================== 配置 ==================== const DEFAULT_CONFIG = { keyword: '', // 搜索关键词(如:前端开发、Java、产品经理) city: '', // 城市(如:北京、上海、深圳、广州) salary: '', // 薪资范围 dailyLimit: 80, // 每日投递上限 delayBetweenApply: 3000, // 两次投递间隔(毫秒) delayBetweenPage: 5000, // 翻页间隔(毫秒) autoScroll: true, // 自动滚动加载 }; // ==================== 状态 ==================== let isRunning = false; let isPaused = false; let stats = { delivered: 0, skipped: 0, failed: 0, pages: 0, todayTotal: 0 }; let deliveredJobIds = new Set(); // 本次会话已投递的职位ID let statusMsg = '就绪'; // ==================== 存储 ==================== function gmGet(key, def) { try { const v = GM_getValue(key, null); return v !== null ? v : def; } catch { return def; } } function gmSet(key, val) { try { GM_setValue(key, val); } catch (e) { console.warn('[智联投递] 存储失败:', e); } } function getTodayKey() { return 'zhipin_delivered_' + new Date().toISOString().split('T')[0]; } function getTodayDelivered() { try { return JSON.parse(gmGet(getTodayKey(), '[]')); } catch { return []; } } function saveTodayDelivered(ids) { gmSet(getTodayKey(), JSON.stringify(ids)); } function addTodayDelivered(jobId) { const ids = getTodayDelivered(); if (!ids.includes(jobId)) { ids.push(jobId); saveTodayDelivered(ids); } } function isTodayDelivered(jobId) { return getTodayDelivered().includes(jobId); } function getSavedConfig() { try { return JSON.parse(gmGet('zhipin_config', '{}')); } catch { return {}; } } function saveConfig(cfg) { gmSet('zhipin_config', JSON.stringify(cfg)); } // ==================== 工具函数 ==================== function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } function log(msg) { console.log('%c[智联投递] ' + msg, 'color: #00b4d8; font-weight: bold;'); statusMsg = msg; updatePanel(); } function isLoginPage() { return document.querySelector('.passport-dialog') !== null || document.querySelector('a.header-nav__c-no-login') !== null; } function isLoggedIn() { // 多种方式检测登录状态 const selectors = [ 'div.header-nav__c-login', '.c-login__top__name', '.header-nav__user', // 用户头像区域 '.header__user', // 用户区域 '[class*="user"]', // 包含user的类 '.avatar', // 头像 ]; for (const selector of selectors) { const el = document.querySelector(selector); if (el) { log('✅ 检测到登录状态: ' + selector); return true; } } // 检查是否有退出登录链接 const logoutLinks = document.querySelectorAll('a'); for (const link of logoutLinks) { const text = (link.textContent || '').trim(); if (text.includes('退出') || text.includes('注销') || text.includes('logout')) { log('✅ 检测到登录状态(退出链接)'); return true; } } log('⚠️ 未检测到登录状态'); return false; } function getJobIdFromCard(card) { const link = card.querySelector('a.jobinfo__name, .jobinfo a, a[href*="jobs.zhaopin.com"]'); if (link) { const href = link.getAttribute('href') || ''; const match = href.match(/CC(\d+)\.htm/); if (match) return match[1]; // 从URL中提取任何唯一ID const idMatch = href.match(/\/(CC\d+)/); if (idMatch) return idMatch[1]; } return null; } // ==================== 城市编码映射(已验证) ==================== const CITY_MAP = { '全国': '0', '北京': '530', '上海': '538', '广州': '763', '深圳': '765', '杭州': '653', '成都': '801', '南京': '635', '武汉': '736', '西安': '854', '重庆': '551', '天津': '531', '苏州': '639', '长沙': '749', '郑州': '719', '东莞': '779', '青岛': '703', '沈阳': '599', '宁波': '654', '昆明': '811', '大连': '600', '厦门': '682', '合肥': '660', '福州': '681', '哈尔滨': '631', '济南': '702', '佛山': '768', '长春': '621', '温州': '655', '石家庄': '610', '南宁': '774', '贵阳': '803', '南昌': '670', '太原': '611', '常州': '638', '珠海': '766', '惠州': '773', '中山': '780', '嘉兴': '656', '无锡': '636', '烟台': '707', '泉州': '683', '兰州': '805', '呼和浩特': '606', '乌鲁木齐': '870', }; // ==================== 核心逻辑 ==================== // 检查今日投递是否已达上限 function checkDailyLimit() { const config = { ...DEFAULT_CONFIG, ...getSavedConfig() }; const todayCount = getTodayDelivered().length; return todayCount >= config.dailyLimit; } // 查找投递按钮 function findApplyButtons() { const buttons = []; log('🔍 查找投递按钮...'); // 智联搜索结果中的投递按钮 const itemSelectors = [ '.joblist-box__item', '.positionlist__item', '[class*="joblist"]', '.job-card', '.position-card', '[class*="job"][class*="item"]', '.sou-job-list .item', ]; let items = []; for (const selector of itemSelectors) { items = document.querySelectorAll(selector); if (items.length > 0) { log(' 通过选择器找到职位卡片: ' + selector + ' (' + items.length + '个)'); break; } } items.forEach((item, index) => { const btnSelectors = [ 'button.joblist-box__btn', '.btn-apply', 'button[class*="deliver"]', 'button[class*="apply"]', '.btn-primary', 'button', ]; for (const selector of btnSelectors) { const btn = item.querySelector(selector); if (btn) { const btnText = (btn.textContent || '').trim(); if (btnText.includes('投递') || btnText.includes('申请') || btnText.includes('沟通')) { const jobId = getJobIdFromCard(item); // 改进职位名称获取逻辑 let jobName = '未知'; const jobNameSelectors = [ '.jobinfo__name', '.job-name', '.job-title', '[class*="jobinfo"] a', '[class*="job-title"]', 'h3 a', 'h4 a', '.title a', ]; for (const nameSelector of jobNameSelectors) { const nameEl = item.querySelector(nameSelector); if (nameEl) { jobName = nameEl.textContent?.trim() || '未知'; // 限制长度,避免获取到过多内容 if (jobName.length > 50) { jobName = jobName.substring(0, 50) + '...'; } break; } } const salary = item.querySelector('.jobinfo__salary, .salary, [class*="salary"]')?.textContent?.trim() || ''; const company = item.querySelector('.companyinfo__name, .company-name, [class*="company"]')?.textContent?.trim() || ''; buttons.push({ btn, jobId, jobName, salary, company, item }); if (index < 3) { log(' 找到投递按钮[' + index + ']: ' + jobName + ' - ' + btnText); } break; } } } }); // 文本匹配兜底 if (buttons.length === 0) { log(' 尝试通过文本匹配查找...'); const allBtns = document.querySelectorAll('button, a'); allBtns.forEach(btn => { const text = (btn.textContent || '').trim(); if (text === '立即投递' || text === '投递' || text === '申请' || text.includes('立即')) { const card = btn.closest('[class*="job"], [class*="position"], [class*="item"], [class*="card"]'); const jobId = card ? getJobIdFromCard(card) : null; const jobName = card?.querySelector('a, [class*="title"], [class*="name"]')?.textContent?.trim() || '未知'; buttons.push({ btn, jobId, jobName, salary: '', company: '', item: card }); log(' 文本匹配找到: ' + jobName + ' - ' + text); } }); } log('✅ 共找到 ' + buttons.length + ' 个投递按钮'); return buttons; } // 查找并处理投递确认弹窗 function handleConfirmDialog() { // 查找确认弹窗中的确认按钮 const confirmBtns = document.querySelectorAll( '.passport-dialog__confirm, .dialog-confirm, .modal-confirm, ' + '.lp-modal-confirm, button.btn-primary' ); for (const btn of confirmBtns) { const text = (btn.textContent || '').trim().toLowerCase(); if (text.includes('确认') || text.includes('投递') || text.includes('确定') || text.includes('confirm') || text.includes('submit')) { btn.click(); return true; } } return false; } // 关闭投递成功弹窗 function closeSuccessDialog() { // 查找"投递成功"弹窗的关闭按钮 const closeSelectors = [ '.dialog-close', '.modal-close', '.popup-close', '[class*="close"]', '[class*="Close"]', '.icon-close', '.btn-close', 'button[aria-label="关闭"]', 'button[aria-label="close"]' ]; for (const selector of closeSelectors) { const closeBtns = document.querySelectorAll(selector); for (const btn of closeBtns) { // 检查是否在投递成功弹窗内 const dialog = btn.closest('.dialog, .modal, .popup, [class*="dialog"], [class*="modal"], [class*="popup"]'); if (dialog) { const dialogText = (dialog.textContent || '').trim(); if (dialogText.includes('投递成功') || dialogText.includes('申请成功')) { btn.click(); log('✅ 已关闭投递成功弹窗'); return true; } } } } // 尝试点击弹窗外部的遮罩层来关闭 const overlays = document.querySelectorAll('.overlay, .mask, [class*="overlay"], [class*="mask"]'); for (const overlay of overlays) { const style = window.getComputedStyle(overlay); if (style.display !== 'none' && style.visibility !== 'hidden') { overlay.click(); log('✅ 已点击遮罩层关闭弹窗'); return true; } } return false; } // 点击投递按钮 async function clickApply(applyInfo) { const { btn, jobId, jobName, salary, company } = applyInfo; // 检查是否已投递过 if (jobId && (isTodayDelivered(jobId) || deliveredJobIds.has(jobId))) { stats.skipped++; log('⏭️ 已投递过: ' + jobName); return true; } // 检查按钮状态 const btnText = (btn.textContent || '').trim(); if (btnText.includes('已投') || btnText.includes('已申请') || btn.disabled) { stats.skipped++; log('⏭️ 已投递: ' + jobName); return true; } // 检查每日上限 if (checkDailyLimit()) { log('⛔ 今日投递已达上限 (' + DEFAULT_CONFIG.dailyLimit + '份)'); stop(); return false; } // 滚动到按钮可见 btn.scrollIntoView({ behavior: 'smooth', block: 'center' }); await sleep(500); // 点击投递 log('📤 投递: ' + jobName + (company ? ' @ ' + company : '') + (salary ? ' (' + salary + ')' : '')); // 如果按钮是a标签,先阻止默认跳转 if (btn.tagName === 'A') { const originalHref = btn.getAttribute('href'); btn.removeAttribute('href'); // 投递后恢复(虽然页面可能已经跳转) setTimeout(() => { if (originalHref) btn.setAttribute('href', originalHref); }, 100); } // 移除target属性防止新窗口 if (btn.hasAttribute('target')) { btn.removeAttribute('target'); } // 记录当前URL,用于检测跳转 const originalLocation = window.location.href; // 执行点击 btn.click(); log(' 已执行点击'); await sleep(2000); // 检查是否发生了页面跳转,如果是则返回 if (window.location.href !== originalLocation) { log('⚠️ 检测到页面跳转,尝试返回...'); window.history.back(); await sleep(1000); } // 处理可能的确认弹窗 handleConfirmDialog(); await sleep(1000); // 关闭投递成功弹窗 closeSuccessDialog(); await sleep(500); // 再次尝试关闭弹窗(有些弹窗延迟出现) setTimeout(() => { closeSuccessDialog(); }, 2000); // 关闭可能弹出的新标签页 try { if (window._lastOpenedTab) { try { window._lastOpenedTab.close(); } catch(e) {} window._lastOpenedTab = null; } } catch(e) {} // 记录 if (jobId) { deliveredJobIds.add(jobId); addTodayDelivered(jobId); } stats.delivered++; stats.todayTotal = getTodayDelivered().length; return true; } // 翻到下一页 async function goToNextPage() { const nextBtn = document.querySelector('a.sou-pager__next, .sou-pager a:last-child, a:has-text("下一页")'); if (nextBtn) { const href = nextBtn.getAttribute('href') || ''; const text = (nextBtn.textContent || '').trim(); // 检查是否是禁用状态 if (nextBtn.classList.contains('disabled') || nextBtn.getAttribute('aria-disabled') === 'true') { log('📄 没有更多页面了'); return false; } if (href && href !== '#' && href !== 'javascript:void(0)' && href !== 'javascript:;') { log('📄 翻到下一页...'); window.location.href = href; return true; } // 尝试点击 nextBtn.click(); await sleep(3000); return true; } // 尝试页码跳转 const currentPage = document.querySelector('.sou-pager__item--active, .sou-pager .active'); if (currentPage) { const pageNum = parseInt(currentPage.textContent); const nextPage = document.querySelector('.sou-pager__item:nth-child(' + (pageNum + 1) + ')'); if (nextPage) { nextPage.click(); await sleep(3000); return true; } } log('📄 没有找到下一页按钮'); return false; } // 主循环:处理当前页 async function processCurrentPage() { if (!isRunning || isPaused) return; log('📋 开始处理当前页面...'); // 等待页面加载 await sleep(2000); // 查找所有投递按钮 let buttons = findApplyButtons(); if (buttons.length === 0) { log('⚠️ 当前页面未找到投递按钮,等待加载...'); await sleep(3000); buttons = findApplyButtons(); if (buttons.length === 0) { log('❌ 仍未找到投递按钮,可能需要登录或页面结构变化'); stats.failed++; return; } } log('🔍 找到 ' + buttons.length + ' 个职位'); // 逐个投递 for (let i = 0; i < buttons.length; i++) { if (!isRunning || isPaused) return; if (checkDailyLimit()) { log('⛔ 今日投递已达上限!'); stop(); return; } const result = await clickApply(buttons[i]); if (!result) return; // 投递间隔 const config = { ...DEFAULT_CONFIG, ...getSavedConfig() }; if (i < buttons.length - 1) { log('⏳ 等待 ' + (config.delayBetweenApply / 1000) + 's 后继续...'); await sleep(config.delayBetweenApply); } } stats.pages++; log('✅ 当前页处理完成,已处理 ' + stats.pages + ' 页'); // 翻到下一页 const config = { ...DEFAULT_CONFIG, ...getSavedConfig() }; log('⏳ 等待 ' + (config.delayBetweenPage / 1000) + 's 后翻页...'); await sleep(config.delayBetweenPage); if (!isRunning || isPaused) return; const hasNext = await goToNextPage(); if (hasNext) { // 页面跳转后脚本会重新执行 return; } log('🎉 所有页面处理完成!'); stop(); } // ==================== 薪资编码映射 ==================== const SALARY_MAP = { '不限': '', '4K以下': '1', '4K-6K': '2', '6K-8K': '3', '8K-10K': '4', '10K-15K': '5', '15K-25K': '6', '25K-35K': '7', '35K-50K': '8', '50K以上': '9', }; // ==================== 搜索功能 ==================== async function doSearch() { const config = { ...DEFAULT_CONFIG, ...getSavedConfig() }; if (!config.keyword) { log('❌ 请先设置搜索关键词'); return; } // 构建URL参数 const params = new URLSearchParams(); // 薪资参数 const salaryCode = SALARY_MAP[config.salary]; if (salaryCode) { params.append('sl', salaryCode); } // 构建基础URL路径 let urlPath = 'https://www.zhaopin.com/sou/'; // 城市参数(已验证的jl代码,"全国"不传jl参数) if (config.city && config.city !== '全国' && CITY_MAP[config.city]) { urlPath += 'jl' + CITY_MAP[config.city] + '/'; } // 关键词参数 urlPath += 'kw' + encodeURIComponent(config.keyword) + '/'; // 第1页 urlPath += 'p1'; // 组合URL const queryString = params.toString(); const url = queryString ? urlPath + '/?' + queryString : urlPath; // 保存薪资筛选标记(用于页面加载后自动点击) if (config.salary && config.salary !== '不限') { gmSet('zhipin_pending_salary', config.salary); log('💾 已保存薪资筛选标记: ' + config.salary); } else { gmSet('zhipin_pending_salary', ''); log('💾 薪资为"不限",清除筛选标记'); } log('🔍 搜索: ' + config.keyword + (config.city ? ' | ' + config.city : '') + (config.salary && config.salary !== '不限' ? ' | 薪资:' + config.salary : '')); log('🔗 URL: ' + url); window.location.href = url; } // 页面加载后自动设置薪资筛选 - 简化版 async function autoSetSalaryFilter() { const pendingSalary = gmGet('zhipin_pending_salary', ''); if (!pendingSalary) { log('ℹ️ 没有待设置的薪资筛选'); return; } // 清除标记(先清除,避免重复执行) gmSet('zhipin_pending_salary', ''); log('💰 自动设置薪资筛选: ' + pendingSalary); // 检查URL中是否已有sl参数 const urlParams = new URLSearchParams(window.location.search); const urlSl = urlParams.get('sl'); if (urlSl) { log('ℹ️ URL中已有薪资参数 (sl=' + urlSl + '),但仍会尝试点击网页筛选器'); } // 等待页面完全加载 await sleep(3000); // ========== 第一步:找到并点击"薪资要求"筛选器 ========== log('🔍 步骤1: 查找薪资筛选器...'); let salaryTrigger = null; // 方法1: 直接通过已知的选择器查找智联招聘的薪资筛选器 const knownSelectors = [ '.query-select-comp__content', // 智联招聘薪资筛选器内容区 '.query-select-comp', // 智联招聘薪资筛选器容器 '[class*="salaryType"]', // 薪资类型选择器 '[class*="salary"]', // 包含salary的类 ]; for (const selector of knownSelectors) { const elements = document.querySelectorAll(selector); for (const el of elements) { const text = (el.textContent || '').trim(); if (text.includes('薪资') || text.includes('salary')) { salaryTrigger = el; log('✅ 通过选择器找到薪资触发器: ' + selector); break; } } if (salaryTrigger) break; } // 方法2: 通过文本查找 if (!salaryTrigger) { const allElements = Array.from(document.querySelectorAll('*')); const salaryElements = allElements.filter(el => { const text = (el.textContent || '').trim(); return text.includes('薪资') && text.length < 20; }); log('📊 找到 ' + salaryElements.length + ' 个包含"薪资"的元素'); for (const el of salaryElements) { // 向上查找3层 let parent = el; for (let i = 0; i < 3; i++) { if (!parent.parentElement) break; parent = parent.parentElement; const className = (parent.className || '').toLowerCase(); const tagName = parent.tagName.toLowerCase(); if (className.includes('select') || className.includes('query') || tagName === 'button') { salaryTrigger = parent; log('✅ 找到薪资触发器: ' + tagName + ' | class: ' + className.substring(0, 50)); break; } } if (salaryTrigger) break; } } if (!salaryTrigger) { log('❌ 未找到薪资筛选器'); return; } // 点击薪资筛选器 log('📌 点击薪资触发器...'); log(' 触发器元素: ' + salaryTrigger.tagName + ' | class: ' + (salaryTrigger.className || '无')); // 使用多种方式点击,确保触发下拉菜单 salaryTrigger.click(); // 同时尝试触发mousedown和mouseup事件(有些组件需要这些事件) const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true }); const mouseUpEvent = new MouseEvent('mouseup', { bubbles: true }); salaryTrigger.dispatchEvent(mouseDownEvent); salaryTrigger.dispatchEvent(mouseUpEvent); log('⏳ 等待下拉菜单展开...'); await sleep(3000); // 增加等待时间 // ========== 第二步:点击目标薪资选项 ========== log('🔍 步骤2: 查找薪资选项: ' + pendingSalary); let targetOption = null; // 方法1: 在智联招聘的下拉菜单中查找 const dropdownSelectors = [ '.query-select-comp__list', // 智联招聘下拉列表 '.query-select-comp ul', // 智联招聘ul '[class*="select"] ul', // 通用选择器 '[class*="dropdown"] ul', // 通用下拉菜单 ]; for (const selector of dropdownSelectors) { const dropdowns = document.querySelectorAll(selector); log(' 检查选择器: ' + selector + ' (找到 ' + dropdowns.length + ' 个)'); for (const dropdown of dropdowns) { // 检查下拉菜单的显示状态 const style = window.getComputedStyle(dropdown); const isVisible = style.display !== 'none' && style.visibility !== 'hidden'; log(' 下拉菜单可见性: display=' + style.display + ', visibility=' + style.visibility); if (!isVisible) { log(' 下拉菜单隐藏,强制显示...'); dropdown.style.display = 'block'; dropdown.style.visibility = 'visible'; } // 查找所有li选项 const options = dropdown.querySelectorAll('li'); log(' 找到 ' + options.length + ' 个li选项'); // 打印所有选项用于调试 options.forEach((opt, idx) => { const text = (opt.textContent || '').trim(); log(' [' + idx + '] ' + text); }); for (const option of options) { const text = (option.textContent || '').trim(); if (text === pendingSalary) { targetOption = option; log('✅ 在下拉菜单中匹配到选项: ' + text); break; } } if (targetOption) break; } if (targetOption) break; } // 方法2: 全局查找所有li元素 if (!targetOption) { log('🔍 方法2: 全局查找li元素...'); const allLi = document.querySelectorAll('li'); log(' 找到 ' + allLi.length + ' 个li元素'); for (const li of allLi) { const text = (li.textContent || '').trim(); const rect = li.getBoundingClientRect(); // 只考虑可见的li if (rect.width === 0 || rect.height === 0) continue; if (text === pendingSalary) { targetOption = li; log('✅ 匹配到li选项: ' + text); break; } } } // 方法3: 模糊匹配 if (!targetOption) { log('🔍 方法3: 模糊匹配...'); const allElements = document.querySelectorAll('li, div, span, a'); for (const el of allElements) { const text = (el.textContent || '').trim(); const rect = el.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) continue; if (text.length > 30) continue; if (text.includes(pendingSalary) || pendingSalary.includes(text)) { targetOption = el; log('✅ 模糊匹配到: ' + text); break; } } } if (targetOption) { log('📌 点击薪资选项...'); log(' 选项元素: ' + targetOption.tagName + ' | class: ' + (targetOption.className || '无')); // 尝试多种点击方式 try { // 方式1: 直接点击 targetOption.click(); log(' 已执行 click()'); // 方式2: 触发鼠标事件 const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }); targetOption.dispatchEvent(clickEvent); log(' 已触发 click 事件'); // 方式3: 如果选项内有子元素,尝试点击子元素 const childClickable = targetOption.querySelector('a, span, div'); if (childClickable) { childClickable.click(); log(' 已点击子元素: ' + childClickable.tagName); } log('✅ 已选择薪资: ' + pendingSalary); } catch (err) { log('❌ 点击失败: ' + err.message); } await sleep(1500); } else { log('❌ 未找到薪资选项: ' + pendingSalary); } } // 获取元素的直接文本(不包括子元素文本) function getDirectText(el) { let text = ''; for (const node of el.childNodes) { if (node.nodeType === Node.TEXT_NODE) { text += node.textContent; } } return text.trim(); } // 标准化文本用于比较(处理各种不可见字符差异) function normalizeText(text) { return text .replace(/[\s\u00A0\u3000]/g, '') // 去掉所有空格 .replace(/[–—‑‐‒]/g, '-') // 统一破折号为半角连字符 .replace(/K/g, 'k').replace(/k/g, 'K') // 统一K大小写 .toUpperCase(); } // 策略A:用XPath按文本精确查找 async function clickByText(targetText) { // 尝试多种文本变体 const variants = [ targetText, targetText.replace(/-/g, '–'), // en-dash targetText.replace(/-/g, '—'), // em-dash targetText.replace(/-/g, '‑'), // non-breaking hyphen targetText.replace(/K/g, 'k'), targetText.replace(/K/g, 'K'), ]; for (const text of variants) { try { // 查找文本节点等于目标文本的元素 const xpath = `//*[normalize-space(text())="${text}"]`; const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0; i < result.snapshotLength; i++) { const el = result.snapshotItem(i); // 确保是可见的、可点击的元素(不是页面其他地方的相同文本) if (el.offsetWidth > 0 && el.offsetHeight > 0 && el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE') { const clickable = el.closest('a, button, [role="option"], [role="listbox"] *, li, [class*="option"], [class*="item"]') || el; clickable.click(); log('✅ 已设置薪资(XPath): ' + targetText); return true; } } } catch(e) {} } return false; } // 策略B:按索引点击(薪资选项列表中的第N个,0=不限) async function clickByIndex(index) { // 薪资下拉展开后,找到所有可见的选项元素 // 智联的选项通常是:不限(0), 4K以下(1), 4K-6K(2), ... // index=5 对应 10K-15K,即第6个选项(从0开始) // 尝试多种选择器找选项列表 const containerSelectors = [ '[class*="filter"] [class*="options"]', '[class*="filter"] [class*="list"]', '[class*="filter"] [class*="dropdown"]', '[class*="filter"] [class*="menu"]', '[class*="select"] [class*="list"]', '[class*="select"] [class*="options"]', '[class*="select"] [class*="dropdown"]', '.sou__filter__box [class*="item"]', '[class*="condition"] [class*="list"]', ]; for (const sel of containerSelectors) { const container = document.querySelector(sel); if (!container) continue; const items = container.children; if (items.length >= index + 1) { items[index].click(); log('✅ 已设置薪资(索引' + index + '): ' + items[index].textContent.trim()); return true; } } return false; } // 策略C:遍历所有可见元素模糊匹配 async function clickByFuzzyMatch(targetText) { const normalizedTarget = normalizeText(targetText); const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT, { acceptNode: function(node) { // 跳过隐藏元素、脚本、样式 if (!node.offsetWidth || !node.offsetHeight) return NodeFilter.FILTER_REJECT; if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'HEAD', 'META', 'LINK'].includes(node.tagName)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); let node; let candidates = []; while (node = walker.nextNode()) { const text = (node.textContent || '').trim(); // 只看短文本元素(薪资选项通常不超过20字符) if (text.length > 30 || text.length < 2) continue; const normalized = normalizeText(text); if (normalized === normalizedTarget || text === targetText) { candidates.push(node); } } // 优先选择看起来像下拉选项的元素 const preferredTags = ['A', 'BUTTON', 'LI', '[ROLE="OPTION"]']; for (const tag of preferredTags) { const found = candidates.find(el => el.tagName === tag || el.getAttribute('role') === 'option'); if (found) { found.click(); log('✅ 已设置薪资(模糊匹配): ' + targetText); return true; } } // 如果没找到首选标签的,点击第一个候选 if (candidates.length > 0) { candidates[0].click(); log('✅ 已设置薪资(模糊匹配): ' + targetText); return true; } return false; } // ==================== 控制 ==================== function start() { if (isRunning) return; isRunning = true; isPaused = false; // 检查登录状态 if (!isLoggedIn()) { log('❌ 请先登录智联招聘!'); isRunning = false; updatePanel(); return; } // 检查每日上限 if (checkDailyLimit()) { log('⛔ 今日投递已达上限 (' + getTodayDelivered().length + '/' + DEFAULT_CONFIG.dailyLimit + ')'); isRunning = false; updatePanel(); return; } // 启用location拦截,防止页面跳转 interceptLocation(); log('🚀 自动投递已启动!'); updatePanel(); processCurrentPage(); } function stop() { isRunning = false; isPaused = false; log('⏹️ 已停止'); updatePanel(); } function pause() { if (!isRunning) return; isPaused = !isPaused; if (isPaused) { log('⏸️ 已暂停'); } else { log('▶️ 继续运行...'); processCurrentPage(); } updatePanel(); } // ==================== 控制面板 ==================== function createPanel() { const savedConfig = getSavedConfig(); const config = { ...DEFAULT_CONFIG, ...savedConfig }; const panel = document.createElement('div'); panel.id = 'zp-auto-panel'; panel.innerHTML = `