Estoy tratando de obtener elementos secundarios de un div usando titiritero.
este es mi código:
const puppeteer = require('puppeteer'); const url = 'https://example.com/'; //browser initialization async function initBrowser() { const browser = await puppeteer.launch({ headless: false, }); return browser; } async function getPage(browser) { const page = await browser.newPage(); await page.goto(url); return page; } async function getNewMessage(page) { let els = []; els = await page.$$('div.class'); for (let i = 0; i < els.length; i++) { const imgSrc = await els[i] .$('div.class2') .$eval('img', (el) => el.getAttribute('src')); console.log(imgSrc); const link = await els[i].$eval('div.class3', (elem) => elem.texContent); console.log(link); } } async function loading() { const browser = await initBrowser(); const page = await getPage(browser); await getNewMessage(page); } loading();la estructura de la página se ve así:
<div class="class"> <div class="class2"> <img src="..."> </div> <div class="class3"> message </div> </div> el error que me sale es:
Error: Error: no se pudo encontrar el selector de coincidencia de elementos "img".
De hecho, creo que descubrí cuál es el problema, porque en algunos casos obtengo la imagen. Creo que cuando trato de obtener la imagen, todavía se está cargando y no puedo atraparla.
A este código le falta un await :
const imgSrc = await els[i] .$('div.class2') .$eval('img', (el) => el.getAttribute('src')); No es posible encadenar de esta manera, porque .$ devuelve una promesa que no se esperaba, por lo que está llamando a .$eval en una simple y antigua promesa de JS en lugar de una función Titiritero. La solución sería await cada nueva llamada de Titiritero en una línea separada y usar variables intermedias.
const div = await els[i].$('div.class2'); const imgSrc = await div.$eval('img', (el) => el.getAttribute('src')); Técnicamente, podría encadenar entre paréntesis y await s en línea, pero sería más difícil de leer:
const imgSrc = await (await els[i].$('div.class2')) .$eval('img', (el) => el.getAttribute('src'));Mejor aún es combinar ambos selectores en uno. No es necesario el procedimiento de dos pasos:
const imgSrc = await div.$eval('div.class2 img', (el) => el.getAttribute('src')); Siguiendo esa lógica un paso más allá, es posible que pueda omitir todos los selectores intermedios, usando un .$$eval por arreglo que desee construir. Opcionalmente, puede fusionarlos si sabe que tendrán la misma longitud:
const puppeteer = require("puppeteer"); const html = `<div class="class"> <div class="class2"> <img src="..."> </div> <div class="class3"> message </div> </div>`.repeat(2); // repeat the structure for demonstration purposes let browser; (async () => { browser = await puppeteer.launch({headless: true}); const [page] = await browser.pages(); await page.setContent(html); await page.waitForSelector("div.class div.class2 img"); const imgSrcs = await page.$$eval( "div.class div.class2 img", els => els.map(e => e.getAttribute("src")) ); const textContents = await page.$$eval( "div.class div.class3", els => els.map(e => e.textContent) ); const data = imgSrcs.map((e, i) => ({ src: e, text: textContents[i] })); console.log(JSON.stringify(data, null, 2)); })() .catch(err => console.error(err)) .finally(() => browser?.close()) ;Producción:
[ { "src": "...", "text": "\n message\n " }, { "src": "...", "text": "\n message\n " } ]