news 2026/9/9 18:57:15

Puppeteer 的 page.evaluate 和 evaluateHandle 有什么区别,该怎么取回页面数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Puppeteer 的 page.evaluate 和 evaluateHandle 有什么区别,该怎么取回页面数据

Puppeteer 的 page.evaluate 和 evaluateHandle 有什么区别,该怎么取回页面数据

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

用 Puppeteer 驱动页面后,最常见的一个任务是"把页面上的某个值取回自己的脚本里"。此时会遇到两个方法:page.evaluatepage.evaluateHandle,签名几乎一样,但返回的东西完全不同。更常见的情况是:evaluatereturn document.body之后拿到的是{},于是不知道该换哪个方法。

本文基于 Puppeteer 的 JavaScript execution 指南 和 API 文档(Page.evaluate、Page.evaluateHandle、JSHandle、ElementHandle),讲清楚两者的区别,以及取回数据时该走哪条路径。

先建立上下文:在页面里执行 JS 的最小路径

以下代码取自 JavaScript execution 指南,其中的'YOUR_SITE'是文档原有占位符,替换为你要访问的实际地址即可:

import puppeteer from 'puppeteer'; const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('YOUR_SITE'); const three = await page.evaluate(() => { return 1 + 2; }); console.log(three); await browser.close();

有一个必须记住的执行机制:函数体是在你的脚本里定义的,但会被 Puppeteer 转成字符串发送到目标页面里执行。因此它无法访问你脚本作用域里的变量,也不能调用脚本里定义的其他函数——整个逻辑必须写在函数体内。文档也支持直接传字符串(如page.evaluate('1 + 2')),但文档明确建议优先用函数形式:字符串形式下,被求值函数能看到的类型和全局变量无法确定,TypeScript 里尤其要注意被引用对象的类型是否正确。

两者的核心区别:取回的是"值"还是"页面内对象的引用"

API 文档对两者差异的原文定义只有一句话(见 Page.evaluateHandle):

The only difference between page.evaluate and page.evaluateHandle is that evaluateHandle will return the value wrapped in an in-page object.

也就是说:

返回什么适用情况
page.evaluatePromise<Awaited<ReturnType<Func>>>,页面函数执行后的原始值只要一个可以跨上下文传回的最终值
page.evaluateHandlePromise<HandleFor<...>>,即JSHandleElementHandle页面内对象的引用需要在页面里继续持有这个对象并做后续操作

两者对 Promise 的行为一致:如果页面里的函数返回 Promise,Puppeteer 会等它 resolve 后再继续,所以page.evaluate(() => new Promise(...))会阻塞到页面内 Promise 完成。

evaluate:取回纯数据的正确姿势

指南对evaluate返回值的说明是:

  • 返回基本类型时,自动转换成脚本上下文中的基本类型;
  • 返回对象时,Puppeteer 会把它序列化成 JSON 再在脚本端重建——"This process might not always yield correct results"。

所以只要目标数据是字符串、数字、布尔值,或者你手动在页面里拼好的普通对象/数组,用evaluate一步到位。

从元素上提取单个值,优先用page.$eval(Page.$eval:找到第一个匹配元素,把元素作为pageFunction的第一个参数)。文档给出的示例:

const searchValue = await page.$eval('#search', el => el.value); const preloadHref = await page.$eval('link[rel=preload]', el => el.href); const html = await page.$eval('.main-container', el => el.outerHTML);

TypeScript 下默认参数类型是Element,访问valuehref这类子类型字段时需要显式标注:

// if you don't provide HTMLInputElement here, TS will error // as `value` is not on `Element` const searchValue = await page.$eval( '#search', (el: HTMLInputElement) => el.value, );

两个行为差异要注意:$eval找不到匹配元素会抛错,而 Page.$ 找不到时返回null。写断言式提取用$eval,需要容错判断"元素是否存在"时用page.$然后自己判null

page.evaluate本身也可以接收参数(Page.evaluate):

