乐闻世界logo
搜索文章和话题

NodeJS

Node 是一个 Javascript 运行环境(runtime)。实际上它是对 Google V8 引擎(应用于 Google Chrome 浏览器)进行了封装。V8 引擎执行 Javascript 的速度非常快,性能非常好。Node 对一些特殊用例进行了优化,提供了替代的 API,使得 V8 在非浏览器环境下运行得更好。例如,在服务器环境中,处理二进制数据通常是必不可少的,但 Javascript 对此支持不足,因此,V8.Node 增加了 Buffer 类,方便并且高效地 处理二进制数据。因此,Node 不仅仅简单的使用了 V8,还对其进行了优化,使其在各环境下更加给力。
NodeJS
查看更多相关内容
Cheerio 和 jsdom 有什么区别?如何选择使用?Cheerio 和 jsdom 都是 Node.js 中处理 HTML/XML 的工具,但它们的设计理念和实现方式有显著差异。以下是详细的对比分析: ## 1. 核心架构对比 ### Cheerio - **类型**:HTML 解析器 - **底层实现**:基于 htmlparser2 - **DOM 实现**:自定义的轻量级 DOM 实现 - **JavaScript 执行**:不支持 - **浏览器环境模拟**:不模拟 ### jsdom - **类型**:完整的 DOM 和浏览器环境模拟器 - **底层实现**:基于 WHATWG DOM 标准 - **DOM 实现**:完整的 W3C DOM 规范实现 - **JavaScript 执行**:完全支持 - **浏览器环境模拟**:完整模拟 ## 2. 功能对比表 | 特性 | Cheerio | jsdom | |------|---------|-------| | **HTML 解析** | ✅ 快速 | ✅ 标准 | | **CSS 选择器** | ✅ jQuery 风格 | ✅ 标准 | | **DOM 操作** | ✅ 基础操作 | ✅ 完整 API | | **JavaScript 执行** | ❌ 不支持 | ✅ 完全支持 | | **事件处理** | ❌ 不支持 | ✅ 完全支持 | | **性能** | ⚡ 极快 | 🐢 较慢 | | **内存占用** | 📉 低 | 📈 高 | | **浏览器 API** | ❌ 无 | ✅ 完整 | | **网络请求** | ❌ 无 | ✅ 支持 | | **Canvas** | ❌ 无 | ✅ 支持 | | **LocalStorage** | ❌ 无 | ✅ 支持 | ## 3. 使用示例对比 ### Cheerio 使用示例 ```javascript const cheerio = require('cheerio'); const html = ` <div id="container"> <p class="text">Hello World</p> <button onclick="alert('Clicked')">Click</button> </div> `; const $ = cheerio.load(html); // 基本操作 console.log($('#container').text()); // "Hello World" console.log($('.text').text()); // "Hello World" // DOM 操作 $('.text').addClass('highlight'); console.log($('.text').attr('class')); // "text highlight" // 无法执行 JavaScript $('button').click(); // 无效,Cheerio 不支持事件 ``` ### jsdom 使用示例 ```javascript const { JSDOM } = require('jsdom'); const html = ` <div id="container"> <p class="text">Hello World</p> <button onclick="alert('Clicked')">Click</button> </div> `; const dom = new JSDOM(html); const document = dom.window.document; // 基本操作 console.log(document.getElementById('container').textContent); // "Hello World" console.log(document.querySelector('.text').textContent); // "Hello World" // DOM 操作 document.querySelector('.text').classList.add('highlight'); console.log(document.querySelector('.text').className); // "text highlight" // 可以执行 JavaScript const button = document.querySelector('button'); button.click(); // 有效,会触发 onclick 事件 // 可以使用浏览器 API console.log(dom.window.innerWidth); // 窗口宽度 console.log(dom.window.location.href); // 当前 URL ``` ## 4. 性能对比 ### 解析速度测试 ```javascript const cheerio = require('cheerio'); const { JSDOM } = require('jsdom'); const largeHtml = '<div>' + '<p>Test</p>'.repeat(10000) + '</div>'; // Cheerio 性能测试 const start1 = Date.now(); const $ = cheerio.load(largeHtml); const cheerioTime = Date.now() - start1; console.log(`Cheerio: ${cheerioTime}ms`); // jsdom 性能测试 const start2 = Date.now(); const dom = new JSDOM(largeHtml); const jsdomTime = Date.now() - start2; console.log(`jsdom: ${jsdomTime}ms`); // 典型结果: // Cheerio: 5-10ms // jsdom: 100-500ms ``` ### 内存占用对比 ```javascript // Cheerio - 内存占用低 function cheerioMemoryTest() { const $ = cheerio.load(largeHtml); const elements = $('p'); return elements.length; } // jsdom - 内存占用高 function jsdomMemoryTest() { const dom = new JSDOM(largeHtml); const elements = dom.window.document.querySelectorAll('p'); return elements.length; } ``` ## 5. 适用场景对比 ### 使用 Cheerio 的场景 ```javascript // 1. 网页爬虫和数据提取 async function scrapeWebsite() { const axios = require('axios'); const response = await axios.get('https://example.com'); const $ = cheerio.load(response.data); return { title: $('title').text(), links: $('a').map((i, el) => $(el).attr('href')).get() }; } // 2. HTML 内容处理 function processHtml(html) { const $ = cheerio.load(html); $('script').remove(); // 移除脚本 $('style').remove(); // 移除样式 return $.html(); } // 3. 批量处理大量文档 function batchProcess(htmlList) { return htmlList.map(html => { const $ = cheerio.load(html); return $('title').text(); }); } ``` ### 使用 jsdom 的场景 ```javascript // 1. 测试前端代码 const { JSDOM } = require('jsdom'); function testFrontendCode() { const dom = new JSDOM(` <div id="app"></div> <script> document.getElementById('app').textContent = 'Hello'; </script> `, { runScripts: 'dangerously' }); console.log(dom.window.document.getElementById('app').textContent); } // 2. 服务端渲染 (SSR) function renderComponent(component) { const dom = new JSDOM('<div id="root"></div>'); const root = dom.window.document.getElementById('root'); // 执行组件代码 component(root); return dom.serialize(); } // 3. 处理需要 JavaScript 的内容 function processDynamicContent(html) { const dom = new JSDOM(html, { runScripts: 'dangerously', resources: 'usable' }); // 等待 JavaScript 执行完成 return new Promise(resolve => { dom.window.onload = () => { resolve(dom.serialize()); }; }); } ``` ## 6. API 对比 ### Cheerio API 特点 ```javascript const $ = cheerio.load(html); // jQuery 风格的 API $('.class').text(); $('.class').html(); $('.class').attr('href'); $('.class').addClass('active'); $('.class').find('a'); // 链式调用 $('.container') .find('.item') .addClass('highlight') .text(); // 不支持的浏览器 API $.window; // undefined $.document; // undefined $.localStorage; // undefined ``` ### jsdom API 特点 ```javascript const dom = new JSDOM(html); const document = dom.window.document; // 标准 DOM API document.querySelector('.class').textContent; document.querySelector('.class').innerHTML; document.querySelector('.class').getAttribute('href'); document.querySelector('.class').classList.add('active'); document.querySelector('.class').querySelector('a'); // 支持浏览器 API dom.window.innerWidth; dom.window.location.href; dom.window.localStorage; dom.window.fetch; dom.window.console; ``` ## 7. 选择建议 ### 选择 Cheerio 的情况 1. **只需要解析和提取数据** - 网页爬虫 - 数据抓取 - HTML 内容处理 2. **性能要求高** - 处理大量文档 - 批量操作 - 实时处理 3. **资源受限** - 内存有限 - CPU 有限 - 无服务器环境 4. **不需要浏览器功能** - 不需要执行 JavaScript - 不需要事件处理 - 不需要浏览器 API ### 选择 jsdom 的情况 1. **需要完整的浏览器环境** - 前端代码测试 - 服务端渲染 - 组件测试 2. **需要执行 JavaScript** - 动态内容处理 - 客户端代码执行 - 框架渲染 3. **需要浏览器 API** - LocalStorage - Fetch API - Canvas - Web Workers 4. **需要标准 DOM 行为** - 事件冒泡 - DOM 事件 - 浏览器兼容性测试 ## 8. 混合使用场景 ```javascript // 先用 jsdom 执行 JavaScript,再用 Cheerio 解析 const { JSDOM } = require('jsdom'); const cheerio = require('cheerio'); async function hybridProcess(html) { // 1. 使用 jsdom 执行 JavaScript const dom = new JSDOM(html, { runScripts: 'dangerously' }); // 等待 JavaScript 执行 await new Promise(resolve => { dom.window.onload = resolve; }); // 2. 获取执行后的 HTML const processedHtml = dom.serialize(); // 3. 使用 Cheerio 快速解析 const $ = cheerio.load(processedHtml); return { title: $('title').text(), content: $('.content').text() }; } ``` ## 总结 - **Cheerio**:轻量、快速、专注数据提取,适合爬虫和静态 HTML 处理 - **jsdom**:完整、标准、模拟浏览器,适合测试和动态内容处理 - **选择原则**:根据需求选择,需要性能用 Cheerio,需要完整功能用 jsdom - **混合使用**:可以结合两者优势,先用 jsdom 执行 JS,再用 Cheerio 解析
服务端 · 2月22日 14:31
Cheerio 和 Puppeteer 有什么区别?如何选择使用?Cheerio 和 Puppeteer 都是 Node.js 中用于处理网页的工具,但它们的设计目标和使用场景有显著差异: ## 1. 核心区别 | 特性 | Cheerio | Puppeteer | |------|---------|-----------| | **类型** | HTML 解析器 | 浏览器自动化工具 | | **JavaScript 执行** | 不支持 | 完全支持 | | **动态内容** | 无法处理 | 完全支持 | | **性能** | 极快 | 较慢 | | **资源消耗** | 低 | 高 | | **API** | jQuery 风格 | 浏览器 DevTools 协议 | | **使用场景** | 静态 HTML 解析 | 动态网页、截图、PDF | ## 2. Cheerio 的特点 ### 优势 - **轻量快速**:核心代码只有几百行,解析速度极快 - **简单易用**:jQuery 风格的 API,学习成本低 - **低资源消耗**:不需要启动浏览器,内存占用少 - **适合批量处理**:可以快速处理大量静态页面 ### 局限性 - **无法执行 JavaScript**:只能解析静态 HTML - **无法处理动态内容**:无法获取通过 JS 动态加载的数据 - **无法处理复杂交互**:不支持点击、滚动等用户操作 - **无法截图或生成 PDF**:没有可视化能力 ### 适用场景 ```javascript // 适合:静态网页数据提取 const cheerio = require('cheerio'); const axios = require('axios'); async function scrapeStaticSite() { const response = await axios.get('https://example.com'); const $ = cheerio.load(response.data); return { title: $('title').text(), links: $('a').map((i, el) => $(el).attr('href')).get() }; } ``` ## 3. Puppeteer 的特点 ### 优势 - **完整浏览器环境**:使用真实的 Chrome/Chromium - **JavaScript 执行**:可以执行页面中的所有 JavaScript - **动态内容支持**:可以获取 AJAX 加载的数据 - **交互能力**:支持点击、输入、滚动等操作 - **可视化功能**:支持截图、生成 PDF - **网络拦截**:可以监控和修改网络请求 ### 局限性 - **资源消耗大**:需要启动完整的浏览器实例 - **速度较慢**:相比 Cheerio 慢很多 - **复杂度高**:API 相对复杂,学习成本高 - **部署困难**:在某些服务器环境部署较复杂 ### 适用场景 ```javascript // 适合:动态网页、需要交互的场景 const puppeteer = require('puppeteer'); async function scrapeDynamicSite() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com', { waitUntil: 'networkidle2' }); // 等待动态内容加载 await page.waitForSelector('.dynamic-content'); const data = await page.evaluate(() => { return { title: document.title, content: document.querySelector('.dynamic-content').textContent }; }); await browser.close(); return data; } ``` ## 4. 性能对比 ```javascript // Cheerio - 快速解析 const cheerio = require('cheerio'); async function cheerioBenchmark() { const start = Date.now(); const $ = cheerio.load(htmlString); const items = $('.item').map((i, el) => $(el).text()).get(); const time = Date.now() - start; console.log(`Cheerio: ${time}ms, ${items.length} items`); // 结果:通常 < 10ms } // Puppeteer - 完整浏览器 const puppeteer = require('puppeteer'); async function puppeteerBenchmark() { const start = Date.now(); const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.setContent(htmlString); const items = await page.$$eval('.item', elements => elements.map(el => el.textContent) ); await browser.close(); const time = Date.now() - start; console.log(`Puppeteer: ${time}ms, ${items.length} items`); // 结果:通常 500-2000ms } ``` ## 5. 选择建议 ### 使用 Cheerio 的场景 - 网站内容是静态 HTML - 需要处理大量页面 - 对性能要求高 - 只需要提取数据,不需要交互 - 服务器资源有限 ### 使用 Puppeteer 的场景 - 网站使用 JavaScript 动态加载内容 - 需要模拟用户操作(点击、滚动等) - 需要截图或生成 PDF - 需要处理复杂的 SPA 应用 - 需要监控网络请求 ### 混合使用场景 ```javascript // 先用 Puppeteer 获取动态内容,再用 Cheerio 解析 const puppeteer = require('puppeteer'); const cheerio = require('cheerio'); async function hybridScrape() { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 使用 Puppeteer 加载动态页面 await page.goto('https://example.com/dynamic'); await page.waitForSelector('.content'); // 获取 HTML const html = await page.content(); await browser.close(); // 使用 Cheerio 快速解析 const $ = cheerio.load(html); const data = $('.item').map((i, el) => ({ title: $(el).find('.title').text(), content: $(el).find('.content').text() })).get(); return data; } ``` ## 6. 实际应用示例 ### Cheerio - 抓取静态博客 ```javascript async function scrapeBlog() { const response = await axios.get('https://blog.example.com'); const $ = cheerio.load(response.data); return $('.post').map((i, el) => ({ title: $(el).find('h2').text(), date: $(el).find('.date').text(), excerpt: $(el).find('.excerpt').text() })).get(); } ``` ### Puppeteer - 抓取动态电商网站 ```javascript async function scrapeShop() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://shop.example.com'); // 滚动加载更多商品 for (let i = 0; i < 5; i++) { await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.waitForTimeout(1000); } const products = await page.$$eval('.product', items => items.map(item => ({ name: item.querySelector('.name').textContent, price: item.querySelector('.price').textContent })) ); await browser.close(); return products; } ``` ## 总结 - **Cheerio**:适合静态页面、高性能需求、批量处理 - **Puppeteer**:适合动态页面、需要交互、可视化需求 - **混合使用**:先用 Puppeteer 加载动态内容,再用 Cheerio 解析,可以获得最佳的性能和功能平衡
服务端 · 2月22日 14:30
如何使用 Cheerio 进行网页爬虫和数据抓取?Cheerio 在网页爬虫和数据抓取方面表现出色,因为它轻量、快速且易于使用。以下是使用 Cheerio 进行网页爬虫的完整指南: ## 1. 基本爬虫架构 ```javascript const cheerio = require('cheerio'); const axios = require('axios'); async function scrapePage(url) { try { // 1. 发送 HTTP 请求获取 HTML const response = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); // 2. 使用 Cheerio 加载 HTML const $ = cheerio.load(response.data); // 3. 提取数据 const data = { title: $('title').text(), description: $('meta[name="description"]').attr('content'), links: [] }; // 4. 提取所有链接 $('a[href]').each((index, element) => { data.links.push({ text: $(element).text().trim(), href: $(element).attr('href') }); }); return data; } catch (error) { console.error('爬取失败:', error.message); throw error; } } ``` ## 2. 抓取新闻网站示例 ```javascript async function scrapeNews() { const url = 'https://example-news.com'; const response = await axios.get(url); const $ = cheerio.load(response.data); const articles = []; $('.news-item').each((index, element) => { const $item = $(element); articles.push({ title: $item.find('.title').text().trim(), link: $item.find('a').attr('href'), summary: $item.find('.summary').text().trim(), date: $item.find('.date').text().trim(), author: $item.find('.author').text().trim() }); }); return articles; } ``` ## 3. 抓取电商产品信息 ```javascript async function scrapeProducts() { const url = 'https://example-shop.com/products'; const response = await axios.get(url); const $ = cheerio.load(response.data); const products = []; $('.product-card').each((index, element) => { const $product = $(element); const priceText = $product.find('.price').text(); const price = parseFloat(priceText.replace(/[^0-9.]/g, '')); products.push({ name: $product.find('.product-name').text().trim(), price: price, originalPrice: parseFloat($product.find('.original-price').text().replace(/[^0-9.]/g, '')) || null, discount: $product.find('.discount').text().trim(), rating: parseFloat($product.find('.rating').attr('data-rating')), reviews: parseInt($product.find('.review-count').text().replace(/[^0-9]/g, '')), image: $product.find('img').attr('src'), link: $product.find('a.product-link').attr('href') }); }); return products; } ``` ## 4. 分页爬取 ```javascript async function scrapeMultiplePages(baseUrl, maxPages) { const allData = []; for (let page = 1; page <= maxPages; page++) { const url = `${baseUrl}?page=${page}`; console.log(`正在爬取第 ${page} 页...`); try { const response = await axios.get(url); const $ = cheerio.load(response.data); $('.item').each((index, element) => { allData.push({ id: $(element).attr('data-id'), title: $(element).find('.title').text().trim(), content: $(element).find('.content').text().trim() }); }); // 延迟避免被封 await new Promise(resolve => setTimeout(resolve, 1000)); } catch (error) { console.error(`第 ${page} 页爬取失败:`, error.message); } } return allData; } ``` ## 5. 处理相对 URL ```javascript const URL = require('url'); function resolveUrl(base, relative) { return URL.resolve(base, relative); } // 使用示例 const baseUrl = 'https://example.com'; const relativeLink = '/article/123'; const absoluteUrl = resolveUrl(baseUrl, relativeLink); // 结果: https://example.com/article/123 ``` ## 6. 数据清洗和验证 ```javascript function cleanData(rawData) { return rawData.map(item => ({ title: item.title.replace(/\s+/g, ' ').trim(), price: parseFloat(item.price) || 0, description: item.description .replace(/<[^>]*>/g, '') // 移除 HTML 标签 .replace(/\s+/g, ' ') // 合并空格 .trim(), date: new Date(item.date), isValid: item.title.length > 0 && item.price > 0 })).filter(item => item.isValid); } ``` ## 7. 错误处理和重试机制 ```javascript async function fetchWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await axios.get(url, { timeout: 10000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); return response.data; } catch (error) { console.log(`尝试 ${i + 1}/${maxRetries} 失败:`, error.message); if (i === maxRetries - 1) { throw error; } // 指数退避 await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000)); } } } ``` ## 8. 保存数据到文件 ```javascript const fs = require('fs'); function saveToFile(data, filename) { const jsonData = JSON.stringify(data, null, 2); fs.writeFileSync(filename, jsonData, 'utf8'); console.log(`数据已保存到 ${filename}`); } // 保存为 CSV function saveToCSV(data, filename) { if (data.length === 0) return; const headers = Object.keys(data[0]).join(','); const rows = data.map(item => Object.values(item).map(value => `"${String(value).replace(/"/g, '""')}"` ).join(',') ); const csv = [headers, ...rows].join('\n'); fs.writeFileSync(filename, csv, 'utf8'); console.log(`CSV 已保存到 ${filename}`); } ``` ## 9. 完整爬虫示例 ```javascript const cheerio = require('cheerio'); const axios = require('axios'); const fs = require('fs'); class WebScraper { constructor(baseUrl) { this.baseUrl = baseUrl; this.data = []; } async scrape(maxPages = 5) { for (let page = 1; page <= maxPages; page++) { await this.scrapePage(page); await this.delay(1000); } this.saveData(); return this.data; } async scrapePage(page) { const url = `${this.baseUrl}?page=${page}`; console.log(`正在爬取: ${url}`); try { const html = await fetchWithRetry(url); const $ = cheerio.load(html); $('.article').each((index, element) => { this.data.push(this.extractData($, element)); }); console.log(`第 ${page} 页完成,共 ${this.data.length} 条数据`); } catch (error) { console.error(`第 ${page} 页爬取失败:`, error.message); } } extractData($, element) { const $el = $(element); return { title: $el.find('.title').text().trim(), author: $el.find('.author').text().trim(), date: $el.find('.date').text().trim(), content: $el.find('.content').text().trim(), link: $el.find('a').attr('href'), tags: $el.find('.tag').map((i, tag) => $(tag).text()).get() }; } saveData() { const filename = `scraped_data_${Date.now()}.json`; fs.writeFileSync(filename, JSON.stringify(this.data, null, 2)); console.log(`数据已保存到 ${filename}`); } delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } // 使用示例 async function main() { const scraper = new WebScraper('https://example-blog.com/articles'); const data = await scraper.scrape(10); console.log(`爬取完成,共 ${data.length} 条数据`); } main().catch(console.error); ``` ## 最佳实践 1. **设置合理的延迟**:避免频繁请求导致被封 2. **使用 User-Agent**:模拟真实浏览器请求 3. **处理异常**:完善的错误处理和重试机制 4. **数据验证**:清洗和验证提取的数据 5. **遵守 robots.txt**:尊重网站的爬虫规则 6. **增量更新**:只抓取新增或变化的数据 7. **并发控制**:使用队列控制并发请求数量
服务端 · 2月22日 14:30
Cheerio 使用中的常见问题有哪些?如何解决这些问题?Cheerio 提供了丰富的 API,但在实际使用中,开发者经常会遇到一些常见问题。以下是 Cheerio 使用中的常见问题及其解决方案: ## 1. 中文乱码问题 ### 问题描述 当抓取包含中文的网页时,出现乱码。 ### 解决方案 ```javascript const axios = require('axios'); const cheerio = require('cheerio'); const iconv = require('iconv-lite'); async function scrapeWithEncoding(url) { // 方案1:设置响应类型为 arraybuffer const response = await axios.get(url, { responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); // 方案2:检测编码并转换 let html = response.data; // 检测 Content-Type 中的编码 const contentType = response.headers['content-type'] || ''; const charsetMatch = contentType.match(/charset=([^;]+)/i); if (charsetMatch) { const charset = charsetMatch[1].toLowerCase(); if (charset !== 'utf-8') { html = iconv.decode(Buffer.from(html), charset); } } // 方案3:从 HTML meta 标签获取编码 const $temp = cheerio.load(html); const metaCharset = $temp('meta[charset]').attr('charset'); if (metaCharset && metaCharset.toLowerCase() !== 'utf-8') { html = iconv.decode(Buffer.from(html), metaCharset); } const $ = cheerio.load(html); return $('title').text(); } ``` ## 2. 选择器找不到元素 ### 问题描述 使用选择器查询时返回空结果,但元素确实存在。 ### 解决方案 ```javascript const cheerio = require('cheerio'); const html = ` <div class="container"> <p class="text">Hello</p> </div> `; const $ = cheerio.load(html); // 问题:选择器错误 console.log($('.container p.text').length); // 1 // 解决方案1:检查选择器语法 console.log($('.container > p.text').length); // 1 // 解决方案2:使用更宽松的选择器 console.log($('.container .text').length); // 1 // 解决方案3:逐步调试 console.log($('.container').length); // 1 console.log($('.container p').length); // 1 console.log($('.container p').hasClass('text')); // true // 解决方案4:使用 contains() 查找包含文本的元素 console.log($('p:contains("Hello")').length); // 1 // 解决方案5:检查 HTML 是否正确加载 console.log($.html()); // 查看完整的 HTML ``` ## 3. 动态内容无法获取 ### 问题描述 页面中通过 JavaScript 动态加载的内容无法获取。 ### 解决方案 ```javascript const cheerio = require('cheerio'); const axios = require('axios'); // 问题:直接使用 Cheerio 无法获取动态内容 async function scrapeStatic() { const response = await axios.get('https://example.com/dynamic'); const $ = cheerio.load(response.data); console.log($('.dynamic-content').text()); // 空 } // 解决方案:结合 Puppeteer const puppeteer = require('puppeteer'); async function scrapeDynamic() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com/dynamic'); // 等待动态内容加载 await page.waitForSelector('.dynamic-content'); const html = await page.content(); await browser.close(); const $ = cheerio.load(html); console.log($('.dynamic-content').text()); // 有内容 } // 解决方案:直接调用 API async function scrapeAPI() { const response = await axios.get('https://example.com/api/data'); const data = response.data; console.log(data); // 直接获取 JSON 数据 } ``` ## 4. 内存占用过高 ### 问题描述 处理大量 HTML 时内存占用过高,导致程序崩溃。 ### 解决方案 ```javascript const cheerio = require('cheerio'); // 问题:一次性处理大文件 function processLargeFileBad(html) { const $ = cheerio.load(html); const results = []; // 处理数百万个元素 $('.item').each((i, el) => { results.push({ title: $(el).find('.title').text(), content: $(el).find('.content').text() }); }); return results; } // 解决方案1:分批处理 function processLargeFileGood(html) { const $ = cheerio.load(html); const batchSize = 1000; const total = $('.item').length; const results = []; for (let i = 0; i < total; i += batchSize) { const $batch = $('.item').slice(i, i + batchSize); const batchData = $batch.map((j, el) => ({ title: $(el).find('.title').text(), content: $(el).find('.content').text() })).get(); results.push(...batchData); // 及时清理 $batch = null; // 强制垃圾回收(开发环境) if (global.gc) { global.gc(); } } return results; } // 解决方案2:使用流式处理 const fs = require('fs'); const { Transform } = require('stream'); function processWithStream(filePath) { return new Promise((resolve, reject) => { const results = []; let buffer = ''; const transformStream = new Transform({ transform(chunk, encoding, callback) { buffer += chunk.toString(); // 按标签分割处理 const items = buffer.match(/<item[^>]*>[\s\S]*?<\/item>/g) || []; items.forEach(item => { const $ = cheerio.load(item); results.push({ title: $('.title').text(), content: $('.content').text() }); }); // 清理已处理的内容 const lastIndex = buffer.lastIndexOf('</item>'); if (lastIndex !== -1) { buffer = buffer.slice(lastIndex + 7); } callback(); }, flush(callback) { resolve(results); callback(); } }); fs.createReadStream(filePath) .pipe(transformStream) .on('error', reject); }); } ``` ## 5. 相对路径处理问题 ### 问题描述 提取的链接是相对路径,无法直接访问。 ### 解决方案 ```javascript const cheerio = require('cheerio'); const { URL } = require('url'); function resolveLinks(html, baseUrl) { const $ = cheerio.load(html); const links = []; $('a[href]').each((i, el) => { const href = $(el).attr('href'); const absoluteUrl = new URL(href, baseUrl).href; links.push({ text: $(el).text().trim(), href: href, absoluteUrl: absoluteUrl }); }); return links; } // 使用示例 const html = ` <a href="/page1">Page 1</a> <a href="../page2">Page 2</a> <a href="https://example.com/page3">Page 3</a> `; const links = resolveLinks(html, 'https://example.com/dir/index.html'); console.log(links); ``` ## 6. 表单数据提取问题 ### 问题描述 提取表单数据时遇到复选框、多选框等复杂情况。 ### 解决方案 ```javascript const cheerio = require('cheerio'); function extractFormData(html) { const $ = cheerio.load(html); const formData = {}; // 文本输入 $('input[type="text"]').each((i, el) => { const name = $(el).attr('name'); const value = $(el).val() || ''; formData[name] = value; }); // 复选框(多选) $('input[type="checkbox"]:checked').each((i, el) => { const name = $(el).attr('name'); const value = $(el).val(); if (!formData[name]) { formData[name] = []; } formData[name].push(value); }); // 单选框 $('input[type="radio"]:checked').each((i, el) => { const name = $(el).attr('name'); const value = $(el).val(); formData[name] = value; }); // 下拉选择 $('select').each((i, el) => { const name = $(el).attr('name'); const selectedOption = $(el).find('option:selected'); formData[name] = selectedOption.val(); }); // 多选下拉 $('select[multiple]').each((i, el) => { const name = $(el).attr('name'); const selectedOptions = $(el).find('option:selected'); formData[name] = selectedOptions.map((j, opt) => $(opt).val()).get(); }); // 文本域 $('textarea').each((i, el) => { const name = $(el).attr('name'); const value = $(el).val() || ''; formData[name] = value; }); return formData; } ``` ## 7. HTML 实体编码问题 ### 问题描述 HTML 中的特殊字符被编码,如 `&nbsp;`、`&amp;` 等。 ### 解决方案 ```javascript const cheerio = require('cheerio'); const html = '<div>Hello &amp; World &nbsp; Test</div>'; // 问题:默认会解码实体 const $ = cheerio.load(html); console.log($('.div').text()); // "Hello & World Test" // 解决方案1:禁用实体解码 const $2 = cheerio.load(html, { decodeEntities: false }); console.log($2('.div').text()); // "Hello &amp; World &nbsp; Test" // 解决方案2:手动处理实体 const he = require('he'); const text = he.decode($('.div').text()); console.log(text); // "Hello & World Test" // 解决方案3:使用 html() 方法获取原始 HTML const rawHtml = $('.div').html(); console.log(rawHtml); // "Hello &amp; World &nbsp; Test" ``` ## 8. 性能问题 ### 问题描述 处理大量数据时性能不佳。 ### 解决方案 ```javascript const cheerio = require('cheerio'); // 问题:使用复杂选择器 function slowQuery($) { return $('div div div p span a').text(); } // 解决方案1:使用更具体的选择器 function fastQuery1($) { return $('.container .link').text(); } // 解决方案2:使用 find() 方法 function fastQuery2($) { return $('.container').find('.link').text(); } // 解决方案3:缓存选择器结果 function fastQuery3($) { const $container = $('.container'); return $container.find('.link').text(); } // 解决方案4:使用原生方法 function fastestQuery($) { const container = $('.container')[0]; return container.querySelector('.link').textContent; } ``` ## 9. 空白字符处理 ### 问题描述 提取的文本包含大量空白字符。 ### 解决方案 ```javascript const cheerio = require('cheerio'); const html = ` <div> <p> Hello World </p> </div> `; const $ = cheerio.load(html); // 问题:包含大量空白 console.log($('p').text()); // "\n Hello\n World\n " // 解决方案1:使用 trim() console.log($('p').text().trim()); // "Hello\n World" // 解决方案2:使用正则替换 console.log($('p').text().replace(/\s+/g, ' ').trim()); // "Hello World" // 解决方案3:使用 normalizeWhitespace 选项 const $2 = cheerio.load(html, { normalizeWhitespace: true }); console.log($2('p').text()); // "Hello World" // 解决方案4:自定义清理函数 function cleanText(text) { return text .replace(/[\r\n\t]+/g, ' ') // 替换换行和制表符 .replace(/\s+/g, ' ') // 合并多个空格 .trim(); // 去除首尾空格 } console.log(cleanText($('p').text())); // "Hello World" ``` ## 10. XML 解析问题 ### 问题描述 解析 XML 文档时出现问题。 ### 解决方案 ```javascript const cheerio = require('cheerio'); const xml = ` <root> <item id="1"> <name>Item 1</name> </item> <item id="2"> <name>Item 2</name> </item> </root> `; // 解决方案:使用 XML 模式 const $ = cheerio.load(xml, { xmlMode: true, decodeEntities: false }); // 提取数据 const items = []; $('item').each((i, el) => { items.push({ id: $(el).attr('id'), name: $(el).find('name').text() }); }); console.log(items); ``` 通过掌握这些常见问题的解决方案,可以更有效地使用 Cheerio 进行 HTML/XML 解析和数据提取。
服务端 · 2月22日 14:30
Cheerio 的 DOM 操作方法有哪些?如何使用这些方法?Cheerio 提供了丰富的 DOM 操作方法,与 jQuery 的 API 高度兼容。以下是常用的 DOM 操作方法: ## 1. 获取和设置内容 ```javascript // 获取文本内容 $('p').text(); // 获取第一个匹配元素的文本 $('p').text('New text'); // 设置所有匹配元素的文本 // 获取 HTML 内容 $('.container').html(); // 获取第一个匹配元素的 HTML $('.container').html('<p>New</p>'); // 设置所有匹配元素的 HTML // 获取表单值 $('input').val(); // 获取输入值 $('input').val('new value'); // 设置输入值 // 获取属性值 $('a').attr('href'); // 获取 href 属性 $('a').attr('href', 'new-url'); // 设置 href 属性 $('a').attr({ // 设置多个属性 href: 'new-url', target: '_blank' }); $('a').removeAttr('target'); // 移除属性 // 获取数据属性 $('div').data('id'); // 获取 data-id $('div').data('id', '123'); // 设置 data-id ``` ## 2. CSS 类操作 ```javascript // 添加类 $('div').addClass('active'); $('div').addClass('active highlight'); // 移除类 $('div').removeClass('active'); $('div').removeClass('active highlight'); // 切换类 $('div').toggleClass('active'); // 检查类 $('div').hasClass('active'); // 返回 true/false // 获取类名 $('div').attr('class'); // 获取所有类名 ``` ## 3. CSS 样式操作 ```javascript // 获取样式 $('div').css('color'); // 获取 color 样式 // 设置样式 $('div').css('color', 'red'); $('div').css({ // 设置多个样式 color: 'red', fontSize: '14px', backgroundColor: '#f0f0f0' }); ``` ## 4. DOM 遍历 ```javascript // 查找子元素 $('div').find('p'); // 查找所有后代 p $('div').children(); // 获取直接子元素 $('div').children('p'); // 获取直接子元素 p // 父元素 $('p').parent(); // 获取直接父元素 $('p').parents(); // 获取所有祖先元素 $('p').parents('.container'); // 获取匹配的祖先元素 $('p').closest('.container'); // 获取最近的匹配祖先 // 兄弟元素 $('p').next(); // 下一个兄弟元素 $('p').prev(); // 上一个兄弟元素 $('p').nextAll(); // 之后的所有兄弟元素 $('p').prevAll(); // 之前的所有兄弟元素 $('p').siblings(); // 所有兄弟元素 $('p').siblings('.active'); // 匹配的兄弟元素 ``` ## 5. DOM 插入 ```javascript // 内部插入 $('div').append('<p>New paragraph</p>'); // 在元素末尾插入 $('<p>New</p>').appendTo('div'); // 插入到元素末尾 $('div').prepend('<p>New paragraph</p>'); // 在元素开头插入 $('<p>New</p>').prependTo('div'); // 插入到元素开头 // 外部插入 $('div').after('<p>New paragraph</p>'); // 在元素之后插入 $('<p>New</p>').insertAfter('div'); // 插入到元素之后 $('div').before('<p>New paragraph</p>'); // 在元素之前插入 $('<p>New</p>').insertBefore('div'); // 插入到元素之前 // 替换 $('p').replaceWith('<div>New</div>'); // 替换元素 $('<div>New</div>').replaceAll('p'); // 替换所有匹配元素 ``` ## 6. DOM 删除 ```javascript // 删除元素 $('p').remove(); // 删除匹配的元素及其子元素 $('p').empty(); // 清空元素内容,保留元素本身 // 分离元素 const $detached = $('p').detach(); // 删除元素但保留数据和事件 ``` ## 7. DOM 复制 ```javascript // 克隆元素 const $clone = $('div').clone(); // 克隆元素 const $cloneWithEvents = $('div').clone(true); // 克隆元素并复制事件 ``` ## 8. 元素过滤 ```javascript // 过滤元素 $('li').filter('.active'); // 保留匹配的元素 $('li').not('.active'); // 移除匹配的元素 $('li').first(); // 获取第一个元素 $('li').last(); // 获取最后一个元素 $('li').eq(2); // 获取索引为 2 的元素 $('li').slice(1, 4); // 获取索引 1-3 的元素 // 查找元素 $('div').has('p'); // 包含 p 的 div $('div').is('.active'); // 检查是否匹配 ``` ## 9. 集合操作 ```javascript // 获取元素数量 $('li').length; // 或 $('li').size() // 获取索引 $('li').index(); // 当前元素在集合中的索引 $('li').index($('.active')); // 指定元素的索引 // 转换为数组 const texts = $('li').map(function() { return $(this).text(); }).get(); // 转换为普通数组 // 遍历元素 $('li').each(function(i, elem) { console.log(i, $(this).text()); }); // 获取 DOM 元素 const elem = $('div')[0]; // 获取第一个原生 DOM 元素 const elem = $('div').get(0); // 同上 const elems = $('div').get(); // 获取所有原生 DOM 元素 ``` ## 10. 实用示例 ```javascript // 提取文章标题和链接 const articles = []; $('.article').each(function() { articles.push({ title: $(this).find('h2').text(), link: $(this).find('a').attr('href'), summary: $(this).find('p').text().trim() }); }); // 修改表格数据 $('table tr').each(function() { const $row = $(this); const price = parseFloat($row.find('.price').text()); if (price > 100) { $row.addClass('highlight'); } }); // 生成新的 HTML const $container = cheerio.load('<div class="container"></div>'); data.items.forEach(item => { $container('.container').append(` <div class="item"> <h3>${item.title}</h3> <p>${item.description}</p> </div> `); }); ```
服务端 · 2月22日 14:30
Cheerio 如何加载 HTML 内容?有哪些加载方式?Cheerio 提供了多种加载 HTML 内容的方法,适用于不同的使用场景: ## 1. 从 HTML 字符串加载 最常用的方法,直接传入 HTML 字符串: ```javascript const cheerio = require('cheerio'); const $ = cheerio.load('<div class="content"><p>Hello</p></div>'); ``` ## 2. 从文件加载 读取 HTML 文件后加载: ```javascript const fs = require('fs'); const cheerio = require('cheerio'); const html = fs.readFileSync('index.html', 'utf8'); const $ = cheerio.load(html); ``` ## 3. 使用配置选项加载 `cheerio.load()` 方法接受第二个参数作为配置选项: ```javascript const $ = cheerio.load(html, { // 是否识别 XML 模式 xmlMode: false, // 是否解码 HTML 实体 decodeEntities: true, // 是否包含空白节点 withDomLvl1: false, // 自�认函数处理 XML 标签 normalizeWhitespace: false, // 使用 htmlparser2 的选项 xml: { xmlMode: false, decodeEntities: true } }); ``` ## 4. XML 模式加载 处理 XML 文档时启用 XML 模式: ```javascript const xml = '<root><item>Value</item></root>'; const $ = cheerio.load(xml, { xmlMode: true }); ``` ## 5. 流式处理(结合其他库) 对于大文件,可以使用流式读取: ```javascript const fs = require('fs'); const cheerio = require('cheerio'); const stream = fs.createReadStream('large.html'); let html = ''; stream.on('data', chunk => { html += chunk; }); stream.on('end', () => { const $ = cheerio.load(html); // 处理 DOM }); ``` ## 最佳实践 - 对于小到中等大小的 HTML,直接使用 `cheerio.load()` - 对于大文件,考虑分块处理或使用专门的流式解析器 - XML 文档务必设置 `xmlMode: true` - 根据需求调整 `decodeEntities` 选项
服务端 · 2月22日 14:30
Cheerio 中常用的选择器有哪些?如何高效使用选择器?Cheerio 支持几乎所有 jQuery 的选择器语法,这使得熟悉 jQuery 的开发者可以快速上手。以下是 Cheerio 中常用的选择器类型: ## 1. 基本选择器 ```javascript // 元素选择器 $('div') $('p') $('a') // ID 选择器 $('#header') $('#main-content') // 类选择器 $('.container') $('.active') // 多重选择器 $('div, p, a') $('.class1, .class2') ``` ## 2. 层级选择器 ```javascript // 后代选择器 $('div p') // div 内的所有 p 元素 $('.container a') // .container 内的所有 a 元素 // 子元素选择器 $('ul > li') // ul 的直接子元素 li // 相邻兄弟选择器 $('h2 + p') // 紧跟在 h2 后的 p 元素 // 通用兄弟选择器 $('h2 ~ p') // h2 之后的所有兄弟 p 元素 ``` ## 3. 属性选择器 ```javascript // 存在属性 $('[href]') $('[data-id]') // 属性值匹配 $('[class="active"]') $('[href="https://example.com"]') // 属性值包含 $('[class*="btn"]') // class 包含 "btn" $('[href*="/api/"]') // 属性值开头 $('[class^="col-"]') // class 以 "col-" 开头 $('[href^="https://"]') // 属性值结尾 $('[src$=".jpg"]') // src 以 ".jpg" 结尾 $('[href$=".html"]') // 多属性选择器 $('a[href^="http"][target="_blank"]') ``` ## 4. 伪类选择器 ```javascript // 位置伪类 $('li:first') // 第一个 li $('li:last') // 最后一个 li $('li:even') // 偶数位置的 li $('li:odd') // 奇数位置的 li $('li:eq(2)') // 第三个 li(从 0 开始) $('li:gt(2)') // 索引大于 2 的 li $('li:lt(5)') // 索引小于 5 的 li // 内容伪类 $('p:contains("Hello")') // 包含 "Hello" 文本的 p $('p:empty') // 空的 p 元素 $('div:has(p)') // 包含 p 的 div // 表单伪类 $('input:checked') // 选中的 checkbox/radio $('input:disabled') // 禁用的 input $('input:enabled') // 启用的 input $('input:focus') // 获得焦点的 input $('option:selected') // 选中的 option ``` ## 5. 表单选择器 ```javascript $(':text') // 文本输入框 $(':password') // 密码输入框 $(':radio') // 单选按钮 $(':checkbox') // 复选框 $(':submit') // 提交按钮 $(':reset') // 重置按钮 $(':button') // 按钮 $(':file') // 文件上传 $(':image') // 图片按钮 ``` ## 6. 组合选择器示例 ```javascript // 复杂选择器组合 $('.container > .row:first-child .col-md-4 a[href^="http"]') // 多条件筛选 $('div.active[data-type="article"]:not(.hidden)') // 嵌套选择 $('.content').find('p').filter('.highlight') ``` ## 性能优化建议 1. **使用具体的选择器**:`$('#content p')` 比 `$('p')` 更快 2. **避免过度使用通配符**:`$('*')` 性能较差 3. **缓存选择结果**:重复使用时保存到变量 4. **使用 find() 代替层级选择器**:在某些情况下性能更好 5. **限制搜索范围**:先选择父元素再查找子元素 ```javascript // 好的做法 const $content = $('#content'); const $paragraphs = $content.find('p'); // 避免 $('p').each(function() { if ($(this).parents('#content').length) { // 处理 } }); ```
服务端 · 2月22日 14:30
如何开发和使用 Cheerio 插件?有哪些实用的插件示例?Cheerio 支持插件系统,可以通过插件扩展其功能。以下是 Cheerio 插件开发的完整指南: ## 1. Cheerio 插件基础 ### 插件结构 Cheerio 插件本质上是一个函数,它接收 Cheerio 实例作为参数,并扩展其原型: ```javascript // 基本插件结构 module.exports = function(cheerio) { // 扩展 Cheerio 原型 cheerio.prototype.myMethod = function(selector) { // 插件逻辑 return this; }; }; ``` ### 简单插件示例 ```javascript // my-plugin.js module.exports = function(cheerio) { // 添加一个获取所有文本内容的方法 cheerio.prototype.getAllText = function() { return this.map((i, el) => cheerio(el).text()).get(); }; // 添加一个过滤空元素的方法 cheerio.prototype.filterEmpty = function() { return this.filter((i, el) => { return cheerio(el).text().trim().length > 0; }); }; }; ``` ## 2. 使用插件 ### 安装和加载插件 ```javascript const cheerio = require('cheerio'); const myPlugin = require('./my-plugin'); // 加载插件 cheerio.use(myPlugin); // 使用插件提供的方法 const $ = cheerio.load('<div><p>Hello</p><p></p></div>'); console.log($('p').getAllText()); // ['Hello'] console.log($('p').filterEmpty().length); // 1 ``` ### 链式调用 ```javascript // 插件方法支持链式调用 const result = $('p') .filterEmpty() .getAllText() .map(text => text.toUpperCase()); ``` ## 3. 实用插件示例 ### 1. 文本清理插件 ```javascript // text-cleaner.js module.exports = function(cheerio) { // 清理文本中的多余空白 cheerio.prototype.cleanText = function() { return this.each((i, el) => { const $el = cheerio(el); const text = $el.text() .replace(/\s+/g, ' ') .trim(); $el.text(text); }); }; // 移除指定标签 cheerio.prototype.removeTags = function(tags) { const tagArray = Array.isArray(tags) ? tags : [tags]; return this.each((i, el) => { const $el = cheerio(el); tagArray.forEach(tag => { $el.find(tag).remove(); }); }); }; }; ``` ### 2. 数据提取插件 ```javascript // data-extractor.js module.exports = function(cheerio) { // 提取表格数据为二维数组 cheerio.prototype.tableToArray = function() { const result = []; this.find('tr').each((i, row) => { const rowData = []; cheerio(row).find('td, th').each((j, cell) => { rowData.push(cheerio(cell).text().trim()); }); result.push(rowData); }); return result; }; // 提取表格数据为对象数组 cheerio.prototype.tableToObjects = function() { const $ = cheerio(this); const headers = []; const result = []; // 提取表头 $.find('thead th, tr:first-child td').each((i, th) => { headers.push(cheerio(th).text().trim()); }); // 提取数据行 $find('tbody tr, tr:not(:first-child)').each((i, row) => { const obj = {}; cheerio(row).find('td').each((j, td) => { const key = headers[j] || `col_${j}`; obj[key] = cheerio(td).text().trim(); }); result.push(obj); }); return result; }; }; ``` ### 3. URL 处理插件 ```javascript // url-handler.js const { URL } = require('url'); module.exports = function(cheerio) { // 将相对 URL 转换为绝对 URL cheerio.prototype.resolveUrls = function(baseUrl) { return this.each((i, el) => { const $el = cheerio(el); const href = $el.attr('href'); const src = $el.attr('src'); if (href) { $el.attr('href', new URL(href, baseUrl).href); } if (src) { $el.attr('src', new URL(src, baseUrl).href); } }); }; // 提取所有链接 cheerio.prototype.extractLinks = function() { const links = []; this.find('a[href]').each((i, el) => { const $el = cheerio(el); links.push({ text: $el.text().trim(), href: $el.attr('href'), title: $el.attr('title') }); }); return links; }; }; ``` ### 4. 图片处理插件 ```javascript // image-handler.js module.exports = function(cheerio) { // 提取所有图片信息 cheerio.prototype.extractImages = function() { const images = []; this.find('img').each((i, el) => { const $el = cheerio(el); images.push({ src: $el.attr('src'), alt: $el.attr('alt'), title: $el.attr('title'), width: $el.attr('width'), height: $el.attr('height') }); }); return images; }; // 懒加载图片处理 cheerio.prototype.handleLazyImages = function() { return this.each((i, el) => { const $el = cheerio(el); const dataSrc = $el.attr('data-src'); if (dataSrc) { $el.attr('src', dataSrc); } }); }; }; ``` ## 4. 高级插件开发 ### 带配置的插件 ```javascript // configurable-plugin.js module.exports = function(cheerio, options = {}) { const defaultOptions = { trim: true, removeEmpty: true, maxLength: 1000 }; const opts = { ...defaultOptions, ...options }; cheerio.prototype.smartExtract = function() { return this.map((i, el) => { let text = cheerio(el).text(); if (opts.trim) { text = text.trim(); } if (opts.removeEmpty && text.length === 0) { return null; } if (opts.maxLength && text.length > opts.maxLength) { text = text.substring(0, opts.maxLength) + '...'; } return text; }).filter(text => text !== null); }; }; // 使用 cheerio.use(configurablePlugin, { trim: true, removeEmpty: true, maxLength: 500 }); ``` ### 异步插件 ```javascript // async-plugin.js module.exports = function(cheerio) { cheerio.prototype.fetchContent = async function(url) { const axios = require('axios'); const response = await axios.get(url); return cheerio.load(response.data); }; cheerio.prototype.batchProcess = async function(processor) { const results = []; for (let i = 0; i < this.length; i++) { const result = await processor(cheerio(this[i])); results.push(result); } return results; }; }; ``` ## 5. 插件发布 ### package.json 配置 ```json { "name": "cheerio-my-plugin", "version": "1.0.0", "description": "My Cheerio plugin", "main": "index.js", "keywords": [ "cheerio", "plugin", "html", "parser" ], "peerDependencies": { "cheerio": ">=1.0.0" }, "repository": { "type": "git", "url": "https://github.com/username/cheerio-my-plugin" }, "license": "MIT" } ``` ### 插件测试 ```javascript // test.js const cheerio = require('cheerio'); const myPlugin = require('./my-plugin'); describe('My Plugin', () => { beforeEach(() => { cheerio.use(myPlugin); }); test('should filter empty elements', () => { const $ = cheerio.load('<div><p>Hello</p><p></p></div>'); const result = $('p').filterEmpty(); expect(result.length).toBe(1); expect(result.text()).toBe('Hello'); }); test('should get all text', () => { const $ = cheerio.load('<div><p>Hello</p><p>World</p></div>'); const result = $('p').getAllText(); expect(result).toEqual(['Hello', 'World']); }); }); ``` ## 6. 现有流行插件 ### cheerio-tableparser ```javascript const cheerio = require('cheerio'); const tableParser = require('cheerio-tableparser'); cheerio.use(tableParser); const $ = cheerio.load(html); const tableData = $('table').parsetable(); ``` ### cheerio-select ```javascript const cheerio = require('cheerio'); const select = require('cheerio-select'); cheerio.use(select); const $ = cheerio.load(html); const elements = $.select('div > p:first-child'); ``` ## 7. 最佳实践 ### 1. 命名规范 ```javascript // ✅ 好的命名 cheerio.prototype.extractLinks = function() {} cheerio.prototype.cleanText = function() {} // ❌ 不好的命名 cheerio.prototype.doSomething = function() {} cheerio.prototype.method1 = function() {} ``` ### 2. 返回值处理 ```javascript // ✅ 支持链式调用 cheerio.prototype.myMethod = function() { // 处理逻辑 return this; }; // ✅ 返回新集合 cheerio.prototype.myFilter = function() { const filtered = this.filter(/* 条件 */); return filtered; }; ``` ### 3. 错误处理 ```javascript cheerio.prototype.safeExtract = function() { try { // 提取逻辑 return this.map((i, el) => { return cheerio(el).text(); }).get(); } catch (error) { console.error('Extraction failed:', error); return []; } }; ``` ### 4. 文档和注释 ```javascript /** * 提取所有链接信息 * @param {Object} options - 配置选项 * @param {boolean} options.resolveAbsolute - 是否转换为绝对 URL * @param {string} options.baseUrl - 基础 URL * @returns {Array} 链接信息数组 */ cheerio.prototype.extractLinks = function(options = {}) { // 实现 }; ``` 通过开发和使用 Cheerio 插件,可以大大扩展 Cheerio 的功能,提高开发效率。
服务端 · 2月22日 14:30
Cheerio 如何处理动态加载的内容?有哪些解决方案?Cheerio 本身不支持处理动态加载的内容,因为它只是一个 HTML 解析器,不会执行 JavaScript。但是,我们可以通过多种方式结合其他工具来处理动态内容: ## 1. 使用 Puppeteer + Cheerio 组合 这是最常用的方案,先用 Puppeteer 加载动态页面,然后用 Cheerio 解析: ```javascript const puppeteer = require('puppeteer'); const cheerio = require('cheerio'); async function scrapeDynamicContent(url) { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); // 访问页面并等待动态内容加载 await page.goto(url, { waitUntil: 'networkidle2' }); // 等待特定元素出现 await page.waitForSelector('.dynamic-content', { timeout: 10000 }); // 获取完整的 HTML const html = await page.content(); // 关闭浏览器 await browser.close(); // 使用 Cheerio 解析 const $ = cheerio.load(html); // 提取数据 const data = []; $('.dynamic-item').each((index, element) => { data.push({ title: $(element).find('.title').text().trim(), content: $(element).find('.content').text().trim(), link: $(element).find('a').attr('href') }); }); return data; } ``` ## 2. 处理无限滚动页面 ```javascript async function scrapeInfiniteScroll(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); let previousHeight = 0; let currentHeight = await page.evaluate('document.body.scrollHeight'); // 滚动直到没有新内容加载 while (currentHeight > previousHeight) { previousHeight = currentHeight; // 滚动到底部 await page.evaluate('window.scrollTo(0, document.body.scrollHeight)'); // 等待新内容加载 await page.waitForTimeout(2000); currentHeight = await page.evaluate('document.body.scrollHeight'); } // 获取 HTML 并用 Cheerio 解析 const html = await page.content(); const $ = cheerio.load(html); const items = $('.item').map((i, el) => ({ title: $(el).find('.title').text(), link: $(el).find('a').attr('href') })).get(); await browser.close(); return items; } ``` ## 3. 处理懒加载图片 ```javascript async function scrapeLazyImages(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); // 滚动页面触发懒加载 const scrollHeight = await page.evaluate('document.body.scrollHeight'); for (let i = 0; i < scrollHeight; i += 500) { await page.evaluate(`window.scrollTo(0, ${i})`); await page.waitForTimeout(500); } // 等待所有图片加载完成 await page.evaluate(async () => { const images = Array.from(document.querySelectorAll('img[data-src]')); await Promise.all(images.map(img => { if (img.dataset.src) { img.src = img.dataset.src; } })); }); await page.waitForTimeout(2000); const html = await page.content(); const $ = cheerio.load(html); const images = $('img').map((i, el) => ({ src: $(el).attr('src'), alt: $(el).attr('alt') })).get(); await browser.close(); return images; } ``` ## 4. 处理点击加载更多 ```javascript async function scrapeClickToLoad(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); let hasMore = true; const allItems = []; while (hasMore) { // 等待内容加载 await page.waitForSelector('.item'); // 获取当前页面的 HTML const html = await page.content(); const $ = cheerio.load(html); // 提取当前页面的数据 $('.item').each((index, element) => { const title = $(element).find('.title').text(); // 避免重复添加 if (!allItems.some(item => item.title === title)) { allItems.push({ title }); } }); // 检查是否有"加载更多"按钮 const loadMoreButton = await page.$('.load-more'); if (loadMoreButton) { // 点击加载更多 await loadMoreButton.click(); // 等待新内容加载 await page.waitForTimeout(2000); } else { hasMore = false; } } await browser.close(); return allItems; } ``` ## 5. 处理 AJAX 请求 ```javascript async function scrapeAJAXContent(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 监听网络请求 const apiData = []; page.on('response', async response => { const url = response.url(); if (url.includes('/api/data')) { const data = await response.json(); apiData.push(...data); } }); await page.goto(url); // 等待 AJAX 请求完成 await page.waitForSelector('.data-loaded', { timeout: 15000 }); await browser.close(); return apiData; } ``` ## 6. 使用 Playwright 替代方案 Playwright 是另一个强大的浏览器自动化工具: ```javascript const { chromium } = require('playwright'); const cheerio = require('cheerio'); async function scrapeWithPlaywright(url) { const browser = await chromium.launch(); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle' }); // 等待动态内容 await page.waitForSelector('.dynamic-content'); const html = await page.content(); await browser.close(); const $ = cheerio.load(html); return $('.item').map((i, el) => $(el).text()).get(); } ``` ## 7. 优化性能的技巧 ```javascript // 1. 禁用不必要的资源加载 async function optimizedScrape(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 阻止图片、字体等资源加载 await page.setRequestInterception(true); page.on('request', request => { const resourceType = request.resourceType(); if (['image', 'font', 'stylesheet'].includes(resourceType)) { request.abort(); } else { request.continue(); } }); await page.goto(url); const html = await page.content(); await browser.close(); return cheerio.load(html); } // 2. 复用浏览器实例 class Scraper { constructor() { this.browser = null; } async init() { this.browser = await puppeteer.launch(); } async scrape(url) { const page = await this.browser.newPage(); await page.goto(url); const html = await page.content(); await page.close(); return cheerio.load(html); } async close() { await this.browser.close(); } } // 使用示例 async function main() { const scraper = new Scraper(); await scraper.init(); const urls = ['url1', 'url2', 'url3']; const results = []; for (const url of urls) { const $ = await scraper.scrape(url); results.push($('.title').text()); } await scraper.close(); return results; } ``` ## 8. 错误处理和重试 ```javascript async function scrapeWithRetry(url, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url, { timeout: 30000 }); await page.waitForSelector('.content', { timeout: 10000 }); const html = await page.content(); await browser.close(); return cheerio.load(html); } catch (error) { console.log(`尝试 ${i + 1} 失败:`, error.message); if (i === maxRetries - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 2000)); } } } ``` ## 总结 虽然 Cheerio 本身无法处理动态内容,但通过与 Puppeteer、Playwright 等浏览器自动化工具结合,可以有效地处理各种动态加载场景。关键在于: 1. **等待策略**:使用 `waitForSelector`、`waitForTimeout` 等确保内容加载完成 2. **性能优化**:禁用不必要的资源加载,复用浏览器实例 3. **错误处理**:实现重试机制,处理网络异常 4. **混合使用**:先用浏览器工具加载动态内容,再用 Cheerio 快速解析
服务端 · 2月22日 14:30