NodeJS Puppeteer Получить внутренний текст дочерних элементов из XPath

У меня есть проект по очистке продуктов, приобретенных определенными клиентами, из внутренней CRM. В этой CRM используется много динамически загружаемых плиток, поэтому согласованных имен классов не так много (у многих есть идентификатор, случайным образом добавляемый при каждой загрузке страницы), а также есть много разных отчетов / элементов на странице с тем же именем класса, поэтому Я не могу запросить селектор элементов на всей странице.

Я определил "родительский" элемент, который мне нужен, через xpath. Затем я хочу развернуть и получить innerText только тех детей, которые соответствуют селектору запроса (в большинстве потоков, которые я вижу, люди используют селектор запросов на всей странице, это приведет к результатам из меню, которые мне не нужны).

Я могу сделать это в обычном Javascript в консоли браузера, просто не могу понять, как это сделать в Node / Puppeteer. Вот что у меня есть на данный момент:

//Getting xpath of the "box" that contains all of the product tiles that a customer has
const productsBox = await page.$x("/html/body/blah/blah/blah");

Вот где он ломается. Я не очень хорошо знаком с некоторым синтаксисом и не разбираюсь в документации Puppeteer, но я пробовал несколько разных методов (мне также не хватает функций для использования формата =>. В документации Puppeteer есть пример того, что я пытаюсь сделать , но я пробовал с той же структурой, и она тоже ничего не вернула):

//Tried using the elementHandle.$$eval approach on the zero index of my xpath results, 
//but doesn't return anything when I console.log(productsList)
    const productsList = await productsBox[0].$$eval('.title-heading', function parseAndText (products) {
      productsList=[];
      for (i=0; i<products.length; i++) {
        productsList.push(products[i].innerText.trim());
      }
      return productsList;
      }
    );