const three = await page.evaluate( (a, b) => { return a + b; // 1 + 2 }, 1, 2, );

参数可以是基本值,也可以是JSHandle/ElementHandle实例,比如先page.$('body')拿 handle,再传给evaluateinnerHTML

返回 DOM 节点得到{}时:改用 evaluateHandle

指南给出的典型翻车示例:

const body = await page.evaluate(() => { return document.body; }); console.log(body); // {}, unexpected!

原因上面已经说了:对象要走 JSON 序列化重建,DOM 节点重建出来是空对象。指南给出的解法就是"按引用返回":

const body = await page.evaluateHandle(() => { return document.body; }); console.log(body instanceof ElementHandle); // true

Page.evaluateHandle 文档的说明是:大多数时候返回JSHandle;如果页面函数返回的是元素引用,你拿到的会是ElementHandle(它继承自JSHandle)。

拿元素 handle 之后可以直接执行元素级操作,这是evaluate给不了的:

const button = await page.evaluateHandle(() => document.querySelector('button'), ); // can call `click` because `button` is an `ElementHandle` await button.click();

文档还提示:TypeScript 默认假设evaluateHandle返回JSHandle,如果你确定它返回ElementHandle,用泛型显式标注以获得完整类型:

const button = await page.evaluateHandle<ElementHandle>(...);

从 handle 里取回数据:jsonValue、getProperty、evaluate

evaluateHandle把对象留在了页面里,数据要取回脚本侧,JSHandle 提供了这几条路:

1.jsonValue()—— "A vanilla object representing the serializable portions of the referenced object",即拿到引用对象中可序列化部分的普通 JS 对象:

const aHandle = await page.evaluateHandle(() => document.body); const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle); console.log(await resultHandle.jsonValue()); await resultHandle.dispose();

两个限制必须知道(见 JSHandle.jsonValue):

  • 对象存在循环引用时会抛错;
  • 即使对象上有toJSON方法,jsonValue()不会调用它。

2.getProperty(name)—— 从引用对象上取单个属性,拿到的是新 handle,可以再jsonValue()

const documentHandle = await page.evaluateHandle('document'); // 文档示例 const titleHandle = await documentHandle.getProperty('title'); console.log(await titleHandle.jsonValue());

3.handle.evaluate()—— 以当前 handle 作为第一个参数在页面里继续求值,取回的是原始值:

const url = await windowHandle.evaluate((w) => w.location.href);

文档示例page.evaluateHandle('document')展示的就是字符串形式拿document的 handle。

不要忘记释放 handle

JSHandle 的机制说明:handle 会阻止引用的对象被垃圾回收,除非你主动dispose();当关联 frame 发生导航或父 context 销毁时,handle 会自动释放。跨导航场景不用手动管,但在同一页面里创建了大量 handle(例如循环里反复evaluateHandle提取数据)时,用完后调用 dispose() 释放

const bodyHandle = await page.$('body'); const html = await page.evaluate(body => body.innerHTML, bodyHandle); await bodyHandle.dispose();

这是 Page.evaluate 文档 Example 3 的完整用法。

取数路径怎么选,以及边界

结合上面的机制,取回页面数据可以按这个决策走:

  1. 目标是单个可序列化的值(字符串、数字、布尔、你在页面里拼好的纯数据对象/数组):page.$eval(selector, el => el.xxx)(针对单个元素)或page.evaluate(() => ...)(针对整个页面)一步取回。
  2. 需要持有页面内对象继续操作(点击、滚动、继续取属性):page.evaluateHandleJSHandle/ElementHandle,再用jsonValue()/getProperty()/handle.evaluate()取回具体数据,用完后dispose()
  3. 直接returnDOM 节点这类不可 JSON 化的对象给evaluate,只会得到{}——这是文档明确展示的失败模式,不要靠它取元素。

边界与限制(均来自上述文档):

  • evaluate返回对象走 JSON 序列化重建,"might not always yield correct results";
  • 页面函数被字符串化到页面执行,不能引用脚本作用域的变量;
  • jsonValue()遇循环引用抛错,且不调用toJSON
  • $eval找不到元素抛错,page.$找不到元素返回null

如果要继续深入,JSHandle 还有getProperties()(拿到引用对象所有属性的 handle map)、asElement()(判断 handle 是否为ElementHandle)等方法;ElementHandle 则提供$$$screenshotboundingBox等元素级操作,都是拿 handle 之后的下一组动作。

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 18:56:35

STM32F405RGT6五串口通信实战:引脚分配与代码详解

简介&#xff1a;这套代码基于STM32F405RGT6单片机&#xff0c;面向需要同时管理串口1至串口5通信的嵌入式开发者&#xff0c;解决多路UART数据接收、独立缓冲与状态标志管理的工程问题。压缩包共145个文件&#xff0c;涵盖42个.h头文件、36个.c源文件&#xff0c;以及编译生成…

作者头像 李华
网站建设 2026/9/9 18:56:27

Unity虚拟仿真开发实战:从场景搭建到数字孪生闭环

在虚拟仿真、数字孪生、VR/AR 这些概念频繁出现在招聘要求和项目申报书的今天&#xff0c;很多开发者的第一反应是“先学 Unity”。这个判断没有错&#xff0c;但真正动手之后&#xff0c;很多人会卡在同一个地方&#xff1a;安装好了 Unity&#xff0c;打开编辑器&#xff0c;…

作者头像 李华
网站建设 2026/9/9 18:55:27

Hermes智能体记忆外挂:分层设计、接入与排障

Hermes 这类智能体框架在单轮对话里可以回答得很漂亮&#xff0c;可一旦关掉窗口&#xff0c;它就像失忆了一样。给 Hermes 装上记忆外挂&#xff0c;真正要解决的并不是把模型底座换得更大&#xff0c;而是把用户偏好、历史结论、失败记录和任务状态保存下来&#xff0c;在下次…

作者头像 李华