v10.6 阻碍即停(通用): 弹窗有效性核查(缺姓名或全部结构信号=无效页, 不入库)→熔断; runner二次核查确认→私聊告警+停止(platform_blocked状态); 历史662条限额样本回测100%命中/正常简历0误判

This commit is contained in:
hermes
2026-08-27 03:43:54 +00:00
parent 107fa553b6
commit 0b1bd699b5
2 changed files with 67 additions and 0 deletions
+52
View File
@@ -164,6 +164,7 @@ function maybeSendReport(myStatus, chatLeft) {
function statusText(s) {
if (s === 'kicked_out') return '🚨 被顶号(他处登录)';
if (s === 'login_expired') return '🚨 登录失效';
if (s === 'platform_blocked') return '⛔ 平台阻碍(限额等)';
if (s === 'qr_expired') return '🚨 等扫码但二维码过期';
if (s === 'waiting_relogin') return '⏳ 等待扫码登录';
if (s === 'stopped_by_user') return '⏹️ 手动停止';
@@ -210,6 +211,45 @@ async function waitForRelogin() {
return false;
}
// 阻碍二次核查(v10.6):重新打开一个候选人弹窗,看是否仍是无效内容页
// 返回:确认原因字符串(确认阻碍) / null(未复现,可继续)
async function verifyBlockedPage() {
const pw = require('playwright-core');
try {
const b = await pw.chromium.connectOverCDP(`http://127.0.0.1:${PORT}`);
const p = b.contexts()[0]?.pages().find(x => /rd\d?\.zhaopin\.com/.test(x.url()));
if (!p) return '页面不可达';
const r = await p.evaluate(() => {
const modal = document.querySelector('.new-shortcut-resume__modal');
if (!modal) return null; // 弹窗已关(子进程退出时关了)→ 打开一个再验
const text = modal.innerText || '';
if (/安全限额|查看.*上限|今日.*上限|明天再/.test(text)) return '日浏览限额';
return null;
}).catch(() => null);
// 弹窗关了:主动点开第一个卡片再验一次
if (r === null) {
await p.evaluate(() => document.querySelector('.recommend-item .resume-item__content')?.click()).catch(() => {});
await new Promise(rr => setTimeout(rr, 3000));
const r2 = await p.evaluate(() => {
const modal = document.querySelector('.new-shortcut-resume__modal');
const text = modal?.innerText || '';
if (!modal) return '弹窗无法打开';
// 同样的有效性判定:缺结构信号 = 无效页
const hasNameAge = /\d{2}岁/.test(text) || /(本科|大专|硕士|博士|高中|中专)/.test(text);
if (!hasNameAge) {
if (/安全限额|查看.*上限|明日再|明天再/.test(text)) return '日浏览限额';
return `无效弹窗: ${text.split('\n').filter(Boolean).slice(0, 2).join('/').slice(0, 60)}`;
}
return null; // 弹窗正常 → 未复现
}).catch(() => null);
try { await p.evaluate(() => document.querySelector('.new-shortcut-resume__close')?.click()).catch(() => {}); } catch (_) {}
return r2;
}
try { await b.close(); } catch (_) {}
return r;
} catch (e) { return `核查异常: ${String(e).slice(0, 60)}`; }
}
// ---------- 主流程 ----------
(async () => {
log(`启动 | 端口${PORT} | 库${DB} | 截止北京18:00`);
@@ -250,6 +290,18 @@ async function waitForRelogin() {
maybeSendReport('login_expired', lastChatLeft);
needRelogin = true;
}
// 阻碍即停(v10.6 通用):弹窗无有效简历(限额/提示页等)→ 二次核查确认 → 私聊告警,停止
// 注意:与风控/验证码分开处理——这种是平台功能限制(如日浏览1000份上限),不是风控
else if (/弹窗无有效简历数据/.test(out)) {
log('弹窗无有效数据,二次核查中...');
const confirmed = await verifyBlockedPage();
if (confirmed) {
send(DM, `🚨 ${ACCT} 碰到平台阻碍(${confirmed}),已核查确认,任务停止。请人工查看任务机浏览器确认后再启动\n日志: ${out.match(/弹窗内容: [^\n]*/)?.[0] || ''}`);
maybeSendReport('platform_blocked', lastChatLeft);
break;
}
log('核查未复现(可能是瞬时异常页),跳过继续下一批');
}
// 风控/验证码 → 私聊告警,结束(不监听,风控需人工评估)
else if (/ALERT.*(风控|验证|频繁|受限)/.test(out)) {
send(DM, `🚨 ${ACCT} 触发风控/验证码(${(out.match(/ALERT[::][^\n]*/) || ['未知原因'])[0]}),今日筛选中止`);