querySelector 不适用于 puppeteer 中的子元素

Posted

技术标签:

【中文标题】querySelector 不适用于 puppeteer 中的子元素【英文标题】:querySelector doesn't work with child elements in puppeteer 【发布时间】:2021-01-20 01:56:36 【问题描述】:

我正在尝试使用 puppeteer:https://jcc.org/park-heights-indoor-pool-registration 抓取此页面,并将数据片段放入一个数组(事件时间、标题、注册链接等)。

我将要抓取的页面的 html 复制到本地 html 文件中,一切正常(使用完全相同的代码!),但使用 puppeteer,它返回 null 错误。最重要的是,当我选择单个元素时,收集所有数据时没有错误!

代码:

const puppeteer = require('puppeteer');

(async () => 

    let jcc_url = 'https://jcc.org/park-heights-indoor-pool-registration';


    let browser = await puppeteer.launch();
    let page = await browser.newPage();

    await page.goto(jcc_url, waitUntil: 'networkidle0');

    let data = await page.evaluate(() => 

        let slots_array = [];

        $(".GXPEntry").each(function (index, element) 

           slots_array[index] = 
                index: index,
                cancelled: undefined,
                time: element.querySelector(".GXPTime").textContent,
                title: element.querySelector('.GXPTitle').textContent,
                link: element.querySelector('a.signUpGXP').getAttribute("href"),
                availability: element.querySelector('div.GXPDescription span').textContent,
                dayOfWeek: element.querySelector('a').getAttribute('data-date')
            ;

            if (slots_array[index].title === "CANCELED: Lap Swimming - Men's Only"
                ||
                slots_array[index].title === "CANCELED: Lap Swimming - Women's Only") 
                slots_array[index].cancelled = true;
             else 
                slots_array[index].cancelled = false;
            
        );

        return slots_array;

    );

    console.log(data);

    await browser.close();

    )();

这是我定位的页面的 HTML 布局:

<div class="GXPEntry">
        <div class="GXPTime">8:15am-9:00am</div>
        <div class="GXPTitle"><img src="https://groupexpro.com/schedule/logos/custom/logo_53760.jpg"
         style="display: block; max-height: 30px; max-width: 120px; padding: 0px 5px 5px 0px;"
         title="">Lap Swimming - Men's Only<span
         style="position: relative; top: 2px; left: 4px;"><a class="signUpGXP removeIconGXP"
         href="https://groupexpro.com/gxp/reservations/start/index/11814665/10/05/2020?e=1"
         title="This class requires a reservation"><i
         style="background-image: url('https://groupexpro.com/gxp/design/img/glyphicons-halflings.png'); background-position: -96px -72px; background-repeat: no-repeat; display: inline-block; height: 14px; vertical-align: text-top; width: 14px; position: relative; top: 0px; left: -4px; float: left; margin-right:6px; "></i></a></span>
        </div>
        <div class="GXPInstructor">Staff</div>
        <div class="GXPStudio">Indoor Pool&nbsp;</div>
        <div class="GXPCategory">Aquatics</div>
        <div class="GXPLocation">Park Heights</div>
        <div class="GXPDescription">
            <a 11814665  class="descGXP" data-date="10/05/2020" href="javascript://""="">Description</a>
            &nbsp; | &nbsp;
            <a  class="signUpGXP"
               href="https://groupexpro.com/gxp/reservations/start/index/11814665/10/05/2020?e=1"
               textmsg="3 SPOTS LEFT">
                Sign Up</a>
      &nbsp;      <a  class="addToCalendar" href="#">
                 <img  border="0"  src="https://groupexpro.com/schedule/embed/images/ics.gif">
             </a>
            <br><br><span>3 SPOTS LEFT</span>
        </div>

我只是想从.signUpGXP类的链接中获取href数据,最后一个跨度标记“3 SPOTS LEFT”中的文本,来自div.GXPTitle的标题文本和data-date属性来自div.GXPDescription中的第一个链接。

如果我将 HTML 复制到本地文件中,这适用于 jQuery,但在 pupputeer 中它不起作用,并给我这个错误:

 (node:22638) UnhandledPromiseRejectionWarning: Error: Evaluation failed: TypeError: Cannot read property 'getAttribute' of null
    at HTMLDivElement.<anonymous> (__puppeteer_evaluation_script__:12:59)
    at Function.each (https://jcc.org/sites/default/files/js/js_POjCvph0DpQRBLbuAoUSghIegyfU_5lXHo4ESl4z0tw.js:2:2975)
    at $.fn.init.each (https://jcc.org/sites/default/files/js/js_POjCvph0DpQRBLbuAoUSghIegyfU_5lXHo4ESl4z0tw.js:2:835)
    at __puppeteer_evaluation_script__:5:24
    at ExecutionContext._evaluateInternal (/Users/moshe/coding-workspace/jcc-ph-pool-register/node_modules/puppeteer/lib/cjs/puppeteer/common/ExecutionContext.js:217:19)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async ExecutionContext.evaluate (/Users/moshe/coding-workspace/jcc-ph-pool-register/node_modules/puppeteer/lib/cjs/puppeteer/common/ExecutionContext.js:106:16)
    at async /Users/moshe/coding-workspace/jcc-ph-pool-register/app.js:13:16
(node:22638) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:22638) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

不完全确定为什么它找不到该属性。如果我这样做,它完全可以正常工作:

    const puppeteer = require('puppeteer');

(async () => 

    let jcc_url = 'https://jcc.org/park-heights-indoor-pool-registration';

    let browser = await puppeteer.launch();
    let page = await browser.newPage();

    await page.goto(jcc_url, waitUntil: 'networkidle2');

    let data = await page.evaluate(() => 
        let time = document.querySelector('.GXPTime').innerText;
        let title = document.querySelector('.GXPTitle').innerText;
        let availability = document.querySelector('.GXPDescription span').innerText;
        let link = document.querySelector('.signUpGXP').href;
        let dayOfWeek = document.querySelector('.GXPDescription a').getAttribute('data-date');

        return 
            time,
            title,
            availability,
            link,
            dayOfWeek
        

    );

    console.log(data);

    debugger;

    await browser.close();


)();

我在这里得到所有数据,但只有页面上的第一部分。

我会很感激这方面的帮助。谢谢!

【问题讨论】:

【参考方案1】:

如果我在浏览器中运行评估函数,我会遇到同样的错误。问题似乎在于已选中的活动没有注册链接。

【讨论】:

嗯...非常好的观点。谢谢。我会检查是否可以使用 if 语句将其过滤掉,然后它可能会起作用 非常感谢!有效!让我头疼不已……真不敢相信我没有注意到这一点! 我正在尝试导出 slot_array,以便可以在另一个文件中使用它。您知道即使使用异步功能也可以做到这一点吗? (也不能从另一个文件导入到这个异步中)。谢谢! 您可以尝试导出一个返回 Promise 的函数,该 Promise 与 slot_array 一起返回。然后在另一个文件中你可以 imort 这个函数,调用它,等待它并使用 slot_array。

以上是关于querySelector 不适用于 puppeteer 中的子元素的主要内容,如果未能解决你的问题,请参考以下文章

<template> + querySelector 使用 :scope 伪类适用于文档,但不适用于 documentFragment

CSS 过渡不适用于 height 属性

单击事件侦听器方法不适用于特定的 div

如何使用Puppeteer拍摄包含视频的页面的屏幕截图

Puppeteer 获取 querySelectorAll 的 innerText

在 Chrome 中嵌套 nth-child 的 querySelector 似乎不起作用