Puppeteer调用自定义函数提示未定义、array.forEach不存在问题求助
问题根源
Puppeteer存在两层完全隔离的运行环境,你的所有错误操作本质上都是混淆了两层环境的能力边界:
- Node.js层:你的爬虫业务代码、Puppeteer实例、默认定义的函数都运行在这一层,完全无法访问浏览器的document、window等DOM对象
- 浏览器层:
page.evaluate()的回调会被序列化后传入浏览器执行,运行环境和普通网页的JS环境一致,默认无法访问Node.js层定义的函数、变量
你之前的尝试出错的具体原因:
- 直接在evaluate里调用外层定义的函数:等于在浏览器环境调用不存在的函数,自然报未定义
- 新增page参数改造async函数:page是Node层的对象,浏览器环境根本不存在该实例,
page.document属于完全无效的属性,必然报错 - 使用
page.exposeFunction注入失败:exposeFunction的作用是把Node层的函数暴露给浏览器调用,但函数实际还是运行在Node层,你写的函数里用到了浏览器专属的document,Node层无该对象,返回值为undefined或错误对象,自然没有forEach方法。你参考的示例中注入的函数都是纯数值计算,不涉及DOM操作,所以能正常运行,和你的场景并不匹配。
可复用的解决方案
方案1:工具函数注入到浏览器上下文
如果你的工具函数仅涉及浏览器DOM操作,可以用page.evaluateOnNewDocument直接注入到页面全局作用域,所有page.evaluate都可以直接调用:
// 定义仅使用浏览器API的工具函数 const grabDomConvertNodlistToArray = (grabDomHtmlPath) => { const nList = document.querySelectorAll(grabDomHtmlPath); return Array.from(nList); }; // 页面初始化后将函数注入到浏览器全局 await pageBodies.evaluateOnNewDocument(` window.grabDomConvertNodlistToArray = ${grabDomConvertNodlistToArray.toString()}; `); // 之后任意page.evaluate内都可以直接调用该函数 const rlBodyNames = await pageBodies.evaluate(() => { const array = grabDomConvertNodlistToArray(".testbodies > div > h1"); return array.map(element => element.textContent); });
方案2:封装Node层通用调用方法(更推荐)
把重复逻辑直接封装成Node层的通用函数,不需要维护浏览器注入逻辑,出错概率更低:
// 封装通用的获取指定选择器文本列表方法 const getTextListBySelector = async (page, selector) => { return page.evaluate((selector) => { return Array.from(document.querySelectorAll(selector)).map(el => el.textContent); }, selector); }; // 不同场景调用仅需传入页面对象和选择器即可,无需重复写转换逻辑 const rlBodyNames = await getTextListBySelector(pageBodies, ".testbodies > div > h1"); const testNames = await getTextListBySelector(pageBodies, ".test > div h2");
内容的提问来源于stack exchange,提问作者TheCoderJT
相关产品推荐
相关产品推荐

