Estoy haciendo un webscraper en javascript (nodo) usando titiritero. Quiero recuperar el texto de un elemento.
El selector se ha copiado y pegado desde las herramientas de desarrollo de Chrome, cuando lanzo titiritero headless:false , el sitio se carga correctamente.
'waitForSelector()' da siempre este mensaje de error: UnhandledPromiseRejectionWarning: TimeoutError: waiting for selector `#petrolTable_data > tr:nth-child(3) > td:nth-child(2)` failed: timeout 30000ms exceeded este es mi código:
const puppeteer = require('puppeteer') async function scrape(){ const browser = await puppeteer.launch({headless:false}) const page = await browser.newPage() await page.goto('https://economie.fgov.be/nl/themas/energie/energieprijzen/maximumprijzen/officieel-tarief-van-de', {waitUntil: 'networkidle2'}) await page.click('#fedconsent > div.orejime-AppContainer > div > div > div > button') //await page.screenshot({ path: 'screenshot.png' }) //#petrolTable_data > tr:nth-child(3) > td:nth-child(2) await page.waitForSelector('#petrolTable_data > tr:nth-child(3) > td:nth-child(2)') let el = await page.$("#petrolTable_data > tr:nth-child(3) > td:nth-child(2)") console.log(el) let text = await el.getProperty('textContent') console.log(text) browser.close() } scrape()Los datos que desea están en un iframe, por lo que primero debe ubicar el marco , luego sumergirse en él y consultar su contenido. Si abre el inspector de elementos, el contenido del iframe se vuelve seleccionable de forma no natural. Asumir que la consola traduce 1:1 con Titiritero es un problema común.
Pero un enfoque más fácil es simplemente navegar directamente a la fuente del marco. Esto es más rápido, requiere menos trabajo y es más confiable, suponiendo que la fuente no cambie.
const puppeteer = require("puppeteer"); // ^15.4.0 let browser; (async () => { browser = await puppeteer.launch(); const [page] = await browser.pages(); const url = "https://petrolprices.economie.fgov.be/petrolprices/?locale=nl"; await page.goto(url, {waitUntil: "domcontentloaded"}); const data = await page.$$eval("tr", els => els.slice(1).map(e => [...e.querySelectorAll("td")] .slice(0, 2).map(e => e.textContent) ) ); console.table(data); })() .catch(err => console.error(err)) .finally(() => browser?.close()) ;Producción:
┌─────────┬──────────────────────────────────────────────┬──────────────────┐ │ (index) │ 0 │ 1 │ ├─────────┼──────────────────────────────────────────────┼──────────────────┤ │ 0 │ 'Benzine 95 RON E10' │ '1,9310 euro/l' │ │ 1 │ 'Benzine 98 RON E5' │ '2,1670 euro/l' │ │ 2 │ 'Diesel B7' │ '2,0790 euro/l' │ │ 3 │ 'Gasolie verwarming 50S (minder dan 2000 l)' │ '1,2923 euro/l' │ │ 4 │ 'Gasolie verwarming 50S (vanaf 2000 l)' │ '1,2605 euro/l' │ └─────────┴──────────────────────────────────────────────┴──────────────────┘Dado que los datos están disponibles estáticamente, aún más fácil y rápido es omitir Puppeteer por completo y usar fetch/cheerio:
const cheerio = require("cheerio"); // 1.0.0-rc.12 fetch("https://petrolprices.economie.fgov.be/petrolprices/?locale=nl") .then(res => res.text()) .then(html => { const $ = cheerio.load(html); const rows = []; $("table tr").slice(1).each(function (i, e) { const row = []; rows.push(row); $(this).find("td").each(function (i, e) { row.push($(this).text().trim()); }); }); console.table(rows); }) ;En mi netbook lento con ambos scripts en el caché, Cheerio tarda 2 segundos frente a los 6 segundos de Puppeteer.
Si no tiene Node 18, instale node-fetch o use Axios.
En términos generales, no soy fanático de los selectores generados por el navegador porque son extremadamente sensibles; si un elemento cambia inesperadamente en la cadena, todo se rompe. Casi siempre hay selectores más robustos que puede elegir. Hay algunos otros antipatrones en su código, por lo que me referiré a una publicación de mi blog para más detalles si tiene curiosidad.