This commit is contained in:
z.ai committed 2026-10-08 11:09:37 +08:00
1 parent c1541b80df
commit b4c7908029
97 files changed
+12403 -154

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
/**
* 通过 CDP 在精确的视口尺寸下截图(含整页截图)。
*
* 为什么不直接用 `chrome --headless --screenshot --window-size`:
* 该模式下截图位图宽度与 CSS 视口宽度可能不一致,导致移动端截图
* 看起来"内容被截断",实际测量(document.scrollWidth)却是正常的。
* 这里用 Emulation.setDeviceMetricsOverride 明确指定视口,
* 保证截图与真实设备表现一致。
*
* 用法:CDP_WS=<ws地址> node scripts/screenshot.mjs <url> <width> <height> <输出路径> [--full]
*/
import { writeFileSync } from "node:fs";
const [, , targetUrl, widthArg, heightArg, outputPath, ...flags] = process.argv;
const width = Number(widthArg ?? 1440);
const height = Number(heightArg ?? 900);
const fullPage = flags.includes("--full");
const wsUrl = process.env.CDP_WS;
if (!wsUrl || !targetUrl || !outputPath) {
console.error(
"用法:CDP_WS=<ws地址> node scripts/screenshot.mjs <url> <width> <height> <输出路径> [--full]",
);
process.exit(1);
}
const socket = new WebSocket(wsUrl);
let messageId = 0;
const pending = new Map();
function send(method, params = {}) {
const id = ++messageId;
socket.send(JSON.stringify({ id, method, params }));
return new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
setTimeout(() => {
if (pending.has(id)) {
pending.delete(id);
reject(new Error(`CDP 超时: ${method}`));
}
}, 40000);
});
}
socket.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.id && pending.has(message.id)) {
const { resolve } = pending.get(message.id);
pending.delete(message.id);
resolve(message.result);
}
});
socket.addEventListener("open", async () => {
await send("Page.enable");
await send("Runtime.enable");
await send("Emulation.setDeviceMetricsOverride", {
width,
height,
deviceScaleFactor: 1,
mobile: width < 768,
});
await send("Page.navigate", { url: targetUrl });
// 等待字体加载与首屏动画稳定
await new Promise((resolve) => setTimeout(resolve, 4500));
// 让所有延迟出现的元素(滚动动画)进入最终状态
await send("Runtime.evaluate", {
expression:
"window.scrollTo(0, document.body.scrollHeight); window.scrollTo(0, 0);",
});
await new Promise((resolve) => setTimeout(resolve, 1200));
const screenshot = await send("Page.captureScreenshot", {
format: "png",
captureBeyondViewport: fullPage,
optimizeForSpeed: false,
});
writeFileSync(outputPath, Buffer.from(screenshot.data, "base64"));
console.log(`已保存 ${outputPath} (${width}x${height}${fullPage ? ", 整页" : ""})`);
socket.close();
process.exit(0);
});