vault backup: 2026-11-06 20:25:47
This commit is contained in:
1 parent
a8080408a7
commit
f7d1bff65f
3 files changed
+142
No files matched your search
@@ -0,0 +1,29 @@
|
|||||||
|
// 检查百家号页面状态:登录态、页面文字、可用的数据入口
|
||||||
|
import { connect, attachPage, evaluate, saveJson } from './lib.mjs';
|
||||||
|
|
||||||
|
const cdp = await connect();
|
||||||
|
const hit = await attachPage(cdp, 'baijiahao.baidu.com');
|
||||||
|
if (!hit) { console.log('NO_BAIJIAHAO_TAB'); process.exit(1); }
|
||||||
|
|
||||||
|
const { sessionId } = hit;
|
||||||
|
await cdp.send('Runtime.enable', {}, sessionId).catch(() => {});
|
||||||
|
|
||||||
|
const info = await evaluate(cdp, sessionId, `(() => {
|
||||||
|
const t = document.body ? document.body.innerText : '';
|
||||||
|
return {
|
||||||
|
url: location.href,
|
||||||
|
title: document.title,
|
||||||
|
textLen: t.length,
|
||||||
|
textSample: t.slice(0, 3000),
|
||||||
|
links: Array.from(document.querySelectorAll('a')).slice(0, 60).map(a => ({
|
||||||
|
text: (a.innerText || '').trim().slice(0, 40),
|
||||||
|
href: a.href
|
||||||
|
})).filter(x => x.href)
|
||||||
|
};
|
||||||
|
})()`);
|
||||||
|
|
||||||
|
saveJson('out/page-info.json', info);
|
||||||
|
console.log('URL=' + info.url.replace(/[^\x20-\x7E]/g, '?'));
|
||||||
|
console.log('TEXTLEN=' + info.textLen);
|
||||||
|
console.log('SAVED out/page-info.json');
|
||||||
|
process.exit(0);
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
// CDP 客户端:读 DevToolsActivePort,建立 WebSocket 连接,提供 evaluate 能力
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
const CHROME_USER_DATA = 'C:/Users/Administrator/AppData/Local/Google/Chrome/User Data';
|
||||||
|
|
||||||
|
export function readDevToolsPort(userDataDir = CHROME_USER_DATA) {
|
||||||
|
const p = path.join(userDataDir, 'DevToolsActivePort');
|
||||||
|
const raw = fs.readFileSync(p, 'utf8').trim().split(/\r?\n/);
|
||||||
|
const port = Number(raw[0]);
|
||||||
|
const browserPath = raw[1];
|
||||||
|
if (!port || !browserPath) throw new Error('DevToolsActivePort invalid: ' + raw.join('|'));
|
||||||
|
return { port, browserPath, wsUrl: `ws://127.0.0.1:${port}${browserPath}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cdp {
|
||||||
|
constructor(ws) {
|
||||||
|
this.ws = ws;
|
||||||
|
this.id = 0;
|
||||||
|
this.pending = new Map();
|
||||||
|
this.events = [];
|
||||||
|
ws.addEventListener('message', (ev) => {
|
||||||
|
const msg = JSON.parse(ev.data);
|
||||||
|
if (msg.id && this.pending.has(msg.id)) {
|
||||||
|
const { resolve, reject } = this.pending.get(msg.id);
|
||||||
|
this.pending.delete(msg.id);
|
||||||
|
msg.error ? reject(new Error(JSON.stringify(msg.error))) : resolve(msg.result);
|
||||||
|
} else if (msg.method) {
|
||||||
|
this.events.push(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(method, params = {}, sessionId) {
|
||||||
|
const id = ++this.id;
|
||||||
|
const payload = { id, method, params };
|
||||||
|
if (sessionId) payload.sessionId = sessionId;
|
||||||
|
this.ws.send(JSON.stringify(payload));
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.pending.set(id, { resolve, reject });
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.pending.has(id)) {
|
||||||
|
this.pending.delete(id);
|
||||||
|
reject(new Error(`timeout: ${method}`));
|
||||||
|
}
|
||||||
|
}, 90000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function connect({ timeoutMs = 40000 } = {}) {
|
||||||
|
const { wsUrl, port } = readDevToolsPort();
|
||||||
|
const ws = new WebSocket(wsUrl);
|
||||||
|
const opened = new Promise((resolve, reject) => {
|
||||||
|
const t = setTimeout(() => reject(new Error('ws open timeout')), timeoutMs);
|
||||||
|
ws.addEventListener('open', () => { clearTimeout(t); resolve(); });
|
||||||
|
ws.addEventListener('error', (e) => { clearTimeout(t); reject(new Error('ws error')); });
|
||||||
|
});
|
||||||
|
await opened;
|
||||||
|
const cdp = new Cdp(ws);
|
||||||
|
cdp.port = port;
|
||||||
|
return cdp;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listPages(cdp) {
|
||||||
|
const { targetInfos } = await cdp.send('Target.getTargets');
|
||||||
|
return targetInfos.filter(t => t.type === 'page');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function attachPage(cdp, urlMatch, { create = null } = {}) {
|
||||||
|
let pages = await listPages(cdp);
|
||||||
|
let target = pages.find(t => t.url.includes(urlMatch));
|
||||||
|
if (!target && create) {
|
||||||
|
const { targetId } = await cdp.send('Target.createTarget', { url: create, background: false });
|
||||||
|
await new Promise(r => setTimeout(r, 3500));
|
||||||
|
pages = await listPages(cdp);
|
||||||
|
target = pages.find(t => t.targetId === targetId) || pages.find(t => t.url.includes(urlMatch));
|
||||||
|
}
|
||||||
|
if (!target) return null;
|
||||||
|
const { sessionId } = await cdp.send('Target.attachToTarget', { targetId: target.targetId, flatten: true });
|
||||||
|
return { target, sessionId };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function evaluate(cdp, sessionId, expression, { awaitPromise = true } = {}) {
|
||||||
|
const res = await cdp.send('Runtime.evaluate', {
|
||||||
|
expression,
|
||||||
|
returnByValue: true,
|
||||||
|
awaitPromise,
|
||||||
|
userGesture: true,
|
||||||
|
}, sessionId);
|
||||||
|
if (res.exceptionDetails) {
|
||||||
|
throw new Error('EVAL: ' + (res.exceptionDetails.exception?.description || res.exceptionDetails.text));
|
||||||
|
}
|
||||||
|
return res.result?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveJson(file, data) {
|
||||||
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||||
|
fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8');
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 探测:列出所有页面,并找到百家号相关标签
|
||||||
|
import { connect, listPages } from './lib.mjs';
|
||||||
|
|
||||||
|
const cdp = await connect();
|
||||||
|
console.log('CDP connected, port=' + cdp.port);
|
||||||
|
|
||||||
|
const pages = await listPages(cdp);
|
||||||
|
console.log('pages=' + pages.length);
|
||||||
|
for (const p of pages) {
|
||||||
|
const line = `${p.targetId.slice(0, 8)} | ${p.title.slice(0, 50)} | ${p.url.slice(0, 120)}`;
|
||||||
|
console.log('PAGE ' + line.replace(/[^\x20-\x7E|]/g, '?'));
|
||||||
|
}
|
||||||
|
process.exit(0);
|
||||||
Reference in new issue
Block a user