Search code examples
javascriptcss-selectorspuppeteerapify

Puppeteer $.eval selecting nested elements


Lets say i'm give situation like this page

<div id="details-container" class="style-scope ytd-channel-about-metadata-renderer">
         <yt-formatted-string class="subheadline style-scope ytd-channel-about-metadata-renderer">Details</yt-formatted-string>
        <table class="style-scope ytd-channel-about-metadata-renderer">
          <tbody class="style-scope ytd-channel-about-metadata-renderer"><tr class="style-scope ytd-channel-about-metadata-renderer">
            <td class="label style-scope ytd-channel-about-metadata-renderer">
              <yt-formatted-string class="style-scope ytd-channel-about-metadata-renderer"></yt-formatted-string>
            </td>
            <td class="style-scope ytd-channel-about-metadata-renderer">
              <ytd-button-renderer align-by-text="" class="style-scope ytd-channel-about-metadata-renderer" button-renderer=""></ytd-button-renderer>
              <div id="captcha-container" class="style-scope ytd-channel-about-metadata-renderer"></div>
              <div id="email-container" class="style-scope ytd-channel-about-metadata-renderer"></div>
              <a id="email" target="_blank" class="style-scope ytd-channel-about-metadata-renderer" href="mailto:undefined" hidden=""></a>
            </td>
          </tr>
          <tr class="style-scope ytd-channel-about-metadata-renderer">
            <td class="label style-scope ytd-channel-about-metadata-renderer">
              <yt-formatted-string class="style-scope ytd-channel-about-metadata-renderer"><span class="deemphasize style-scope yt-formatted-string"> Location:   </span></yt-formatted-string>
            </td>
            <td class="style-scope ytd-channel-about-metadata-renderer">
              <yt-formatted-string class="style-scope ytd-channel-about-metadata-renderer">YourCountry</yt-formatted-string>
            </td>
          </tr>
        </tbody></table>
      </div>

Lets say i need get the "YourCountry" how do i actually get to this element?

So far i tried:

  const location = await page.$$eval(
    "#details-container > table > tbody:nth-child(1) > tr:nth-child(1) > yt-formatted-string",
    locationEl => locationEl.innerHTML
  );
console.log(location) // Undefined

Not sure how to go about it, trying to return just tr and then again evaluating tr[1] doesn't work as it says that tr doesn't have a function .$$eval.

Note that i'm using apify to get the page.


Solution

  • In the HTML you’ve provided, the yt-formatted-string element you want is a direct child of the second td under the second tr, but you try to match it a yt-formatted-string that’s a direct child of the second tr. You need to fix your selector. For example:

    console.log("HTML:", document.querySelector("#details-container > table > tbody > tr:nth-child(2) > td:nth-child(2) > yt-formatted-string").innerHTML)
    <div id="details-container" class="style-scope ytd-channel-about-metadata-renderer">
      <yt-formatted-string class="subheadline style-scope ytd-channel-about-metadata-renderer">Details</yt-formatted-string>
      <table class="style-scope ytd-channel-about-metadata-renderer">
        <tbody class="style-scope ytd-channel-about-metadata-renderer">
          <tr class="style-scope ytd-channel-about-metadata-renderer">
            <td class="label style-scope ytd-channel-about-metadata-renderer">
              <yt-formatted-string class="style-scope ytd-channel-about-metadata-renderer"></yt-formatted-string>
            </td>
            <td class="style-scope ytd-channel-about-metadata-renderer">
              <ytd-button-renderer align-by-text="" class="style-scope ytd-channel-about-metadata-renderer" button-renderer=""></ytd-button-renderer>
              <div id="captcha-container" class="style-scope ytd-channel-about-metadata-renderer"></div>
              <div id="email-container" class="style-scope ytd-channel-about-metadata-renderer"></div>
              <a id="email" target="_blank" class="style-scope ytd-channel-about-metadata-renderer" href="mailto:undefined" hidden=""></a>
            </td>
          </tr>
          <tr class="style-scope ytd-channel-about-metadata-renderer">
            <td class="label style-scope ytd-channel-about-metadata-renderer">
              <yt-formatted-string class="style-scope ytd-channel-about-metadata-renderer"><span class="deemphasize style-scope yt-formatted-string"> Location:   </span></yt-formatted-string>
            </td>
            <td class="style-scope ytd-channel-about-metadata-renderer">
              <yt-formatted-string class="style-scope ytd-channel-about-metadata-renderer">YourCountry</yt-formatted-string>
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    And you should be able to call $$eval &c. if you have an ElementHandle. The problem is that your selector didn’t match, so you didn’t have one.