From e9c80a7347e5133fe0c7f8d9f46c43dc57c58ef5 Mon Sep 17 00:00:00 2001 From: hermes Date: Wed, 30 Sep 2026 01:12:42 +0000 Subject: [PATCH] =?UTF-8?q?v10.13=E4=BF=AE=E8=AE=A2:=20=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E9=BB=91=E5=90=8D=E5=8D=95=E6=94=B9=E4=B8=BA=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E7=BA=A7=E7=9C=9F=E5=AE=9E=E6=95=B0=E6=8D=AE=E5=8C=B9=E9=85=8D?= =?UTF-8?q?(=E5=85=A8=E5=90=8D/resumeNumber/=E7=A1=AC=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=BB=84=E5=85=A8=E5=91=BD=E4=B8=AD),=20=E5=BA=9F=E9=99=A4?= =?UTF-8?q?=E5=A7=93+=E5=B9=B4=E9=BE=84=E7=B2=97=E5=8C=B9=E9=85=8D(?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=8C=87=E6=AD=A3=E4=BC=9A=E8=AF=AF=E6=9D=80?= =?UTF-8?q?=E5=90=8C=E5=A7=93=E5=90=8C=E9=BE=84);=20=E5=AD=9F=E5=BF=83?= =?UTF-8?q?=E5=A6=82=E5=8C=B9=E9=85=8D=E5=AD=97=E6=AE=B5=3D=E7=BB=B5?= =?UTF-8?q?=E9=98=B3=E5=9F=8E=E5=B8=82=E5=AD=A6=E9=99=A2+=E6=B2=B3?= =?UTF-8?q?=E5=8D=97=E5=AE=8F=E6=99=BA=E6=9C=BA=E6=A2=B0=E5=88=B6=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/zhaopin-scrape.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/scripts/zhaopin-scrape.js b/scripts/zhaopin-scrape.js index f3e997a..158c488 100755 --- a/scripts/zhaopin-scrape.js +++ b/scripts/zhaopin-scrape.js @@ -49,25 +49,29 @@ const ACCOUNT = process.env.ACCOUNT || 'hr1'; const TARGET_CITY = process.env.TARGET_CITY || '郑州'; const COMPANY_BLACKLIST = ['合塑', '派卡']; // 从 workDetail 提取公司名命中即跳过 -// ---------- 个人黑名单(v10.13, 2026-09-29)---------- -// person_blacklist 表:用户指定的候选人,命中即不开弹窗直接跳过 -// 匹配规则:① 姓名=全名(弹窗里能看到全名)② 卡片场景兜底:姓+年龄一致(如"孟女士"+23岁) -// ③ resumeNumber 精确匹配(如有) +// ---------- 个人黑名单(v10.13, 2026-09-29 修订)---------- +// person_blacklist 表:用户指定的候选人。匹配必须基于弹窗真实数据,不做姓+年龄这种粗匹配(会误杀同姓同龄人) +// 匹配规则(弹窗打开后执行,匹配 raw_text): +// ① resumeNumber 精确匹配(如有) +// ② 全名精确匹配(平台显示全名时) +// ③ match_fields 关键词组(学校/工作单位等硬字段)全部命中 → 同人 let PERSON_BLACKLIST = []; function loadPersonBlacklist(db) { try { - PERSON_BLACKLIST = db.prepare('SELECT resume_number, name, age FROM person_blacklist').all(); + PERSON_BLACKLIST = db.prepare('SELECT * FROM person_blacklist').all().map(p => ({ + ...p, + fields: JSON.parse(p.match_fields || '[]'), + })); if (PERSON_BLACKLIST.length) log(`个人黑名单已加载: ${PERSON_BLACKLIST.length} 人 [${PERSON_BLACKLIST.map(p => p.name).join('、')}]`); } catch (e) { PERSON_BLACKLIST = []; } // 表不存在等异常不阻塞主流程 } -function personBlackHit(cardName, cardAge, resumeNumber) { +// 弹窗级匹配:resumeNumber / 全名 / 关键词组全命中 +function personBlackHitModal(fullName, resumeNumber, rawText) { for (const p of PERSON_BLACKLIST) { if (p.resume_number && resumeNumber && p.resume_number === resumeNumber) return `${p.name}(精确ID)`; - if (cardName && p.name && cardName === p.name) return p.name; // 弹窗全名精确 - // 卡片"姓+称谓"场景:孟女士/孟先生 的姓 == 黑名单人姓 且年龄一致 - if (cardName && cardAge != null && p.name && p.age === cardAge - && /^(.)(女士|先生)/.test(cardName) && cardName.charAt(0) === p.name.charAt(0)) { - return `${p.name}(姓+年龄匹配)`; + if (fullName && p.name && fullName === p.name) return p.name; + if (p.fields.length && rawText && p.fields.every(f => rawText.includes(f))) { + return `${p.name}(字段匹配:${p.fields.join('+')})`; } } return null; @@ -471,9 +475,7 @@ async function scrapeZhaopin(maxCount = 20, fullMode = false, scoreOnly = false) const cardBlackHit = COMPANY_BLACKLIST.find(b => card.cardText && card.cardText.includes(b)); if (cardBlackHit) { log(`🚫 [跳过-列表黑名单] ${card.name} | 卡片含"${cardBlackHit}"`); skipped++; currentDataIndex = await navigateToNextCard(page, currentDataIndex); continue; } - // v10.13:个人黑名单(卡片级:姓+年龄)——不开弹窗直接跳过 - const pBlackHit = personBlackHit(card.name, card.age, null); - if (pBlackHit) { log(`🚫 [跳过-个人黑名单] ${card.name} ${card.age}岁 | 命中: ${pBlackHit}`); skipped++; currentDataIndex = await navigateToNextCard(page, currentDataIndex); continue; } + // v10.13:个人黑名单——弹窗级匹配(全名/resumeNumber/硬字段组),基于真实数据,卡片级不预筛 try { // 打开弹窗(v10.12:Playwright原生点击替代evaluate合成click——平台反自动化检测会 @@ -526,6 +528,13 @@ async function scrapeZhaopin(maxCount = 20, fullMode = false, scoreOnly = false) if (refreshed) { await closeModal(page); log('页面已刷新(列表变化),重扫'); currentDataIndex = null; continue; } const data = await extractModalData(page); + // v10.13:个人黑名单——弹窗真实数据匹配(全名/resumeNumber/硬字段组全命中) + const pHit = personBlackHitModal(data?.name, resumeNumber, data?.rawText || ''); + if (pHit) { + await closeModal(page); + log(`🚫 [跳过-个人黑名单] ${card.name} | 命中: ${pHit}`); skipped++; + currentDataIndex = await navigateToNextCard(page, currentDataIndex); continue; + } // v10.8:候选人级提示(对方已关闭求职等)→ 跳过该人继续,不是平台阻碍 if (data?.candidateLevelNotice && !data?.name) { log(`⊘ 候选人状态提示,跳过: ${(data.rawText || '').split('\n').filter(Boolean)[0] || '(空)'}`);