Cheerio 文档加载指南:load、loadBuffer、stringStream、decodeStream 与 fromURL 全解析
2026/9/19 22:04:14 网站建设 项目流程

Cheerio 文档加载指南:load、loadBuffer、stringStream、decodeStream 与 fromURL 全解析

【免费下载链接】cheerioThe fast, flexible, and elegant library for parsing and manipulating HTML and XML.项目地址: https://gitcode.com/gh_mirrors/ch/cheerio

在能用 CSS 选择器查询、遍历或修改一个文档之前,Cheerio 必须先把它解析成内部的 DOM 树。本文以 Cheerio 官方文档 Loading Documents 为主体,完整讲解 Cheerio(v1.2.0,仓库根目录 package.json)提供的五种加载方式——loadloadBufferstringStreamdecodeStreamfromURL——包括各自适用场景、编码处理机制、流式解析原理与网络请求细节,并结合仓库源码(src/index.ts、src/load.ts、src/options.ts)与测试用例(src/index.spec.ts)深入底层实现。读完后你将能针对「字符串、字节、流、URL」四种输入形态,以及「编码已知/未知」两种前提,做出正确的加载方法选型,并理解 fragment 模式、编码嗅探、重定向处理等关键行为。

为什么 Cheerio 需要显式的「加载」步骤

如果你从 jQuery 迁移过来,这一步是全新的:jQuery 直接操作页面内置的 DOM,而 Cheerio 没有宿主页面,它需要你把文档内容亲手传入。无论标记来自文件、网络响应还是内存字符串,都要先经加载方法解析,才能得到一个$查询函数。正如官方文档开篇所述,选择哪种方法,取决于「标记从哪里来」以及「你是否知道它的字符编码」。

Cheerio 的加载方法家族(全部定义在 src/index.ts 中)可以按下表快速定位:

方法输入适用场景
loadstring已有一份字符串形式的标记
loadBufferBuffer有原始字节,且编码未知
stringStream已解码的文本流正在流式处理,且编码已知
decodeStream原始字节流正在流式处理,且编码未知
fromURLURL希望 Cheerio 替你抓取页面

其中接受字节的方法(loadBufferdecodeStream)会运行 HTML 编码嗅探算法,从而识别<meta charset>或字节序标记(BOM)。只要无法确定源是 UTF-8,就应优先使用这两种方法。

需要特别注意的是浏览器环境限制:在浏览器构建中只有load可用。loadBufferstringStreamdecodeStreamfromURL都依赖 Node.js API,不会被打进浏览器包——这一点可以在 src/index-browser.mts 中得到印证,它只重新导出了 src/load-parse.ts 的load与类型定义,并未包含 src/index.ts 中的流式与网络加载实现。

load:从字符串加载文档

load接收一个包含文档的字符串,返回一个可用于遍历和操作的$函数:

import * as cheerio from 'cheerio'; const $ = cheerio.load('<h1>Hello, world!</h1>'); console.log($('h1').text()); // Output: Hello, world!

从源码看,load的实际签名是(src/load-parse.ts):

load: ( content: string | AnyNode | AnyNode[] | Buffer, options?: CheerioOptions | null, isDocument?: boolean, ) => CheerioAPI

即除了字符串,它还能接收已经解析好的 DOM 节点(AnyNode或其数组)以及Buffer;第二个参数是 Cheerio 选项,第三个参数isDocument控制是否按「完整文档」解析。底层由 src/load.ts 的getLoad工厂函数驱动:它解析输入得到初始根节点,然后构造一个绑定该文档的查询函数,并把_root_optionsfn等属性挂载到返回的initialize上,使其成为完整的CheerioAPI(接口定义见 src/load.ts)。

fragment 模式:跳过文档外壳

和浏览器一样,load在输入中缺失<html><head><body>时会自动补全。如果只想解析一段片段,把false作为第三个参数传入即可:

const $ = cheerio.load('<ul id="fruits">...</ul>', null, false); $.html(); //=> '<ul id="fruits">...</ul>'

这与 配置指南 中讲解的 fragment 模式一致:当输入只是大页面的一小块(如一行表格<tr>、一个列表项<li>)时,通常应该用 fragment 模式,避免序列化时带上无意义的文档外壳。在源码中,isDocument参数会一路传给解析器——src/load-parse.ts 中getParse返回的解析函数会根据它选择调用parseWithParse5(content, options, isDocument, context)还是parseWithHtmlparser2

底层双解析器机制