//Tried doing the page.$$eval approach with selector, passing in the zero index of my xpath
      const productsList = await page.$$eval('.title-heading', function parseAndText (products) {
      productsList=[];
      for (i=0; i<products.length; i++) {
        productsList.push(products[i].innerText.trim());
      }
      return productsList;
      }, productsBox[0]

//Tried the page.evaluate and then page.evaluateHandle approach on the zero index of my xpath, 
//doing the query selection inside the evaluation and then doing something with that.
    let productsList= await page.evaluateHandle(function parseAndText(productsBoxZero) {
      productsInnerList = productsBoxZero.querySelectorAll(".title-heading");
      productsList=[];
      for (i=0; i<productsInnerList.length; i++) {
        productsList.push(productsInnerList[i].innerText.trim());
        //Threw a console log here to see if it does anything, 
        //But nothing is logged
        console.log("Pushed product " + i + " into the product list");
      }
      return productsList;
    }, productsBox[0]);

Что касается вывода, я записал в консоль некоторые переменные и получил следующее:

productsBox is JSHandle@node
productsBox[0] is JSHandle@node
productList is

Для сравнения, я делал это параллельно через Javascript в консоли, чтобы убедиться, что я правильно выполняю логику и получаю то, что ожидаю:

>productsBox=$x("/html/body/blah/blah/blah");
>productsInnerList=productsBox[0].querySelectorAll(".title-heading");
>productsInnerList.length;
//2, and this customer has 2 products
>productsList=[];
>for (i=0; i<productsInnerList.length; i++) {
    productsList.push(productsInnerList[i].innerText.trim());
};
>console.log(productsList)
>["Product 1", "Product 2"]

Спасибо, что дочитали до этого места, и ценю вашу помощь!

[Редактировать]

Для дополнительного исследования я попытался использовать page.evaluateHandle и до сих пор пытался регистрировать свои переменные:

productsBox is JSHandle@node
productsBox[0] is JSHandle@node
productList is JSHandle@array

Это прогресс. Я пытался сделать: let productsText=await productsList.jsonValue();

Но когда я пытаюсь вывести, я ничего не получаю:

await console.log("productsText is " + productsText);

productsBox is JSHandle@node
productsBox[0] is JSHandle@node
productList is JSHandle@array
productsText is

person NotGoodAtThis    schedule 26.03.2020    source источник


Ответы (2)


Я бы посоветовал внимательно прочитать документацию, прежде чем пробовать каждую функцию. $$eval выполняет оценку в селекторе, и передача элемента в этом случае бессмысленна. evaluateHandle предназначен для возврата внутристраничных элементов, поскольку вы возвращаете массив текста и он сериализуемый, он вам не нужен. Все, что вам нужно, это передать элемент page.evaluate или делать все в контексте кукловода.

Чтобы увидеть на странице console.log, вам необходимо:

page.on('console', msg => console.log(msg.text()));
  1. Использование page.evaluate
let productsList= await page.evaluate((element) => {
    const productsInnerList = element.querySelectorAll(".title-heading");
    const productsList=[];
    for (const el of productsInnerList) {
        productsList.push(el.innerText.trim());
        console.log("Pushed product " + el.innerText.trim() + " into the product list");
    }
    return productsList;
}, productsBox[0]);
  1. Использование elementHandle.$$
const productList = [];
const productsInnerList = await productsBox[0].$$('.title-heading');
for (const element of productsInnerList){
    const innerText = await (await element.getProperty('innerText')).jsonValue();
    productList.push(innerText);
}
person mbit    schedule 26.03.2020
comment
Большое спасибо! Я все еще рано изучаю как Node, так и Javascript в целом, поэтому я долго просматривал документацию. Кое-что я понимаю, а кое-что для меня все еще немного сложно. Я провел вечер, играя с вашими примерами кода на нескольких других сайтах. Я наконец-то заставил его работать на другом сайте, используя оба метода. На моем исходном сайте он все еще не работал. Добавлена ​​еще одна строка ожидания для дочернего xpath, и она начала работать! Еще раз спасибо за вашу помощь, и я буду продолжать учиться и работать над этим. - person NotGoodAtThis; 27.03.2020
comment
@ NotGoodAt: Вы правы, я пропустил ожидание перед выбором детей. отредактировал ответ. - person mbit; 27.03.2020
comment
ваше решение полностью сработало, и я проверил его на других сайтах, которые имели структуру страниц, аналогичную моему целевому сайту. Сайт, который я просматриваю для своего проекта, загружается немного медленно, поэтому мне пришлось провести дополнительную проверку, чтобы убедиться, что родительский и хотя бы один из целевых дочерних элементов существуют. Еще раз спасибо за вашу помощь, и я смог много экспериментировать с обоими форматами на основе ваших отзывов! - person NotGoodAtThis; 28.03.2020

Основываясь на ответе @mbit, я смог заставить его работать. Сначала я тестировал на другом сайте, который был похож по структуре на мой. Скопировал код на свой исходный сайт, но он все еще не работал, получил только нулевой вывод. Оказывается, хотя у меня была страница ожидания. $ X (full / xpath) для родительского элемента, дочерние элементы, содержащие innerText, все еще не загружались. Итак, я сделал две вещи:

1) Добавлена ​​еще одна страница ожидания. $ X (full / xpath) для первого элемента в списке, который был одной из моих целей 2) Реализован подход page.evaluate, предоставляемый mbit. 2a) Явно выписал функцию (все еще оборачивая голову вокруг структуры =>)

Окончательный код ниже (имена некоторых переменных изменены в результате тестирования):

let productsTextList= await page.evaluate(function list(list) {
  const productsInnerList = list.querySelectorAll(".title-heading");
  productsTextList =[];
  for (n=0; n<productsInnerList.length; n++) {
      product=productsInnerList[n].innerText.trim();
      productsTextList.push(product);
  }
  return productsTextList;
}, productsBox[0]);

console.log(productsTextList);

Я выбрал подход page.evaluate, потому что он более точно соответствует тому, что я делал в консоли браузера, и с ним так легко тестировать. Уловка с подходом elementHandle. $$ заключалась, как упоминалось в mbit, в использовании await element.getProperty('innerText'), а не .innerText. Во время поиска и устранения неполадок я также наткнулся на эту ветку на GitHub, в которой также рассказывается о том, как для его извлечения (аналогично подходу mbit, описанному выше). Вы не одиноки для тех, кто сталкивается с подобными проблемами!

person NotGoodAtThis    schedule 27.03.2020