写一个油猴脚本实现,具体的脚本代码
// ==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();
})(); 网友回复


