Puppeteer document.querySelectorAll 仅在循环中返回未定义“TypeError:无法读取未定义的属性(读取'innerHTML')”

KL.*_*Sea 5 javascript loops querying web-scraping puppeteer

我正在尝试以编程方式从 JavaScript 中的https://rarity.tools/upcoming/页面访问表中的数据。由于该网站通过 JavaScript 加载,因此我一直在使用 puppeteer。该网站有多个表(总共 4 个),我希望能够引用每个表并检查它们有多少行。

我最初尝试使用 nth-of-type,但我尝试从中接收数据的网站似乎没有以允许我使用 nth-of-type 或 nth-child 的方式构建其页面(请请参阅:在 javascript 错误中使用 pupeteer 访问第 n 个表并计算行数:“无法找到与选择器“table:nth-of-type(2) > tr”匹配的元素”” )。

相反,我尝试创建一个 for 循环来将每个表的innerHTML 设置为其自己的变量,然后根据索引分析HTML 字符串。如果我对数字进行硬编码,以下内容将返回正确的值:

       console.log(table_html)

        let table_html = await page.evaluate(
            () => document.querySelectorAll('table')[2].innerHTML
        )
Run Code Online (Sandbox Code Playgroud)

但是,一旦我将其设置为循环:

        for (let j = 0; j < numTables; j++) {
            let table_html = await page.evaluate(
                (j) => document.querySelectorAll('table')[j].innerHTML
            )

            console.log(table_html)
        }
Run Code Online (Sandbox Code Playgroud)

我收到错误:

错误:评估失败:TypeError:无法在ExecutionContext._evaluateInternal 处的puppeteer_evaluation_script :1:46 处读取未定义的属性(读取“innerHTML”)(C:\Users\kylel\Desktop\NFTSorter_IsolatedJS\node_modules\puppeteer\lib\cjs
puppeteer\common \ExecutionContext.js:221:19) 在 processTicksAndRejections (internal/process/task_queues.js:95:5) 在异步 ExecutionContext.evaluate (C:\Users\kylel\Desktop\NFTSorter_IsolatedJS\node_modules\puppeteer\lib\cjs\pup peteer\common\ExecutionContext.js:110:16) 在异步获取 (C:\Users\kylel\Desktop\NFTSorter_IsolatedJS\app.js:35:30)

所有代码:

const puppeteer = require('puppeteer');

let fetch = async () => {

    try {
        // Puppeteer initialization
        const browser = await puppeteer.launch({ headless: true, defaultViewport: null });
        const [page] = await browser.pages();
        await page.goto('https://rarity.tools/upcoming/');
        await page.waitForTimeout(2500) // Timeout so page can load actual content 

        const numTables = await page.$$eval('table', el => el.length) - 1;

        for (let j = 0; j < numTables; j++) {
            let table_html = await page.evaluate(
                (j) => document.querySelectorAll('table')[j].innerHTML
            )

            console.log(table_html)
        }

    }

    catch (error) {
        console.log(error)
    }
}


fetch();
Run Code Online (Sandbox Code Playgroud)

如何修复此 for 循环以允许我为每个表运行 document.querySelectorAll('table') ?

此外,如果有人对我实现目标的方法有任何见解(使用 puppeteer 基于可变数量的表以编程方式访问这些表中的数据),我将不胜感激!如果我最终使用此处描述的方法,有什么建议可以使用哪些工具来分析字符串形式的 HTML?

非常感谢!

ggo*_*len 1

这段代码展示了一个经典的 Puppeteer 陷阱:

            let table_html = await page.evaluate(
                (j) => document.querySelectorAll('table')[j].innerHTML
            )
Run Code Online (Sandbox Code Playgroud)

您需要j作为参数传递给,evaluate如何将变量传递到评估函数?,否则在反j序列化函数并在浏览器控制台中执行时未定义。

            let table_html = await page.evaluate(
                (j) => document.querySelectorAll('table')[j].innerHTML, j
            //                                                        ^^^ 
            )
Run Code Online (Sandbox Code Playgroud)

也就是说,我建议使用$$evalandmap而不是计数器for循环,以避免担心索引。另外,这waitForTimeout似乎是不必要的竞争条件。waitForSelector按照文档的建议使用事件驱动的方法似乎更快、更可靠。

const puppeteer = require("puppeteer"); // ^13.5.1

let browser;
(async () => {
   browser = await puppeteer.launch({headless: true});
   const [page] = await browser.pages();
   const url = "https://rarity.tools/upcoming/";
   await page.goto(url, {waitUntil: "domcontentloaded"});
   await page.waitForSelector("table");
   const tableLengths = await page.$$eval("table", els =>
     els.map(el => el.querySelectorAll("tr").length)
   );
   console.log(tableLengths);
   const tableHTML = await page.$$eval("table", els =>
     els.map(el => el.innerHTML)
   );
   console.log(tableHTML.map(e => e.slice(0, 50)));
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close())
;
Run Code Online (Sandbox Code Playgroud)

输出:

[ 224, 100, 21, 5 ]
[
  '<tr class=""><th colspan="4" class="text-xl text-c',
  '<tr class=""><th colspan="4" class="text-xl text-c',
  '<tr class=""><th colspan="4" class="text-xl text-c',
  '<tr class="hidden"><th colspan="4" class="text-xl '
]
Run Code Online (Sandbox Code Playgroud)

关于“如果我最终使用此处描述的方法,有什么建议可以使用哪些工具来分析字符串形式的 HTML?”,我不确定您计划对 HTML 进行什么样的分析,但一般来说也就是说,不要以字符串形式处理 HTML。对于 99% 的用例,请使用 Puppeteer 或浏览器控制台提供的 DOM、XPath 和 CSS 选择器。

例如,如果您想要每个表的平均价格,请选择<td>与价格列相对应的 s,循环遍历这些值,然后将单元格转换为数字并取平均值。使用字符串操作或正则表达式做同样的事情就像用剪刀修剪草坪,而那里有一台割草机。

更好的是,如果可以的话,完全避免刮擦。有一个开放端点https://collections.rarity.tools/upcoming2wget ,因此您可以使用或立即检索数据curl,假设它有您要查找的内容。