v10.13修订: 个人黑名单改为弹窗级真实数据匹配(全名/resumeNumber/硬字段组全命中), 废除姓+年龄粗匹配(用户指正会误杀同姓同龄); 孟心如匹配字段=绵阳城市学院+河南宏智机械制造

This commit is contained in:
hermes
2026-09-30 01:12:42 +00:00
parent beaa75b4f6
commit e9c80a7347
+23 -14
View File
@@ -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] || '(空)'}`);