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

Cheerio

Cheerio是一个基于Node.js的快速、灵活、功能强大的HTML解析器和DOM操作库,类似于jQuery,但主要用于服务器端的Web应用程序。Cheerio可以像jQuery一样使用CSS选择器、DOM遍历、事件处理等功能,可以方便地从HTML文档中提取数据、修改内容、操纵DOM等。Cheerio的核心代码非常小,只有几百行代码,因此它非常快速、轻量级、易于使用。Cheerio还支持多种插件和扩展,如cheerio-tableparser、cheerio-eq等,可以扩展其功能以满足各种需求。由于Cheerio的性能和易用性,它已经成为Node.js中最受欢迎的HTML解析和DOM操纵库之一,并被广泛用于Web爬虫、数据挖掘、数据抓取等应用程序的开发。
Cheerio
查看更多相关内容
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