使用JavaScript获取和解析页面内容的完整指南

文章目录

    • [1. 理解DOM和页面结构](#1. 理解DOM和页面结构)
      • [1.1 DOM树结构](#1.1 DOM树结构)
      • [1.2 为什么需要解析页面内容](#1.2 为什么需要解析页面内容)
    • [2. 获取整个页面的HTML代码](#2. 获取整个页面的HTML代码)
      • [2.1 使用document.documentElement.outerHTML](#2.1 使用document.documentElement.outerHTML)
      • [2.2 使用document.documentElement.innerHTML](#2.2 使用document.documentElement.innerHTML)
      • [2.3 使用document.getElementsByTagName('html')0](#2.3 使用document.getElementsByTagName('html')[0])
      • [2.4 获取DOCTYPE声明](#2.4 获取DOCTYPE声明)
    • [3. 解析页面内容](#3. 解析页面内容)
      • [3.1 使用DOM选择器方法](#3.1 使用DOM选择器方法)
        • [3.1.1 getElementById](#3.1.1 getElementById)
        • [3.1.2 getElementsByClassName](#3.1.2 getElementsByClassName)
        • [3.1.3 getElementsByTagName](#3.1.3 getElementsByTagName)
        • [3.1.4 querySelector和querySelectorAll](#3.1.4 querySelector和querySelectorAll)
      • [3.2 遍历DOM树](#3.2 遍历DOM树)
        • [3.2.1 父节点和子节点](#3.2.1 父节点和子节点)
        • [3.2.2 兄弟节点](#3.2.2 兄弟节点)
        • [3.2.3 递归遍历整个DOM树](#3.2.3 递归遍历整个DOM树)
      • [3.3 提取元素属性和内容](#3.3 提取元素属性和内容)
        • [3.3.1 获取元素属性](#3.3.1 获取元素属性)
        • [3.3.2 获取元素文本内容](#3.3.2 获取元素文本内容)
        • [3.3.3 获取表单元素值](#3.3.3 获取表单元素值)
    • [4. 高级解析技术](#4. 高级解析技术)
      • [4.1 使用XPath解析](#4.1 使用XPath解析)
      • [4.2 使用TreeWalker遍历DOM](#4.2 使用TreeWalker遍历DOM)
      • [4.3 使用DOMParser解析HTML字符串](#4.3 使用DOMParser解析HTML字符串)
      • [4.4 使用MutationObserver监听DOM变化](#4.4 使用MutationObserver监听DOM变化)
    • [5. 实际应用示例](#5. 实际应用示例)
      • [5.1 提取所有链接](#5.1 提取所有链接)
      • [5.2 提取文章内容](#5.2 提取文章内容)
      • [5.3 提取表格数据](#5.3 提取表格数据)
      • [5.4 提取元数据](#5.4 提取元数据)
    • [6. 处理动态内容](#6. 处理动态内容)
      • [6.1 检测动态加载的内容](#6.1 检测动态加载的内容)
      • [6.2 等待特定元素出现](#6.2 等待特定元素出现)
      • [6.3 模拟滚动以加载更多内容](#6.3 模拟滚动以加载更多内容)
    • [7. 性能优化和最佳实践](#7. 性能优化和最佳实践)
      • [7.1 批量操作减少重绘](#7.1 批量操作减少重绘)
      • [7.2 使用事件委托提高性能](#7.2 使用事件委托提高性能)
      • [7.3 缓存DOM查询结果](#7.3 缓存DOM查询结果)
      • [7.4 使用更高效的选择器](#7.4 使用更高效的选择器)
    • [8. 安全考虑](#8. 安全考虑)
      • [8.1 防止XSS攻击](#8.1 防止XSS攻击)
      • [8.2 处理用户生成的内容](#8.2 处理用户生成的内容)
    • [9. 跨域限制和解决方案](#9. 跨域限制和解决方案)
      • [9.1 同源策略限制](#9.1 同源策略限制)
      • [9.2 使用CORS](#9.2 使用CORS)
      • [9.3 使用代理服务器](#9.3 使用代理服务器)
      • [9.4 浏览器扩展解决方案](#9.4 浏览器扩展解决方案)
    • [10. 完整的页面解析工具示例](#10. 完整的页面解析工具示例)
    • [11. 总结](#11. 总结)

1. 理解DOM和页面结构

在开始获取和解析页面内容之前,我们需要理解DOM(Document Object Model)的概念。DOM是将HTML或XML文档表示为树状结构的编程接口,其中每个节点都是文档的一部分,如元素、属性或文本。

1.1 DOM树结构

DOM将文档表示为节点树,其中:

  • 文档节点是整个文档的根节点
  • 元素节点代表HTML元素
  • 属性节点代表HTML属性
  • 文本节点包含元素内的文本内容
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>示例页面</title>
</head>
<body>
    <h1>欢迎</h1>
    <p class="intro">这是一个示例段落。</p>
</body>
</html>

对应的DOM树结构:

  • Document
    • html
      • head
        • title
          • "示例页面" (文本节点)
      • body
        • h1
          • "欢迎" (文本节点)
        • p (class属性为"intro")
          • "这是一个示例段落。" (文本节点)

1.2 为什么需要解析页面内容

解析页面内容有许多实际应用:

  • 网页抓取(Web Scraping)
  • 内容分析
  • 自动化测试
  • 浏览器扩展开发
  • 数据提取和转换

2. 获取整个页面的HTML代码

2.1 使用document.documentElement.outerHTML

获取整个页面HTML代码的最简单方法是使用document.documentElement.outerHTML属性:

javascript 复制代码
const fullPageHTML = document.documentElement.outerHTML;
console.log(fullPageHTML); // 输出完整的HTML文档

原理

  • document.documentElement代表HTML文档的根元素(通常是<html>元素)
  • outerHTML属性获取元素及其所有子元素的HTML表示

2.2 使用document.documentElement.innerHTML

如果只需要<html>元素内部的内容(不包括<html>标签本身),可以使用:

javascript 复制代码
const htmlContent = document.documentElement.innerHTML;
console.log(htmlContent); // 输出<html>内部的所有内容

2.3 使用document.getElementsByTagName('html')0

另一种获取整个HTML内容的方式:

javascript 复制代码
const htmlElement = document.getElementsByTagName('html')[0];
const fullHTML = htmlElement.outerHTML;
console.log(fullHTML);

2.4 获取DOCTYPE声明

如果需要包含DOCTYPE声明,可以组合使用:

javascript 复制代码
const doctype = document.doctype;
const doctypeString = doctype ? `<!DOCTYPE ${doctype.name}${doctype.publicId ? ` PUBLIC "${doctype.publicId}"` : ''}${doctype.systemId ? ` "${doctype.systemId}"` : ''}>` : '';
const fullDocument = doctypeString + document.documentElement.outerHTML;
console.log(fullDocument);

3. 解析页面内容

获取HTML代码后,下一步是解析其中的内容。JavaScript提供了多种方法来选择和操作DOM元素。

3.1 使用DOM选择器方法

3.1.1 getElementById

通过元素的ID获取单个元素:

javascript 复制代码
const header = document.getElementById('header');
console.log(header.textContent);
3.1.2 getElementsByClassName

通过类名获取元素集合:

javascript 复制代码
const items = document.getElementsByClassName('item');
Array.from(items).forEach(item => {
    console.log(item.textContent);
});
3.1.3 getElementsByTagName

通过标签名获取元素集合:

javascript 复制代码
const paragraphs = document.getElementsByTagName('p');
Array.from(paragraphs).forEach(p => {
    console.log(p.innerHTML);
});
3.1.4 querySelector和querySelectorAll

使用CSS选择器语法选择元素:

javascript 复制代码
// 获取第一个匹配的元素
const firstItem = document.querySelector('.list-item');
console.log(firstItem.textContent);

// 获取所有匹配的元素
const allItems = document.querySelectorAll('.list-item');
allItems.forEach(item => {
    console.log(item.textContent);
});

3.2 遍历DOM树

3.2.1 父节点和子节点
javascript 复制代码
const parent = document.querySelector('.parent');
const children = parent.children; // 获取所有子元素

// 遍历子节点
Array.from(children).forEach(child => {
    console.log(child.tagName);
});

// 获取父节点
const child = document.querySelector('.child');
const parentNode = child.parentNode;
console.log(parentNode.tagName);
3.2.2 兄弟节点
javascript 复制代码
const item = document.querySelector('.item');
const nextSibling = item.nextElementSibling;
const previousSibling = item.previousElementSibling;

console.log('下一个兄弟节点:', nextSibling);
console.log('上一个兄弟节点:', previousSibling);
3.2.3 递归遍历整个DOM树
javascript 复制代码
function traverseDOM(node, depth = 0) {
    // 打印当前节点信息
    console.log(`${' '.repeat(depth * 2)}${node.nodeName}${node.nodeValue ? `: ${node.nodeValue.trim()}` : ''}`);
    
    // 如果有子节点,递归遍历
    if (node.childNodes && node.childNodes.length > 0) {
        Array.from(node.childNodes).forEach(child => {
            traverseDOM(child, depth + 1);
        });
    }
}

// 从body开始遍历
traverseDOM(document.body);

3.3 提取元素属性和内容

3.3.1 获取元素属性
javascript 复制代码
const link = document.querySelector('a');
console.log('href:', link.getAttribute('href'));
console.log('class:', link.className);
console.log('id:', link.id);
console.log('所有属性:', link.attributes);
3.3.2 获取元素文本内容
javascript 复制代码
const paragraph = document.querySelector('p');
console.log('textContent:', paragraph.textContent); // 包括隐藏元素的文本
console.log('innerText:', paragraph.innerText); // 仅显示文本,受CSS影响
console.log('innerHTML:', paragraph.innerHTML); // 包含HTML标签
3.3.3 获取表单元素值
javascript 复制代码
const input = document.querySelector('input[type="text"]');
console.log('输入值:', input.value);
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/a3878042aafe4c62b4b69a2fa122c956.png)
const checkbox = document.querySelector('input[type="checkbox"]');
console.log('是否选中:', checkbox.checked);

const select = document.querySelector('select');
console.log('选择的值:', select.value);
console.log('选择的文本:', select.options[select.selectedIndex].text);

4. 高级解析技术

4.1 使用XPath解析

XPath提供了一种在XML/HTML文档中导航和选择节点的强大方式:

javascript 复制代码
// 评估XPath表达式
function evaluateXPath(xpath, context = document) {
    const result = [];
    const query = document.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    
    for (let i = 0; i < query.snapshotLength; i++) {
        result.push(query.snapshotItem(i));
    }
    
    return result;
}

// 使用示例:获取所有h2标题的文本
const headings = evaluateXPath('//h2');
headings.forEach(h2 => {
    console.log(h2.textContent);
});

4.2 使用TreeWalker遍历DOM

TreeWalker接口提供了更灵活的DOM遍历方式:

javascript 复制代码
const treeWalker = document.createTreeWalker(
    document.body, // 根节点
    NodeFilter.SHOW_ELEMENT, // 只显示元素节点
    { acceptNode: function(node) { 
        return node.tagName === 'P' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; 
    }}, // 只接受<p>元素
    false
);

const paragraphs = [];
let currentNode = treeWalker.nextNode();
while (currentNode) {
    paragraphs.push(currentNode);
    currentNode = treeWalker.nextNode();
}

console.log('找到的段落:', paragraphs);

4.3 使用DOMParser解析HTML字符串

如果需要解析HTML字符串而不是现有文档:

javascript 复制代码
const htmlString = `<html><body><h1>标题</h1><p>段落内容</p></body></html>`;
const parser = new DOMParser();
const doc = parser.parseFromString(htmlString, 'text/html');

// 现在可以像普通DOM一样操作
const title = doc.querySelector('h1');
console.log(title.textContent); // 输出"标题"

4.4 使用MutationObserver监听DOM变化

如果需要监控DOM的变化:

javascript 复制代码
const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        console.log('DOM发生了变化:', mutation);
        
        if (mutation.addedNodes.length) {
            console.log('添加的节点:', mutation.addedNodes);
        }
        
        if (mutation.removedNodes.length) {
            console.log('移除的节点:', mutation.removedNodes);
        }
    });
});

// 开始观察body元素及其子元素的变化
observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    characterData: true
});

// 停止观察
// observer.disconnect();

5. 实际应用示例

5.1 提取所有链接

javascript 复制代码
function extractAllLinks() {
    const links = document.querySelectorAll('a[href]');
    const urls = Array.from(links).map(link => {
        return {
            text: link.textContent.trim(),
            href: link.getAttribute('href'),
            title: link.getAttribute('title') || ''
        };
    });
    
    console.log('页面中的所有链接:', urls);
    return urls;
}

extractAllLinks();

5.2 提取文章内容

javascript 复制代码
function extractArticleContent() {
    // 尝试找到可能包含文章内容的元素
    const potentialSelectors = [
        'article', 
        '.article', 
        '.post', 
        '.content', 
        'main', 
        '#main'
    ];
    
    let articleElement = null;
    
    for (const selector of potentialSelectors) {
        const element = document.querySelector(selector);
        if (element) {
            articleElement = element;
            break;
        }
    }
    
    // 如果没有找到特定元素,尝试启发式方法
    if (!articleElement) {
        // 查找包含多个段落的最长元素
        const allElements = document.querySelectorAll('body *');
        let maxLength = 0;
        
        allElements.forEach(el => {
            const textLength = el.textContent.trim().length;
            const paragraphCount = el.querySelectorAll('p').length;
            
            if (textLength > maxLength && paragraphCount > 1) {
                maxLength = textLength;
                articleElement = el;
            }
        });
    }
    
    if (articleElement) {
        const title = document.querySelector('h1') || 
                     document.querySelector('title') || 
                     { textContent: '无标题' };
        
        const paragraphs = Array.from(articleElement.querySelectorAll('p'))
            .map(p => p.textContent.trim())
            .filter(text => text.length > 0);
        
        const images = Array.from(articleElement.querySelectorAll('img'))
            .map(img => img.getAttribute('src'));
        
        return {
            title: title.textContent.trim(),
            paragraphs,
            images
        };
    }
    
    return null;
}

console.log('提取的文章内容:', extractArticleContent());

5.3 提取表格数据

javascript 复制代码
function extractTableData() {
    const tables = document.querySelectorAll('table');
    const tableData = [];
    
    tables.forEach((table, index) => {
        const rows = table.querySelectorAll('tr');
        const data = [];
        
        rows.forEach(row => {
            const cells = row.querySelectorAll('td, th');
            const rowData = Array.from(cells).map(cell => cell.textContent.trim());
            data.push(rowData);
        });
        
        tableData.push({
            tableIndex: index + 1,
            rows: data
        });
    });
    
    console.log('提取的表格数据:', tableData);
    return tableData;
}

extractTableData();

5.4 提取元数据

javascript 复制代码
function extractMetaData() {
    const metaTags = document.querySelectorAll('meta');
    const metadata = {};
    
    metaTags.forEach(tag => {
        const name = tag.getAttribute('name') || 
                    tag.getAttribute('property') || 
                    tag.getAttribute('itemprop');
        const content = tag.getAttribute('content');
        
        if (name && content) {
            metadata[name] = content;
        }
    });
    
    // 获取标题
    metadata.title = document.title;
    
    // 获取描述(优先从meta标签获取)
    if (!metadata.description) {
        const firstParagraph = document.querySelector('p');
        if (firstParagraph) {
            metadata.description = firstParagraph.textContent.trim().substring(0, 150) + '...';
        }
    }
    
    // 获取关键词
    if (!metadata.keywords) {
        metadata.keywords = [];
    } else if (typeof metadata.keywords === 'string') {
        metadata.keywords = metadata.keywords.split(',').map(k => k.trim());
    }
    
    console.log('页面元数据:', metadata);
    return metadata;
}

extractMetaData();

6. 处理动态内容

现代网页经常使用JavaScript动态加载内容,这给内容提取带来了挑战。

6.1 检测动态加载的内容

javascript 复制代码
// 使用MutationObserver检测动态加载的内容
function watchForDynamicContent(callback) {
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes.length) {
                callback(mutation.addedNodes);
            }
        });
    });
    
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
    
    return observer;
}

// 示例:检测新加载的内容并提取其中的链接
const dynamicLinks = new Set();

const observer = watchForDynamicContent(nodes => {
    nodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE) {
            const links = node.querySelectorAll('a[href]');
            links.forEach(link => {
                const href = link.getAttribute('href');
                if (!dynamicLinks.has(href)) {
                    dynamicLinks.add(href);
                    console.log('发现新链接:', href);
                }
            });
        }
    });
});

// 停止观察
// observer.disconnect();

6.2 等待特定元素出现

javascript 复制代码
function waitForElement(selector, timeout = 5000) {
    return new Promise((resolve, reject) => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }
        
        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                observer.disconnect();
                resolve(document.querySelector(selector));
            }
        });
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        setTimeout(() => {
            observer.disconnect();
            reject(new Error(`等待元素 "${selector}" 超时`));
        }, timeout);
    });
}

// 使用示例
waitForElement('.dynamic-content')
    .then(element => {
        console.log('元素已加载:', element);
    })
    .catch(error => {
        console.error(error);
    });

6.3 模拟滚动以加载更多内容

javascript 复制代码
async function scrollToLoadAllContent() {
    let lastHeight = document.body.scrollHeight;
    let attempts = 0;
    const maxAttempts = 10;
    
    while (attempts < maxAttempts) {
        // 滚动到底部
        window.scrollTo(0, document.body.scrollHeight);
        
        // 等待内容加载
        await new Promise(resolve => setTimeout(resolve, 2000));
        
        // 检查高度是否变化
        const newHeight = document.body.scrollHeight;
        if (newHeight === lastHeight) {
            break;
        }
        
        lastHeight = newHeight;
        attempts++;
    }
    
    console.log('完成滚动,最终高度:', lastHeight);
}

// 使用示例
scrollToLoadAllContent().then(() => {
    console.log('所有内容已加载(或达到最大尝试次数)');
});

7. 性能优化和最佳实践

7.1 批量操作减少重绘

javascript 复制代码
// 不推荐的方式(每次循环都会导致重绘)
const items = document.querySelectorAll('.item');
items.forEach(item => {
    item.style.color = 'red';
});

// 推荐的方式(使用文档片段批量操作)
const fragment = document.createDocumentFragment();
const newItems = Array(10).fill().map((_, i) => {
    const div = document.createElement('div');
    div.className = 'item';
    div.textContent = `项目 ${i + 1}`;
    fragment.appendChild(div);
    return div;
});

document.body.appendChild(fragment);

7.2 使用事件委托提高性能

javascript 复制代码
// 不推荐的方式(为每个元素添加事件监听器)
document.querySelectorAll('.clickable-item').forEach(item => {
    item.addEventListener('click', handleClick);
});

// 推荐的方式(事件委托)
document.body.addEventListener('click', event => {
    if (event.target.closest('.clickable-item')) {
        handleClick(event);
    }
});

function handleClick(event) {
    console.log('点击的项目:', event.target);
}

7.3 缓存DOM查询结果

javascript 复制代码
// 不推荐的方式(多次查询相同的元素)
function updateElements() {
    document.querySelector('.item').style.color = 'red';
    document.querySelector('.item').style.fontSize = '16px';
    document.querySelector('.item').textContent = '更新后的文本';
}

// 推荐的方式(缓存查询结果)
function updateElementsOptimized() {
    const item = document.querySelector('.item');
    item.style.color = 'red';
    item.style.fontSize = '16px';
    item.textContent = '更新后的文本';
}

7.4 使用更高效的选择器

javascript 复制代码
// 不高效的选择器(过于通用)
const allDivs = document.querySelectorAll('div div div');

// 更高效的选择器(更具体)
const specificDivs = document.querySelectorAll('.container > .wrapper > .content');

8. 安全考虑

8.1 防止XSS攻击

当处理动态内容时,要注意防范XSS(跨站脚本)攻击:

javascript 复制代码
// 不安全的方式(直接插入HTML)
function unsafeInsert(content) {
    document.querySelector('.output').innerHTML = content;
}

// 安全的方式(使用textContent或DOMPurify)
function safeInsert(content) {
    // 方法1:仅插入文本
    document.querySelector('.output').textContent = content;
    
    // 方法2:使用DOMPurify清理HTML
    // const clean = DOMPurify.sanitize(content);
    // document.querySelector('.output').innerHTML = clean;
}

8.2 处理用户生成的内容

javascript 复制代码
function sanitizeUserInput(input) {
    // 移除脚本标签
    let sanitized = input.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
    
    // 移除危险的属性
    sanitized = sanitized.replace(/\son\w+="[^"]*"/g, '');
    
    // 其他清理逻辑...
    return sanitized;
}

const userInput = '<script>alert("XSS")</script><img src="x" onerror="alert(1)">';
console.log('清理后的输入:', sanitizeUserInput(userInput));

9. 跨域限制和解决方案

9.1 同源策略限制

浏览器出于安全考虑实施了同源策略,限制了从不同源(协议、域名、端口)加载和操作内容的能力。

9.2 使用CORS

如果目标服务器支持CORS(跨源资源共享),可以直接请求:

javascript 复制代码
fetch('https://api.example.com/data', {
    method: 'GET',
    mode: 'cors',
    headers: {
        'Content-Type': 'application/json'
    }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('错误:', error));

9.3 使用代理服务器

对于不支持CORS的网站,可以通过自己的服务器代理请求:

javascript 复制代码
// 前端代码
fetch('/proxy?url=' + encodeURIComponent('https://example.com'))
    .then(response => response.text())
    .then(html => {
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, 'text/html');
        // 解析文档...
    });

// 服务器端(Node.js示例)
/*
app.get('/proxy', async (req, res) => {
    const { url } = req.query;
    try {
        const response = await axios.get(url);
        res.send(response.data);
    } catch (error) {
        res.status(500).send('代理请求失败');
    }
});
*/

9.4 浏览器扩展解决方案

如果是开发浏览器扩展,可以使用chrome.webRequest API绕过某些限制:

javascript 复制代码
// 在manifest.json中声明权限
/*
"permissions": [
    "webRequest",
    "webRequestBlocking",
    "<all_urls>"
]
*/

// 在background.js中
/*
chrome.webRequest.onBeforeSendHeaders.addListener(
    details => {
        // 修改请求头
        details.requestHeaders.push({
            name: 'Origin',
            value: 'https://your-extension-id.chromiumapp.org'
        });
        return { requestHeaders: details.requestHeaders };
    },
    { urls: ['<all_urls>'] },
    ['blocking', 'requestHeaders']
);
*/

10. 完整的页面解析工具示例

下面是一个完整的示例,展示如何构建一个功能丰富的页面解析工具:

javascript 复制代码
class PageParser {
    constructor() {
        this.parsedData = {
            metadata: {},
            structure: {},
            content: {},
            resources: {}
        };
    }
    
    // 解析页面元数据
    parseMetadata() {
        // 标题
        this.parsedData.metadata.title = document.title;
        
        // meta标签
        this.parsedData.metadata.metaTags = {};
        document.querySelectorAll('meta').forEach(tag => {
            const name = tag.getAttribute('name') || 
                        tag.getAttribute('property') || 
                        tag.getAttribute('itemprop');
            if (name) {
                this.parsedData.metadata.metaTags[name] = tag.getAttribute('content');
            }
        });
        
        // 链接标签
        this.parsedData.metadata.links = [];
        document.querySelectorAll('link').forEach(link => {
            this.parsedData.metadata.links.push({
                rel: link.getAttribute('rel'),
                href: link.getAttribute('href'),
                type: link.getAttribute('type')
            });
        });
        
        return this;
    }
    
    // 分析页面结构
    analyzeStructure() {
        // 统计各类元素数量
        this.parsedData.structure.elementCounts = {};
        const allElements = document.querySelectorAll('*');
        Array.from(allElements).forEach(el => {
            const tag = el.tagName.toLowerCase();
            this.parsedData.structure.elementCounts[tag] = 
                (this.parsedData.structure.elementCounts[tag] || 0) + 1;
        });
        
        // 获取主要内容区域
        this.parsedData.structure.mainContent = this.findMainContent();
        
        return this;
    }
    
    // 查找主要内容区域
    findMainContent() {
        const contentSelectors = [
            'main',
            'article',
            '.main-content',
            '.content',
            '#content',
            '.article',
            '.post'
        ];
        
        for (const selector of contentSelectors) {
            const element = document.querySelector(selector);
            if (element) {
                return {
                    selector,
                    textLength: element.textContent.length,
                    paragraphCount: element.querySelectorAll('p').length
                };
            }
        }
        
        // 启发式方法:查找包含最多文本的元素
        let maxLength = 0;
        let mainElement = null;
        
        document.querySelectorAll('body > div, body > section').forEach(el => {
            const length = el.textContent.length;
            if (length > maxLength) {
                maxLength = length;
                mainElement = el;
            }
        });
        
        return mainElement ? {
            selector: this.generateSelector(mainElement),
            textLength: mainElement.textContent.length,
            paragraphCount: mainElement.querySelectorAll('p').length
        } : null;
    }
    
    // 生成元素选择器
    generateSelector(element) {
        if (element.id) {
            return `#${element.id}`;
        }
        
        const path = [];
        let current = element;
        
        while (current && current !== document.body) {
            let selector = current.tagName.toLowerCase();
            
            if (current.className && typeof current.className === 'string') {
                const classes = current.className.split(/\s+/).filter(c => c);
                if (classes.length) {
                    selector += `.${classes.join('.')}`;
                }
            }
            
            // 如果有兄弟元素,添加:nth-child
            const siblings = Array.from(current.parentNode.children);
            const index = siblings.indexOf(current);
            if (siblings.length > 1) {
                selector += `:nth-child(${index + 1})`;
            }
            
            path.unshift(selector);
            current = current.parentNode;
        }
        
        return path.join(' > ');
    }
    
    // 提取页面内容
    extractContent() {
        // 提取所有文本段落
        this.parsedData.content.paragraphs = Array.from(document.querySelectorAll('p'))
            .map(p => p.textContent.trim())
            .filter(text => text.length > 0);
        
        // 提取所有标题
        this.parsedData.content.headings = {};
        ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
            this.parsedData.content.headings[tag] = Array.from(document.querySelectorAll(tag))
                .map(el => el.textContent.trim());
        });
        
        // 提取图片
        this.parsedData.content.images = Array.from(document.querySelectorAll('img'))
            .map(img => ({
                src: img.getAttribute('src'),
                alt: img.getAttribute('alt') || '',
                width: img.width,
                height: img.height
            }));
        
        return this;
    }
    
    // 收集页面资源
    collectResources() {
        // 脚本
        this.parsedData.resources.scripts = Array.from(document.querySelectorAll('script[src]'))
            .map(script => script.getAttribute('src'));
        
        // 样式表
        this.parsedData.resources.stylesheets = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
            .map(link => link.getAttribute('href'));
        
        // 图片
        this.parsedData.resources.images = Array.from(document.querySelectorAll('img[src]'))
            .map(img => img.getAttribute('src'));
        
        // 外部链接
        this.parsedData.resources.links = Array.from(document.querySelectorAll('a[href]'))
            .filter(a => {
                const href = a.getAttribute('href');
                return href && !href.startsWith('#') && !href.startsWith('javascript:');
            })
            .map(a => a.getAttribute('href'));
        
        return this;
    }
    
    // 获取解析结果
    getResult() {
        return this.parsedData;
    }
    
    // 静态方法:完整解析页面
    static parseFullPage() {
        return new PageParser()
            .parseMetadata()
            .analyzeStructure()
            .extractContent()
            .collectResources()
            .getResult();
    }
}

// 使用示例
document.addEventListener('DOMContentLoaded', () => {
    const pageData = PageParser.parseFullPage();
    console.log('完整页面分析结果:', pageData);
    
    // 可以将结果发送到服务器或保存
    // fetch('/api/save-analysis', {
    //     method: 'POST',
    //     body: JSON.stringify(pageData)
    // });
});

11. 总结

本文详细介绍了如何使用JavaScript获取和解析页面内容,涵盖了从基础到高级的各种技术。我们学习了:

  1. 获取页面HTML :使用outerHTMLinnerHTML等方法获取完整或部分的HTML代码
  2. DOM遍历和选择:使用各种选择器方法和遍历技术定位特定元素
  3. 内容提取:从元素中提取文本、属性和结构化数据
  4. 高级技术:XPath、TreeWalker、MutationObserver等高级API的使用
  5. 动态内容处理:监控和等待动态加载的内容
  6. 性能优化:批量操作、事件委托等提高性能的技术
  7. 安全考虑:防范XSS攻击和正确处理用户输入
  8. 跨域限制:理解和解决同源策略带来的限制
  9. 完整工具实现:构建一个功能全面的页面解析工具

通过这些技术,你可以构建强大的网页抓取工具、内容分析系统或浏览器扩展,满足各种实际应用需求。记住在实际应用中要考虑性能、安全和合法性,确保你的代码既高效又负责任。

相关推荐
边境悍匪7 分钟前
蜗牛学苑 Java 智能体学习 Day39|SpringAI 会话存储、Function‑Call 工具调用、MCP 思维导图复盘
java·开发语言·spring boot·学习·阿里云
ellenwan20268 分钟前
看到“最新 AI 量化学习”时,先让表达变清楚
人工智能·python
万物智能信息科技20 分钟前
RK3568 的多路显示移植—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
linux·开发语言·华为·开源·harmonyos
掘金挖土25 分钟前
前端手摸手跑路之 AI 应用开发(三)
前端·后端
蓝悦无人机33 分钟前
Qt Design Studio(QDS)4.8.2项目文件结构详解
开发语言·qt·qt creator·qds·项目文件结构·qtc
计算机魔术师42 分钟前
OpenAI 宣称破解千禧年难题,数学家却说是「反例搜索」?
前端
志尊宝43 分钟前
Vue3 零基础每日笔记(015):条件渲染 v-if 与 v-show——控制 DOM 生死还是控制显隐
前端·vue.js·笔记
yxlalm1 小时前
4.java秒杀项目第四课-后端商品后台接口
java·开发语言
掘金者阿豪1 小时前
OceanBase 和金仓怎么选?别只看分布式,复杂查询更考验架构取舍
前端·后端·架构
Bug修理工Bubble1 小时前
Swift ARC:一个对象到底什么时候才会被释放?
前端