#!/usr/bin/env node /** * cdp_net_capture.js —— 用 CDP 抓某个标签页的网络请求(含 postData), 用于拿到 * 前端**现场生成**的请求体 (如 1ms 的 /api/v1/captcha/params 里的 identity_context)。 * * 零依赖: 只用 Node >= 22 内置 WebSocket / fetch。 * * 用法: * node tools/cdp_net_capture.js --port 9222 --match 1ms.run --url-substr captcha/params --reload * node tools/cdp_net_capture.js --match 1ms.run --url-substr captcha/params --reload --wait 6000 */ const fs = require('fs'); function arg(name, def) { const i = process.argv.indexOf('--' + name); if (i < 0) return def; const v = process.argv[i + 1]; return (v === undefined || v.startsWith('--')) ? true : v; } class Cdp { constructor(ws) { this.ws = ws; this.id = 0; this.waits = new Map(); this.handlers = []; } static async connect(url) { const ws = new WebSocket(url); await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; }); const c = new Cdp(ws); ws.onmessage = ev => { const m = JSON.parse(ev.data); if (m.id && c.waits.has(m.id)) { const { res, rej } = c.waits.get(m.id); c.waits.delete(m.id); m.error ? rej(new Error(JSON.stringify(m.error))) : res(m.result); } else if (m.method) { for (const h of c.handlers) h(m); } }; return c; } send(method, params = {}) { const id = ++this.id; this.ws.send(JSON.stringify({ id, method, params })); return new Promise((res, rej) => this.waits.set(id, { res, rej })); } on(fn) { this.handlers.push(fn); } close() { try { this.ws.close(); } catch (e) {} } } const sleep = ms => new Promise(r => setTimeout(r, ms)); (async () => { const port = parseInt(arg('port', '9222'), 10); const match = arg('match', null); const substr = arg('url-substr', null); const wait = parseInt(arg('wait', '8000'), 10); const targets = (await (await fetch(`http://127.0.0.1:${port}/json/list`)).json()) .filter(t => t.type === 'page'); let target = match ? targets.find(t => (t.url || '').includes(match)) : targets[0]; if (!target) throw new Error('没有可用标签页'); const cdp = await Cdp.connect(target.webSocketDebuggerUrl); const reqs = []; cdp.on(m => { if (m.method === 'Network.requestWillBeSent') { const r = m.params.request; if (!substr || r.url.includes(substr)) { reqs.push({ url: r.url, method: r.method, headers: r.headers, postData: r.postData || null, at: Date.now(), }); } } }); await cdp.send('Network.enable'); await cdp.send('Page.enable'); if (arg('reload', false)) { await cdp.send('Page.reload', { ignoreCache: true }); } await sleep(wait); console.log(JSON.stringify({ target: target.url, captured: reqs }, null, 2)); cdp.close(); })().catch(e => { console.error('CDP_ERROR: ' + e.message); process.exit(1); });