NodeJS puppeteer -如何从具有多个div的页面中提取内容,这些div具有相同的类

y3bcpkx1  于 2023-04-20  发布在  Node.js
关注(0)|答案(2)|浏览(146)

我有一个简单的 puppet 脚本来抓取一个公告网站。我需要获取页面的内容,在我检查了DOM之后,我可以看到所有的内容对于包含链接和文本的div都有相同的类。我如何用循环获取每个div的内容?
这是一个html结构的页面的例子,大约有25个div具有相同的类,每一个都是一个公告。

<div class="container">
 <div class="item-card bordertop show-in-related-free-list">
<!-- link and text are nested inside here -->
 </div>
</div>

这是我目前拥有的JS代码。我使用headless-recorder-v2 chrome扩展创建了它。

const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
    headless: false,
    slowMo: 300
})
const page = await browser.newPage()
const navigationPromise = page.waitForNavigation()

await page.goto('https://city.example.com/')

await page.setViewport({ width: 1280, height: 607 })

await page.waitForSelector('.bakec > #app > .alert > .btn')
await page.click('.bakec > #app > .alert > .btn')

await page.waitForSelector('.row > .col-md-4:nth-child(1) > .card > .cursor-pointer > .card-title-home')
await page.click('.row > .col-md-4:nth-child(1) > .card > .cursor-pointer > .card-title-home')

await navigationPromise

await page.waitForSelector('#lightbox-vm18 > .modal-dialog > .modal-content > .modal-footer > .btn-primary')
await page.click('#lightbox-vm18 > .modal-dialog > .modal-content > .modal-footer > .btn-primary')

await page.waitForSelector('.bakec > #app > main > .container')
await page.click('.bakec > #app > main > .container')

await page.waitForSelector('#app > main > .container > .item-card:nth-child(3) > .item-container')
// Here I want to loop over announces and store each link and title inside an array

//await page.click('#app > main > .container > .item-card:nth-child(3) > .item-container')

//await navigationPromise

//await browser.close()

更新

我在我的脚本中添加了这几行代码。我可以得到一个包含所需元素的数组,但我如何循环它们,是foreEach循环还是需要使用for循环??

const nodes = await page.$$('.item-heading > .item-title > a')
const announces = []
nodes.forEach( (el) => {
    let href = el.getProperty('href')
    announces.push(href)
})
console.log(announces);

如果我尝试循环nodes变量,就会得到一个这样的数组

[
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }, Promise { <pending> },
  Promise { <pending> }
]
yqyhoc1h

yqyhoc1h1#

您可以使用page.$$(selector)来获取与给定CSS选择器匹配的所有元素。
然后循环遍历元素并检索属性innerHTML以获取每个div的内容(elementHandle.getProperty(propertyName))。

5gfr0r5j

5gfr0r5j2#

el.getProperty返回一个你需要等待的promise。你可以使用console.log(await Promise.all(announces))来并行等待它们,或者编写一个for .. of循环来顺序运行promise。有关详细信息,请参阅Using async/await with a forEach loop
一般来说,除非你需要在数组上调度可信事件,否则避免使用元素句柄,因为它们本质上是活泼的,比evaluate家族调用更难使用。
下面是一个使用page.$$eval从多个元素中获取文本的示例:

const puppeteer = require("puppeteer"); // ^19.7.2

let browser;
(async () => {
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  const url = "https://quotes.toscrape.com";
  await page.goto(url, {waitUntil: "domcontentloaded"});
  const text = await page.$$eval(
    ".quote .text",
    els => els.map(el => el.textContent)
  );
  console.log(text);
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close());

这适用于任何属性。如果您正在查找href s,您可以将.textContent替换为.href.getProperty("href")

const hrefs = await page.$$eval(".tag", els => els.map(el => el.href));

不要忘记waitForSelector,如果元素是在页面加载后由JS添加的。
关于你的代码的更多评论:

  • 这个代码
const navigationPromise = page.waitForNavigation()

goto已经在等待导航了,所以这看起来是多余的,或者可能是有缺陷的。在触发导航的单击旁边设置一个新的导航,而不是在goto之前。await多次执行相同的导航可能并不像你想象的那样--把它们当作一次性的。

  • 避免devtools生成的选择器。
  • 使用finally块关闭浏览器资源,以便在出现错误时进行正确的清理。

相关问题