写一个油猴脚本实现,具体的脚本代码
// ==UserScript== // @name 网页DOM树和CSS样式导出器 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 导出网页完整的DOM树结构和所有CSS样式 // @author You // @match *://*/* // @grant none // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // ==/UserScript== (function() { 'use strict'; // 创建导出按钮 function createExportButton() { // 检查按钮是否已存在 if (document.getElementById('export-dom-css-btn')) return; // 创建按钮 const button = document.createElement('button'); button.id = 'export-dom-css-btn'; button.textContent = '导出DOM和CSS'; button.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 999999; padding: 10px 15px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); `; // 添加悬停效果 button.addEventListener('mouseenter', () => { button.style.background = '#45a049'; }); button.addEventListener('mouseleave', () => { button.style.background = '#4CAF50'; }); // 点击事件 button.addEventListener('click', exportDOMAndCSS); // 添加到页面 document.body.appendChild(button); } // 导出DOM和CSS function exportDOMAndCSS() { try { // 获取完整的DOM树 const domTree = getDOMTree(); // 获取所有CSS样式 const cssStyles = getAllCSSStyles(); // 生成完整的HTML文档 const fullHTML = generateFullHTML(domTree, cssStyles); // 下载文件 downloadFile(fullHTML, `dom-export-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.html`); alert('DOM树和CSS样式导出成功!'); } catch (error) { console.error('导出失败:', error); alert('导出失败: ' + error.message); } } // 获取完整的DOM树结构 function getDOMTree() { // 克隆整个文档 const clonedDoc = document.cloneNode(true); // 移除脚本标签以避免执行 const scripts = clonedDoc.querySelectorAll('script'); scripts.forEach(script => script.remove()); // 移除导出按钮 const exportBtn = clonedDoc.getElementById('export-dom-css-btn'); if (exportBtn) exportBtn.remove(); // 移除可能的油猴脚本元素 const tampermonkeyElements = clonedDoc.querySelectorAll('[id^="tampermonkey"], [class^="tampermonkey"]'); tampermonkeyElements.forEach(el => el.remove()); return clonedDoc; } // 获取所有CSS样式 function getAllCSSStyles() { let allCSS = ''; // 1. 获取所有内联样式表 for (let i = 0; i < document.styleSheets.length; i++) { try { const sheet = document.styleSheets[i]; const cssText = getCSSFromStyleSheet(sheet); if (cssText) { allCSS += `/* 样式表 ${i + 1} */\n${cssText}\n\n`; } } catch (error) { console.warn(`无法获取样式表 ${i}:`, error); } } // 2. 获取所有内联样式 const inlineStyles = getInlineStyles(); if (inlineStyles) { allCSS += `/* 内联样式 */\n${inlineStyles}\n\n`; } // 3. 获取计算样式(可选,会很大) // const computedStyles = getComputedStyles(); // if (computedStyles) { // allCSS += `/* 计算样式 */\n${computedStyles}\n\n`; // } return allCSS; } // 从样式表获取CSS文本 function getCSSFromStyleSheet(sheet) { try { // 如果是内联样式表 if (sheet.cssRules) { let cssText = ''; for (let j = 0; j < sheet.cssRules.length; j++) { cssText += sheet.cssRules[j].cssText + '\n'; } return cssText; } // 如果是外部样式表,尝试获取href if (sheet.href) { return `/* 外部样式表: ${sheet.href} */\n`; } return ''; } catch (error) { console.warn('获取样式表内容失败:', error); return `/* 无法获取样式表内容: ${error.message} */\n`; } } // 获取所有内联样式 function getInlineStyles() { let inlineCSS = ''; const elementsWithStyle = document.querySelectorAll('[style]'); elementsWithStyle.forEach((el, index) => { const style = el.getAttribute('style'); if (style) { const selector = getUniqueSelector(el); inlineCSS += `${selector} { ${style} }\n`; } }); return inlineCSS; } // 获取元素的唯一选择器 function getUniqueSelector(element) { if (element.id) { return `#${element.id}`; } const tagName = element.tagName.toLowerCase(); const className = element.className ? '.' + element.className.split(' ').join('.') : ''; if (element.parentNode && element.parentNode !== document) { const parentSelector = getUniqueSelector(element.parentNode); const index = Array.from(element.parentNode.children).indexOf(element) + 1; return `${parentSelector} > ${tagName}${className}:nth-child(${index})`; } return `${tagName}${className}`; } // 获取计算样式(可选功能,数据量很大) function getComputedStyles() { let computedCSS = ''; const allElements = document.querySelectorAll('*'); allElements.forEach((el, index) => { try { const computedStyle = window.getComputedStyle(el); const selector = getUniqueSelector(el); let styleText = ''; for (let i = 0; i < computedStyle.length; i++) { const property = computedStyle[i]; const value = computedStyle.getPropertyValue(property); if (value && value !== 'initial' && value !== 'normal') { styleText += ` ${property}: ${value};\n`; } } if (styleText) { computedCSS += `${selector} {\n${styleText}}\n\n`; } } catch (error) { console.warn('获取计算样式失败:', error); } }); return computedCSS; } // 生成完整的HTML文档 function generateFullHTML(domTree, cssStyles) { // 获取原始文档的头部信息 const originalHead = document.head.cloneNode(true); // 移除脚本标签 const scripts = originalHead.querySelectorAll('script'); scripts.forEach(script => script.remove()); // 添加收集的CSS样式 if (cssStyles) { const styleElement = document.createElement('style'); styleElement.textContent = cssStyles; originalHead.appendChild(styleElement); } // 构建完整的HTML const html = ` <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>DOM导出 - ${document.title}</title> ${originalHead.innerHTML} </head> <body> ${domTree.body.innerHTML} </body> </html>`; return html; } // 下载文件 function downloadFile(content, filename) { const blob = new Blob([content], { type: 'text/html;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); // 清理 setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } // 高级导出功能 function createAdvancedExportMenu() { // 创建高级导出菜单 const menu = document.createElement('div'); menu.id = 'advanced-export-menu'; menu.style.cssText = ` position: fixed; top: 70px; right: 20px; z-index: 999998; background: white; border: 1px solid #ccc; border-radius: 5px; padding: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); display: none; min-width: 200px; `; menu.innerHTML = ` <h3 style="margin-top: 0; margin-bottom: 15px;">导出选项</h3> <button id="export-full" style="display: block; width: 100%; margin-bottom: 10px; padding: 8px;">完整导出</button> <button id="export-dom-only" style="display: block; width: 100%; margin-bottom: 10px; padding: 8px;">仅DOM</button> <button id="export-css-only" style="display: block; width: 100%; margin-bottom: 10px; padding: 8px;">仅CSS</button> <label style="display: block; margin-bottom: 5px;"> <input type="checkbox" id="include-computed" /> 包含计算样式 </label> `; document.body.appendChild(menu); // 绑定事件 document.getElementById('export-full').addEventListener('click', () => { const includeComputed = document.getElementById('include-computed').checked; exportWithOptions({ includeComputed, type: 'full' }); menu.style.display = 'none'; }); document.getElementById('export-dom-only').addEventListener('click', () => { exportWithOptions({ type: 'dom' }); menu.style.display = 'none'; }); document.getElementById('export-css-only').addEventListener('click', () => { exportWithOptions({ type: 'css' }); menu.style.display = 'none'; }); return menu; } // 带选项的导出 function exportWithOptions(options) { try { let content = ''; let filename = ''; let mimeType = 'text/html'; switch (options.type) { case 'full': const domTree = getDOMTree(); const cssStyles = getAllCSSStyles(); content = generateFullHTML(domTree, cssStyles); filename = `full-export-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.html`; break; case 'dom': const domOnly = getDOMTree(); content = `<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>DOM导出</title>\n</head>\n<body>\n${domOnly.body.innerHTML}\n</body>\n</html>`; filename = `dom-only-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.html`; break; case 'css': const cssOnly = getAllCSSStyles(); content = cssOnly; filename = `styles-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.css`; mimeType = 'text/css'; break; } downloadFile(content, filename, mimeType); alert('导出成功!'); } catch (error) { console.error('导出失败:', error); alert('导出失败: ' + error.message); } } // 初始化脚本 function init() { // 等待页面加载完成 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', createExportButton); } else { createExportButton(); } // 添加键盘快捷键 (Ctrl+Shift+E) document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.shiftKey && e.key === 'E') { e.preventDefault(); exportDOMAndCSS(); } }); } // 启动脚本 init(); })();
网友回复
ace.js如何获取选择文本的开始和结束行数?
如何把qwen code cli或gemini cli的免费调用额度换成http api对外开放接口?
如何限制windows10电脑只能打开指定的程序?
python如何调用ai大模型实现web网页系统的功能测试并生成测试报告?
有没有免费进行web网站ai仿真人测试生成测试报告的mcp服务或api?
Context Engineering到底是啥,有什么用?
如何使用Google veo 3+高斯溅射(Gaussian Splatting)技术生成4d视频?
浏览器中如何实时调用摄像头扫描二维码?
grok4、gemini2.5pro、gpt5、claude4.1到底谁的编程能力更强一些?
python能将2d平面户型图转换成3d三维户型效果图吗?