尝试将util模块传入puppeteer page.evaluate失败求解决
我明白你现在卡在把Node.js的util模块传到Puppeteer的page.evaluate里的问题了——这个坑我之前也踩过,毕竟浏览器上下文和Node.js上下文是完全隔离的,直接传模块对象肯定不行。你遇到的ReferenceError: util is not defined本质上就是因为这两个环境完全不互通,Node.js的模块没法直接在浏览器环境里使用。
为什么你之前找的方案可能没用?
很多旧方案可能尝试直接把util对象作为参数传给page.evaluate,但util是一个包含大量不可序列化属性的复杂对象(比如内部函数引用、非JSON兼容的状态),Puppeteer在序列化参数的时候会丢失这些内容,导致浏览器上下文里拿到的是个空对象或者直接是undefined,自然会报错。
可行的解决方法
方法1:用page.exposeFunction桥接需要的util方法
page.exposeFunction可以把Node.js端的函数暴露成浏览器上下文里的全局函数,这样你就能在page.evaluate里调用Node端的util方法了。比如你需要用util.format:
const puppeteer = require("puppeteer"); const util = require("util"); const path = "http://books.toscrape.com/"; (async () => { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); // 把Node端的util.format暴露给浏览器,命名为window.utilFormat await page.exposeFunction("utilFormat", (...args) => { return util.format(...args); }); await page.goto(path, {waitUntil: "networkidle2"}); const result = await page.evaluate(async () => { // 在浏览器上下文调用暴露的方法 const bookTitle = document.querySelector("h1").textContent; return utilFormat("当前页面的书籍标题:%s", bookTitle); }); console.log(result); await browser.close(); })();
方法2:批量暴露多个util方法
如果你需要用到util里的多个方法,可以写个小工具函数批量处理:
const puppeteer = require("puppeteer"); const util = require("util"); const path = "http://books.toscrape.com/"; // 批量暴露指定的util方法 async function exposeUtilMethods(page, methodNames) { for (const name of methodNames) { // 生成驼峰命名的全局函数名,比如util.inspect变成window.utilInspect const exposedName = `util${name.charAt(0).toUpperCase() + name.slice(1)}`; await page.exposeFunction(exposedName, (...args) => util[name](...args)); } } (async () => { const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); // 暴露format和inspect两个方法 await exposeUtilMethods(page, ["format", "inspect"]); await page.goto(path, {waitUntil: "networkidle2"}); const result = await page.evaluate(async () => { const firstBook = document.querySelector(".product_pod h3 a"); // 使用暴露的utilInspect查看元素信息 const elementInfo = utilInspect(firstBook); // 使用utilFormat格式化输出 return utilFormat("第一本书的信息:%s", elementInfo); }); console.log(result); await browser.close(); })();
方法3:复制util方法的实现到浏览器上下文
如果只是用到util里的简单工具函数(比如一些纯JS实现的方法),你可以直接把方法的代码复制到page.evaluate里,完全脱离Node端的依赖:
await page.evaluate(() => { // 直接在浏览器上下文实现util.format的核心逻辑 function format(str, ...args) { return str.replace(/%[sdj%]/g, (match) => { if (match === "%%") return "%"; const arg = args.shift(); switch (match) { case "%s": return String(arg); case "%d": return Number(arg); case "%j": return JSON.stringify(arg); default: return match; } }); } // 现在可以直接用这个format函数了 console.log(format("Book: %s", document.querySelector("h1").textContent)); });
关键提醒
永远记住:page.evaluate的回调函数是在浏览器的V8环境里运行的,而你的Node.js代码是在Node.js的V8环境里运行的,这两个环境没有共享内存,只能通过可序列化的JSON数据或者exposeFunction这种桥接机制通信,直接传递Node.js模块对象是不可能的。
内容的提问来源于stack exchange,提问作者JdeMello

