【问题标题】:'page.evaluate()' inside a loop (in Puppeteer) returning same value循环内的“page.evaluate()”(在 Puppeteer 中)返回相同的值
【发布时间】:2020-12-21 13:58:22
【问题描述】:

概述: 这不是实际问题,但我已经简化了问题并将其转换为更易于理解的问题。在循环的每次迭代期间,我都会从 page.evaluate() 中返回相同的数据。我猜是因为closure,我很久没有用JS编程了,所以我无法用async函数实现IIFE。另外,我认为我们不能用async 函数实现forEach 而不是for...if。如果发布的问题太长,我提前道歉,但我想说明一下。

说明: 我正在尝试抓取包含杂货数据的网页。杂货类型(如蔬菜、水果、奶制品、面包店)可以从选择元素(下拉菜单)中选择。选择选项时,杂货清单会根据类型显示。

  • grocery-choice 是选择标签的 id,让我们可以选择选项 (即select#grocery-choice
  • grocery-item 是 div 的类,其中包含杂货店每个单独项目的数据。(即div.grocery-item例如:对于杂货类水果 它包含关于香蕉、苹果等的数据。 每个 div 都有一个<h4> 和两个<p> 元素,分别代表名称价格 和 可用性

我正在遍历数组 ([ 'fruit', 'vegetable', 'bakery']) 中的项目,然后将每个项目作为 JSON 添加到数组 grocery_info 并从 page.evaluate() 返回 grocery_infgrocery_all是返回值被赋值的变量。

这是我遇到问题的代码部分。

async function scrape_and_display(item) {
  await page.select("select#grocery-choice", item);
  await page.waitForSelector("div.grocery-item");
  const grocery_all = await page.evaluate((x) => {
    let grocery_info = [];
    let grocery_list = document.querySelectorAll("div.grocery-item");
    grocery_list.forEach((item) => {
      grocery_info.push({
        name: item.getElementsByTagName("h4")[0].innerText,
        price: item.getElementsByTagName("p")[0].innerText,
        availability: item.getElementsByTagName("p")[1].innerText,
      });
    });
    return grocery_info;
  });
  console.log(grocery_all);
}

for (item of ["fruit", "vegetable", "bakery"]) {
  await scrape_and_display(item);
}

所需输出:

[
  { name: 'Banana', price: '$10 / kg', availability: 'In Stock' },
  { name: 'Apple', price: '$20 / kg', availability: 'In Stock' },
  { name: 'Grape', price: '$45 / kg', availability: 'Out of Stock' },
  { name: 'Orange', price: '$10 / kg', availability: 'In Stock' }
]
[
  { name: 'Brocli', price: '$10 / kg', availability: 'In Stock' },
  { name: 'Pumpkin', price: '$15 / kg', availability: 'In Stock' },
  { name: 'Lettuce', price: '$7 / kg', availability: 'In Stock' }
]
[
  { name: 'Bread', price: '$15 / piece', availability: 'In Stock' },
  { name: 'Apple Pie', price: '$20 / piece', availability: 'In Stock' }
]

电流输出:

[
  { name: 'Bread', price: '$15 / piece', availability: 'In Stock' },
  { name: 'Apple Pie', price: '$20 / piece', availability: 'In Stock' }
]
[
  { name: 'Bread', price: '$15 / piece', availability: 'In Stock' },
  { name: 'Apple Pie', price: '$20 / piece', availability: 'In Stock' }
]
[
  { name: 'Bread', price: '$15 / piece', availability: 'In Stock' },
  { name: 'Apple Pie', price: '$20 / piece', availability: 'In Stock' }
]

这是实际的问题,而不是 杂货类别我有 尼泊尔的州,而不是 杂货项目 em>,我需要学校;而不是记录到控制台,我将其导出为CSV 文件。 如果您检查 CSV 文件,您会发现所有相同的数据,即下拉列表的最后一项,即最后一个选项。 这是工作代码:

const puppeteer = require("puppeteer");
const createCsvWriter = require("csv-writer").createObjectCsvWriter;

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    IgnoreHTTPSErrors: true,
  });
  const page = await browser.newPage();
  await page.setViewport({ width: 1366, height: 820 });

  await page.goto("https://www.pabson.org/#!/School-locator");
  const selectOptions = await page.$$eval(
    "select#ddlState > option",
    (options) => {
      return options.map(function (option) {
        return { option: option.innerHTML, value: option.value };
      });
    }
  );
  async function scrape_and_save(item) {
    // let item = obj;
    await page.select("#ddlState", item.value);
    try {
      await page.waitForSelector("div.count-box-info");
      const school_array = await page.evaluate((x) => {
        let school = [];
        let school_list = document.querySelectorAll("div.count-box-info");
        school_list.forEach((item) => {
          school.push({
            name: item.getElementsByTagName("h4")[0].innerText,
            phone: item.getElementsByTagName("p")[0].innerText,
            email: item.getElementsByTagName("p")[1].innerText,
          });
        });
        return school;
      });

      let csvWriter = createCsvWriter({
        path: "school_" + item.option + ".csv",
        header: [
          { id: "name", title: "NAME" },
          { id: "phone", title: "PHONE" },
          { id: "email", title: "EMAIL" },
        ],
      });

      csvWriter
        .writeRecords(school_array) // returns a promise
        .then(() => {
          console.log("...Done");
        });
    } catch (e) {
      console.log(e);
    }
  }
  for (item of selectOptions) {
    await scrape_and_save(item);
  }
  await browser.close();
})();

我们将不胜感激。提前致谢。

【问题讨论】:

  • 您确定更改select 有效吗?你能用headless: false确认吗?
  • @Vaviloff:感谢您的回复。实际上我已经编辑了帖子,实际代码在最后,它工作正常。所以,是的,select 有效。我只是想创建一个更容易理解的问题,所以我用杂货和水果创建了一个问题。任何帮助将不胜感激。

标签: javascript html node.js closures puppeteer


【解决方案1】:

似乎重新渲染学校列表需要一些时间,而您不必等待这个时间,因此您的代码只是太快地抓取了相同的数据。您可以等待一些更改,例如更改列表上方的主要信息文本:

  async function scrape_and_save(item) {
    const previousData = await page.evaluate(
      () => document.querySelector('#divschoolData').innerText
    );

    await page.select("#ddlState", item.value);
    await page.waitForFunction(
      data => { return data !== document.querySelector('#divschoolData').innerText; },
      {},
      previousData,
    );

    try {
      // ...

顺便说一句,尝试始终添加一个 const/let for 循环变量以防止范围问题:

  for (const item of selectOptions) {

并使用严格模式不要忘记这一点)

【讨论】:

  • 非常感谢,成功了。这实际上并没有出现在我的脑海中,而且我朝着错误的方向寻求解决方案,需要很长时间才能弄清楚。谢谢!!
猜你喜欢
  • 2020-07-20
  • 2019-12-25
  • 2021-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-04
相关资源
最近更新 更多