Кукольник - как выбрать элемент на основе его внутреннего текста?

Я работаю над очисткой нескольких страниц с помощью Puppeteer. Контент не различается по классам/идентификаторам/и т.д. и представлены в другом порядке между страницами. Таким образом, мне нужно будет выбрать элементы на основе их внутреннего текста. Я включил упрощенный образец html ниже:

<table>
<tr>
    <th>Product name</th>
    <td>Shakeweight</td>
</tr>
<tr>
    <th>Product category</th>
    <td>Exercise equipment</td>
</tr>
<tr>
    <th>Manufacturer name</th>
    <td>The Shakeweight Company</td>
</tr>
<tr>
    <th>Manufacturer address</th>
    <td>
        <table>
            <tr><td>123 Fake Street</td></tr>
            <tr><td>Springfield, MO</td></tr>
        </table>
    </td>
</tr>

В этом примере мне нужно будет очистить имя производителя и адрес производителя. Поэтому я полагаю, что мне нужно будет выбрать соответствующий tr на основе внутреннего текста вложенного th и очистить связанный td внутри того же tr. Обратите внимание, что порядок строк в этой таблице не всегда одинаков, и таблица содержит гораздо больше строк, чем в этом упрощенном примере, поэтому я не могу просто выбрать 3-ю и 4-ю td.

Я попытался выбрать элемент на основе внутреннего текста, используя XPATH, как показано ниже, но, похоже, он не работает:

var manufacturerName = document.evaluate("//th[text()='Manufacturer name']", document, null, XPathResult.ANY_TYPE, null)

Это даже не те данные, которые мне понадобятся (это будет td, связанный с this th), но я решил, что это будет как минимум шаг 1. Если бы кто-то мог внести свой вклад в стратегию выбора по внутреннему тексту или для выбора td, связанного с этим th, я был бы очень признателен.


person MacGruber    schedule 24.09.2020    source источник


Ответы (4)


Это действительно вопрос xpath и не относится к кукловоду, поэтому этот вопрос также может помочь, так как вам нужно будет найти <td>, который идет после найденного вами <th>: XPath:: Получить следующего брата

Но ваш xpath действительно работает для меня. В Chrome DevTools на странице с HTML в вашем вопросе запустите эту строку, чтобы запросить документ:

$x('//th[text()="Manufacturer name"]')

ПРИМЕЧАНИЕ. $x() — это вспомогательная функция, которая работает только в Chrome DevTools, хотя в Puppeteer есть аналогичная функция Page.$x.

Это выражение должно возвращать массив с одним элементом, <th> с этим текстом в запросе. Чтобы получить <td> рядом с ним:

$x('//th[text()="Manufacturer name"]/following-sibling::td')

И чтобы получить его внутренний текст:

$x('//th[text()="Manufacturer name"]/following-sibling::td')[0].innerText

Как только вы сможете следовать этому шаблону, вы сможете использовать аналогичные стратегии для получения нужных данных в кукловоде, например:

const puppeteer = require('puppeteer');

const main = async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('http://127.0.0.1:8080/');  // <-- EDIT THIS

  const mfg = await page.$x('//th[text()="Manufacturer name"]/following-sibling::td');
  const prop = await mfg[0].getProperty('innerText');
  const text = await prop.jsonValue();
  console.log(text);

  await browser.close();
}

main();
person Todd Price    schedule 24.09.2020

Согласно вашему объяснению варианта использования в приведенном выше ответе, вот логика для варианта использования:

await page.goto(url, { waitUntil: 'networkidle2' }); // Go to webpage url

await page.waitFor('table'); //waitFor an element that contains the text

const textDataArr = await page.evaluate(() => {
    const trArr = Array.from(document.querySelectorAll('table tbody tr'));

    //Find an index of a tr row where th innerText equals 'Manufacturer name'
    let fetchValueRowIndex = trArr.findIndex((v, i) => {
        const element = document.querySelector('table tbody tr:nth-child(i+1) th');
        return element.innerText === 'Manufacturer name';
    });

    //If the findex is found return the innerText of td of the same row else returns undefined
    return (fetchValueRowIndex > -1) ? document.querySelector(`table tbody tr:nth-child(${fetchValueRowIndex}+1) td`).innerText : undefined;
});
console.log(textDataArr);
person kavigun    schedule 24.09.2020

Вы можете сделать что-то вроде этого, чтобы получить данные:

await page.goto(url, { waitUntil: 'networkidle2' }); // Go to webpage url

await page.waitFor('table'); //waitFor an element that contains the text

const textDataArr = await page.evaluate(() => {
    const element = document.querySelector('table tbody tr:nth-child(3) td'); // select thrid row td element like so
    return element && element.innerText; // will return text and undefined if the element is not found
});
console.log(textDataArr);
person kavigun    schedule 24.09.2020
comment
Спасибо за ответ - к сожалению, порядок строк этой таблицы не всегда одинаков, поэтому я не могу просто выбрать 3-ю и 4-ю тд. Также нет идентификаторов или классов - мне нужно выбрать td на основе внутреннего текста th того же tr, являющегося именем производителя или адресом производителя. - person MacGruber; 24.09.2020
comment
Я опубликовал новый ответ для варианта использования, который вы здесь разъясняете, попробуйте эту логику, она сработает для вас. - person kavigun; 24.09.2020

Простой способ получить их все сразу:

let data = await page.evaluate(() => {
  return [...document.querySelectorAll('tr')].reduce((acc, tr, i) => {
    let cells = [...tr.querySelectorAll('th,td')].map(el => el.innerText)
    acc[cells[0]] = cells[1]
    return acc
  }, {})
})
person pguardiario    schedule 25.09.2020