load背后实际是两套解析器的统一入口(src/load-parse.ts):

  • parse5:HTML 解析的默认选择,严格遵循 HTML 标准,能产出与浏览器一致的 DOM 树;
  • htmlparser2:XML 解析的默认选择,更快、更省内存、对畸形标记更宽容。

具体用哪套由选项决定:flattenOptions(src/options.ts)在xmlxmlMode为真时把内部标志_useHtmlParser2置为true,解析与渲染(src/load-parse.ts)随即切到 htmlparser2 管线。相关选项定义见 CheerioOptions,包括xmlxmlModebaseURIquirksModepseudos等。

loadBuffer:从字节加载文档

loadBufferload行为一致,但接收Buffer而非字符串。编码由 Cheerio 从字节本身推断,因此它特别适合处理编码不受你控制的文件与网络响应:

import * as cheerio from 'cheerio'; import * as fs from 'node:fs'; // 文件可能是 UTF-8、ISO-8859-1……由 Cheerio 自行判断 const $ = cheerio.loadBuffer(fs.readFileSync('document.html')); console.log($('title').text());

其实现(src/index.ts)先调用encoding-sniffer包的decodeBuffer做字节嗅探与解码,再交给load解析:

export function loadBuffer(buffer: Buffer, options: DecodeStreamOptions = {}): CheerioAPI { const opts = flattenOptions(options); const str = decodeBuffer(buffer, { defaultEncoding: opts?.xmlMode ? 'utf8' : 'windows-1252', ...options.encoding, }); return load(str, opts); }

两个值得注意的实现细节:

  • 默认编码:HTML 模式下默认windows-1252,XML 模式(xmlMode)下默认utf8。也就是说,嗅探失败时回退的编码因解析模式而异;
  • 编码覆盖:可通过options.encoding传入encoding-snifferSnifferOptions覆盖默认行为,相关类型为DecodeStreamOptions(src/index.ts)。

测试用例(src/index.spec.ts)验证了两种典型场景:普通 UTF-8 字节,以及带 UTF-16 LE BOM 的字节(0xff 0xfe前缀),后者能被正确嗅探并解析,证明 BOM 检测确实生效。

stringStream:从已解码文本流加载

stringStream返回一个可写流,数据到达时边接收边解析。它适用于编码已知、可以先解码成文本再喂给 Cheerio 的场景:

