木偶:将圆形结构转换为JSON是否要传递嵌套的JSHandle? 为什么会发生错误可能的修复

我正在尝试抓取一页纸的网站。有多种选择组合会导致不同的搜索重定向。我在page.evaluate的回调函数中编写了一个for循环,以单击不同的选择,并在每个按钮中进行了单击搜索。但是,我得到了一个错误:将圆形结构转换为JSON是否要传递嵌套的JSHandle?

请帮助!

我当前的代码版本如下:

const res = await page.evaluate(async (i,courseCountArr,page) => {
    for (let j = 1; j < courseCountArr[i]; j++) {
        await document.querySelectorAll('.btn-group > button,.bootstrap-select > button')['1'].click() // click on school drop down
        await document.querySelectorAll('div.bs-container > div.dropdown-menu > ul > li > a')[`${j}`].click() // click on each school option
        await document.querySelectorAll('.btn-group > button,.bootstrap-select > button')['2'].click() // click on subject drop down
        const subjectLen = document.querySelectorAll('div.bs-container > div.dropdown-menu > ul > li > a').length // length of the subject drop down
        for (let k = 1; k < subjectLen; k++) {
            await document.querySelectorAll('div.bs-container > div.dropdown-menu > ul > li > a')[`${k}`].click() // click on each subject option
            document.getElementById('buttonSearch').click() //click on search button
            page.waitForSelector('.strong,.section-body')
            return document.querySelectorAll('.strong,.section-body').length
        }
    }
},i,page);
hustkazz 回答:木偶:将圆形结构转换为JSON是否要传递嵌套的JSHandle? 为什么会发生错误可能的修复

为什么会发生错误

虽然您没有显示足够的代码来重现问题,但这里有一个显示可能模式的最小重现:

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

大家都在问