const { chromium } = require('playwright'); const fs = require('fs'); const path = require('path'); (async () => { // 启动浏览器 const browser = await chromium.launch({ headless: true // 设置为有头模式,方便观察 }); const context = await browser.newContext(); const page = await context.newPage(); try { page.setViewportSize({ width: 2560, height: 1440 }); // url = "https://play.grafana.org/a/grafana-app-observability-app" url = "https://play.grafana.org/dashboards" // url = "https://play.grafana.org/a/grafana-synthetic-monitoring-app/probes" // 访问目标网站,增加超时时间并添加重试逻辑 console.log(`正在访问 ${url}...`); // 增加超时时间到 120 秒 await page.goto(url, { timeout: 120000, // 增加到 120 秒 waitUntil: 'domcontentloaded' // 改为只等待 DOM 加载完成,不等待所有资源 }); console.log('页面加载完成'); // 等待页面稳定 try { await page.waitForLoadState('networkidle', { timeout: 30000 }); } catch (e) { console.log('网络未完全空闲,但继续执行:', e.message); } // 展开所有折叠的部分 console.log('\n开始展开导航项...'); // 用 Set 来记录已经点击过的按钮 const clickedButtons = new Set(); const expandButtons = async () => { console.log('开始寻找可展开按钮...'); // 查找所有折叠按钮 const buttons = await page.$$('button[aria-label*="Expand"], button[aria-label*="Open"], button[aria-expanded="false"]'); console.log(`找到 ${buttons.length} 个折叠按钮`); let newButtonsFound = false; for (const button of buttons) { try { const ariaLabel = await button.getAttribute('aria-label'); // 检查按钮是否已经点击过 if (!clickedButtons.has(ariaLabel)) { console.log(`点击新按钮: ${ariaLabel}`); await button.click(); clickedButtons.add(ariaLabel); newButtonsFound = true; await page.waitForTimeout(200); } } catch (e) { console.log(`点击失败: ${e.message}`); } } return newButtonsFound; }; // 持续查找和点击,直到没有新按钮 let iteration = 1; while (true) { console.log(`\n第 ${iteration} 次查找...`); const foundNewButtons = await expandButtons(); if (!foundNewButtons) { console.log('没有发现新的可展开按钮,结束查找'); break; } console.log(`已点击按钮数量: ${clickedButtons.size}`); await page.waitForTimeout(500); iteration++; } // 确保截图目录存在 const screenshotDir = path.join(__dirname, 'temp_screenshot'); fs.rmSync(screenshotDir, { recursive: true, force: true }); fs.mkdirSync(screenshotDir, { recursive: true }); console.log(`创建截图目录: ${screenshotDir}`); const anchorHandles = await page.$$('a'); console.log(`找到 ${anchorHandles.length} 个链接`); for (let i = 0; i < anchorHandles.length; i++) { try { const anchorHandle = anchorHandles[i]; // 先获取 标签的 href 与文本内容 const anchorData = await page.evaluate(el => { return { url: el.href, text: el.innerText.trim() }; }, anchorHandle); // 生成文件名 (使用索引和文本内容的组合) let filename = `link_${i+1}_${anchorData.text}`; // 替换不合法的文件名字符 filename = filename.replace(/[\\/:*?"<>|]/g, '_'); // 限制文件名长度 if (filename.length > 100) filename = filename.substring(0, 100); try { // 使用更可靠的滚动方法 await page.evaluate(element => { // 使用JavaScript的scrollIntoView,更直接且兼容性更好 element.scrollIntoView({behavior: 'smooth', block: 'center'}); }, anchorHandle); await page.waitForTimeout(500); // 给滚动和渲染更多时间 const rect = await anchorHandle.boundingBox(); filename = `${filename}_${rect.x}_${rect.y}_${rect.width}_${rect.height}.png`; // 截图并保存 const screenshotPath = path.join(screenshotDir, filename); await page.screenshot({ path: screenshotPath }); console.log(`处理链接 ${i+1}/${anchorHandles.length}: ${anchorData.text} - 已截图保存至 ${filename}`); } catch (scrollError) { console.log(`处理链接 ${i+1}/${anchorHandles.length}: ${anchorData.text} - 滚动失败但尝试截图`); // 即使滚动失败也尝试截图 try { const screenshotPath = path.join(screenshotDir, filename); await page.screenshot({ path: screenshotPath }); } catch (e) { console.error(`截图失败: ${e.message}`); } } } catch (error) { console.error(`处理第 ${i+1} 个链接时出错:`, error.message); } } console.log(`链接总数: ${anchorHandles.length}, 截图已保存到 ${screenshotDir}`); console.log('\n等待1000秒...'); await page.waitForTimeout(1000 * 1000); } catch (error) { console.error('发生错误:', error); } finally { // 关闭浏览器 await browser.close(); } })();