import * as cheerio from 'cheerio'; import * as fs from 'node:fs'; const writeStream = cheerio.stringStream({}, (err, $) => { if (err) { // Handle error return; } console.log($('title').text()); }); fs.createReadStream('document.html', { encoding: 'utf8' }).pipe(writeStream);

第一个参数是 Cheerio 的选项,第二个是流结束时的回调,接收错误对象与已加载的CheerioAPI

底层实现_stringStream(src/index.ts)同样存在双管线分支:

  • 默认使用 parse5 的Parse5Stream,并通过finished(stream, ...)等待流结束后用load(stream.document, options)组装查询函数;
  • _useHtmlParser2为真(即启用了xmlMode/xml)时,改用htmlparser2.createDocumentStream,包装成自定义Writable

测试中可以看到该流只接受字符串(src/index.spec.ts):向它write一个Buffer会抛出Parser can work only with string streams.——因为按设计,解码工作由上游完成,Cheerio 在这里只负责解析已解码的文本。

decodeStream:从原始字节流加载

decodeStreamloadBuffer的流式对应物:它接收原始字节,在解析前运行编码嗅探算法。当需要流式处理一个编码未知的文档时,选它:

import * as cheerio from 'cheerio'; import * as fs from 'node:fs'; const writeStream = cheerio.decodeStream({}, (err, $) => { if (err) { // Handle error return; } console.log($('title').text()); }); // 注意:不传 encoding 选项——字节原样透传 fs.createReadStream('document.html').pipe(writeStream);

实现(src/index.ts)清晰展示了它的两层结构:

export function decodeStream( options: DecodeStreamOptions, cb: (err: Error | null | undefined, $: CheerioAPI) => void, ): Writable { const { encoding = {}, ...cheerioOptions } = options; const opts = flattenOptions(cheerioOptions); // XML 模式下默认编码为 UTF-8 encoding.defaultEncoding ??= opts?.xmlMode ? 'utf8' : 'windows-1252'; const decodeStream = new DecodeStream(encoding); const loadStream = _stringStream(opts, cb); decodeStream.pipe(loadStream); return decodeStream; }

DecodeStream来自encoding-sniffer包,负责按字节嗅探编码并解码;解码结果随即pipe给内部的_stringStream完成解析。也就是说,decodeStream= 编码嗅探层 +stringStream。默认编码规则与loadBuffer保持一致(HTML 为windows-1252,XML 为utf8)。测试(src/index.spec.ts)验证了 UTF-16 BOM 字节流可被正确解析,且xmlMode: true时切换 htmlparser2 管线。

fromURL:让 Cheerio 帮你抓取页面

fromURL是五个方法中唯一会替你发起网络请求的,它是异步的,需要await

import * as cheerio from 'cheerio'; const $ = await cheerio.fromURL('https://example.com');

它做的事情比裸fetch多得多,以下行为值得逐条掌握(实现见 src/index.ts):

  • 重定向:自动跟随,最多 5 次。实现上通过interceptors.redirect({ maxRedirections: 5 })组合进 undici 的Client
  • 非 2xx 响应直接拒绝:抛出的错误是 undici 的ResponseError,携带状态码(src/index.ts),而不是把错误页交给你解析;
  • 非标记内容直接拒绝:若Content-Type既不是 HTML 也不是 XML,抛出RangeErrorThe content-type "..." is neither HTML nor XML.),避免把 PDF 当 HTML 解析。MIME 判断通过whatwg-mimetypeMIMEType完成(src/index.ts);
  • 自动选择 XML 模式:根据同一个Content-Type推断——application/xml之类的响应会以xmlMode: true解析,无需你手动指定(src/index.ts);
  • 编码来源:优先取Content-Typecharset参数,通过encoding.transportLayerEncodingLabel传给解码层;没有时才回退到字节嗅探;
  • baseURI设置为最终 URL:即重定向之后的地址(src/index.ts 中取请求历史history?.at(-1))。这正是prop('href')与 extract 文档抽取 能拿到绝对链接的原因——测试(src/index.spec.ts)验证了重定向后prop('href')解析为基于最终地址的完整 URL,以及无重定向时相对链接按请求 URL 解析的行为。

定制请求:requestOptions

fromURL的第二个参数接受requestOptions来控制请求,它们会被传给 undici 的stream方法:

const $ = await cheerio.fromURL('https://example.com', { requestOptions: { method: 'GET', headers: { 'user-agent': 'my-scraper/1.0 (+https://example.com/bot)', }, }, });

类型CheerioRequestOptions(src/index.ts)声明了requestOptions字段。使用时必须注意两条覆盖而非合并的规则:

  • method不会被默认defaultRequestOptions(src/index.ts)虽然定义了method: 'GET',但它只在你完全不传requestOptions时生效;一旦你提供了requestOptions却没有method,请求会以method must be a string失败,所以请始终带上method
  • headers是全有或全无:省略它则保留 Cheerio 默认的Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8;提供了它就整体替换默认值,而不是追加。源码中streamOptions{ headers: defaultRequestOptions.headers, path, ...requestOptions }的展开合并(src/index.ts),这正是「覆盖不合并」行为的来源。

性能与安全提示

fromURL依赖 undici,而 undici 仅在调用时才懒加载——源码注释(src/index.ts)明确说明:导入 undici 大约消耗 60ms 和 8MB 堆内存,因此不能因为引入fromURL就让所有使用者背上这个成本。从await import('undici')的写法可以看出这个优化。

安全方面,fromURL是 Cheerio 唯一会主动触网的 API(其余解析都不执行脚本、不请求网络),详见 安全指南。在把用户提供的 URL 交给fromURL之前务必校验——否则可能构成 SSRF。另外,Cheerio 解析开销与输入大小成正比,对不可信来源的标记应在上游限制大小。

如何选择正确的加载方法

综合以上分析,选型决策可以归结为两个问题:

  1. 输入是什么形态?字符串 →loadBufferloadBuffer;已解码文本流 →stringStream;原始字节流 →decodeStream;URL →fromURL
  2. 编码是否可知?不确定编码时,优先选择带编码嗅探能力的loadBufferdecodeStream,它们能识别<meta charset>与 BOM,并依据解析模式回退到windows-1252(HTML)或utf8(XML);编码确定时,loadstringStream更直接高效。

最后记住浏览器环境只提供load。凡涉及流式大文档或跨编码抓取,优先考虑loadBuffer/decodeStream的嗅探能力;需要完整 URL 解析时,用fromURL并善用其自动重定向、Content-Type校验与baseURI设置。各方法的测试用例集中在 src/index.spec.ts,可作为行为契约继续深入阅读。

【免费下载链接】cheerioThe fast, flexible, and elegant library for parsing and manipulating HTML and XML.项目地址: https://gitcode.com/gh_mirrors/ch/cheerio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询