This commit is contained in:
1 parent
c1541b80df
commit
b4c7908029
97 files changed
+12403
-154
No files matched your search
@@ -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);
|
||||
});
|
||||
Reference in new issue
Block a user