为什么会发生错误
虽然您没有显示足够的代码来重现问题,但这里有一个显示可能模式的最小重现:
const puppeteer = require("puppeteer");
let browser;
(async () => {
const html = `<ul><li>a</li><li>b</li><li>c</li></ul>`;
browser = await puppeteer.launch();
const [page] = await browser.pages();
await page.setContent(html);
// ...
const nestedHandle = await page.$$("li"); // $$ selects all matches
await page.evaluate(els => {},nestedHandle); // throws
// ...
})()
.catch(err => console.error(err))
.finally(async () => await browser.close())
;
输出是
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'BrowserContext'
| property '_browser' -> object with constructor 'Browser'
--- property '_defaultContext' closes the circle Are you passing a nested JSHandle?
at JSON.stringify (<anonymous>)
为什么会这样? Puppeteer 以编程方式在浏览器控制台内执行 page.evaluate
(和系列:evaluateHandle
、$eval
、$$eval
)回调中的所有代码。浏览器控制台是一个不同于 Node 的环境,它是 Puppeteer 和 elementHandles 所在的环境。为了弥补进程间的差距,对 evaluate
的回调、参数和返回值进行了序列化和反序列化。
这样做的结果是,您无法像在浏览器中尝试使用 page.waitForSelector('.strong,.section-body')
那样访问任何 Node 状态。 page
与浏览器处于完全不同的进程中。 (顺便说一句,document.querySelectorAll
是完全同步的,因此await
没有意义。)
Puppeteer elementHandles 是一种复杂的结构,用于挂接页面的 DOM,无法像您尝试那样序列化并传递到页面。 Puppeteer 必须在幕后执行翻译。任何传递给 evaluate
(或对它们调用 .evaluate()
)的 elementHandles 都被跟随到它们所代表的浏览器中的 DOM 节点,而该 DOM 节点就是你的 evaluate
的回调调用。截至撰写本文时,Puppeteer 无法使用嵌套的 elementHandles 执行此操作。
可能的修复
在上面的代码中,如果您将 .$$
更改为 .$
,您将只检索第一个 <li>
。这个单一的、非嵌套的 elementHandle 可以转换为一个元素:
// ...
const handle = await page.$("li");
const val = await page.evaluate(el => el.innerText,handle);
console.log(val); // => a
// ...
或者:
const handle = await page.$("li");
const val = await handle.evaluate(el => el.innerText);
console.log(val); // => a
在您的示例中进行这项工作是交换循环和 evaluate
调用的问题,以便您访问 Puppeteer 中的 courseCountArr[i]
,将嵌套的 elementHandles 解包为单独的参数以 {{1} },或将大部分控制台浏览器调用移回 Puppeteer(取决于您的用例和代码目标)。
您可以对每个 elementHandle 应用 evaluate
调用:
evaluate
要获得一系列结果,您可以这样做:
const nestedHandles = await page.$$("li");
for (const handle of nestedHandles) {
const val = await handle.evaluate(el => el.innerText);
console.log(val); // a b c
}
您还可以将 elementHandles 解包为 const nestedHandles = await page.$$("li");
const vals = await Promise.all(
nestedHandles.map(el => el.evaluate(el => el.innerText))
);
console.log(vals); // [ 'a','b','c' ]
的参数并在回调中使用 evaluate
参数列表:
(...els)
如果您除了句柄之外还有其他参数,您可以这样做:
const nestedHandles = await page.$$("li");
const vals = await page.evaluate((...els) =>
els.map(e => e.innerText),...nestedHandles
);
console.log(vals); // => [ 'a','c' ]
或:
const nestedHandle = await page.$$("li");
const vals = await page.evaluate((foo,bar,...els) =>
els.map(e => e.innerText + foo + bar),1,2,...nestedHandle);
console.log(vals); // => [ 'a12','b12','c12' ]
另一种选择可能是使用 const nestedHandle = await page.$$("li");
const vals = await page.evaluate(({foo,bar},{foo: 1,bar: 2},'c12' ]
,它选择多个句柄,然后在浏览器上下文中以所选元素的数组作为参数运行回调:
$$eval
如果您不使用 Node 中的句柄做任何其他事情,这可能是最干净的。
同样,您可以完全绕过 Puppeteer 并在浏览器上下文中进行整个选择和操作:
const vals = await page.$$eval("li",els =>
els.map(e => e.innerText)
);
console.log(vals); // => [ 'a','c' ]
(请注意,获取内部文本只是您可能拥有的任意复杂浏览器代码的占位符)
本文链接:https://www.f2er.com/3097746.html