Commit ca6c075c authored by 郭阔's avatar 郭阔

feat(news-harvester): wsj login-state recipe + instance recycle helper

- logins/wsj.json: detection-only recipe (checkUrl + loggedOutSelector
  sso.accounts.wsj.com link, no steps — DataDome blocks auto-login); preflight
  now reports real wsj login state instead of 无登录配方
- scripts/instance-quit.js: idempotent browser instance recycle (close all
  tabs → Browser.close fallback); called by harvest.sh EXIT trap
parent 01fbc55f
{
"note": "检测型配方:仅用于登录态检查,不含 steps(WSJ 有 DataDome 反爬,自动登录不可行,人工在 wsj 实例窗口登录一次即可,cookie 落 profile)。登出态首页头部会出现 sso.accounts.wsj.com 链接,登录态则无(2026-10-23 实测)。",
"checkUrl": "https://www.wsj.com/",
"loggedOutSelector": "a[href*=\"sso.accounts.wsj.com\"]"
}
#!/usr/bin/env node
/**
* instance-quit.js — 回收本源浏览器实例(任务结束后调用)。
*
* 登录态持久化在 profile 目录(~/.news-harvester/profiles/<source>/),关闭实例
* 不丢失;下次任务 ensure-chromium.js 以同一 profile 拉起,登录态自动恢复。
*
* 原理:窗口模式(headless=false)下关闭全部标签页,Chromium 自行退出
* (ensure-chromium.js 注释的标准行为)。HTTP PUT /json/close/<id> 即可,
* 无需 WebSocket;headless 下兜底走 CDP Browser.close。
*
* Usage:
* CDP_PORT_OVERRIDE=9312 node scripts/instance-quit.js # 指定端口(与 login-tools 一致)
* node scripts/instance-quit.js # config.chromium.cdpPort / 9222
*
* 幂等:实例不在线 exit 0;任何失败不抛错(harvest.sh 的 trap 里静默调用)。
*/
const fs = require('fs');
const path = require('path');
const CONFIG_PATH = path.join(__dirname, '..', 'config.json');
function cdpPort() {
if (process.env.CDP_PORT_OVERRIDE) return parseInt(process.env.CDP_PORT_OVERRIDE, 10);
try {
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
if (cfg.chromium && cfg.chromium.cdpPort) return parseInt(cfg.chromium.cdpPort, 10);
} catch (e) { /* ignore */ }
return 9222;
}
const PORT = cdpPort();
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function alive() {
try {
const res = await fetch(`http://127.0.0.1:${PORT}/json/version`, { method: 'GET' });
return res.ok;
} catch (e) { return false; }
}
async function main() {
if (!(await alive())) {
console.log(`浏览器实例不在线(端口 ${PORT}),无需回收`);
return;
}
// 1. 关闭全部 page 标签 → 窗口模式 Chromium 自行退出
try {
const tabs = await (await fetch(`http://127.0.0.1:${PORT}/json`)).json();
const pages = tabs.filter((t) => t.type === 'page');
for (const t of pages) {
await fetch(`http://127.0.0.1:${PORT}/json/close/${t.id}`, { method: 'PUT' }).catch(() => {});
}
console.log(`已关闭 ${pages.length} 个标签页(端口 ${PORT})`);
} catch (e) { /* fallthrough */ }
await sleep(1500);
if (!(await alive())) {
console.log(`✅ 浏览器实例已回收(端口 ${PORT})`);
return;
}
// 2. 兜底(如 headless 模式关标签不退出):CDP Browser.close
try {
const WebSocket = require('ws');
const ver = await (await fetch(`http://127.0.0.1:${PORT}/json/version`)).json();
const ws = new WebSocket(ver.webSocketDebuggerUrl);
await new Promise((r, j) => { ws.on('open', r); ws.on('error', j); });
ws.send(JSON.stringify({ id: 1, method: 'Browser.close' }));
await sleep(1000);
ws.close();
console.log(await alive()
? `⚠️ Browser.close 后端口 ${PORT} 仍在线,请手动检查`
: `✅ 浏览器实例已回收(端口 ${PORT},Browser.close)`);
} catch (e) {
console.log(`⚠️ 端口 ${PORT} 回收未确认: ${e.message}`);
}
}
main().catch((e) => console.log(`⚠️ 回收脚本异常(忽略): ${e.message}`));
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment