+
36
-

回答

写一个油猴脚本实现,具体的脚本代码

// ==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();
})();

网友回复

我知道答案,我要回答