You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js中通过Puppeteer获取餐厅营业时间?

问题

正在开展数据分析项目,需获取西雅图地区餐厅列表。现有Puppeteer代码已能抓取餐厅名称、地址、电话号码及Plus Codes,但无法获取营业时间,怀疑是选择器问题,尝试过手动查找和AI生成的选择器均无效,求修改代码以获取营业时间。

当前代码
import puppeteer from "puppeteer";
import xlsx from "xlsx";

(async () => {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();

  // Set the navigation timeout value
  page.setDefaultNavigationTimeout(60000);

  await page.goto("https://www.google.com/maps/@48.1403077,17.1036957,70m/data=!3m1!1e3?entry=ttu");

  // Set screen size
  await page.setViewport({ width: 1080, height: 1024 });

  // Type into search box
  await page.type("#searchboxinput", "restaurant+seattle");

  // Wait and click on first result
  const searchResultSelector = ".mL3xi";
  await page.waitForSelector(searchResultSelector);
  await page.click(searchResultSelector);

  await page.waitForSelector(".hfpxzc");
  const places = [];

  for (let i = 0; i < 1; i++) {
    console.log(i);

    const element = await page.evaluateHandle(
      (index) => document.querySelectorAll(".hfpxzc")[index],
      i
    );

    if (element) {
      try {
        await element.click();
        await page.waitForNavigation();
        await page.waitForSelector(".CsEnBe");
        await page.waitForTimeout(1500);

        const placeName = await page.evaluate(
          () => document.querySelectorAll(".DUwDvf")[0].innerText
        );

        const existingPlace = places.find((place) => place.name === placeName);

        if (!existingPlace) {
          const items = await page.evaluate(
            () => document.querySelectorAll(".CsEnBe").length
          );

          const info = {};

          info["Name"] = placeName;

          for (let i = 0; i < items; i++) {
            const innerText = await page.evaluate(
              (index) => document.querySelectorAll(".CsEnBe")[index].innerText,
              i
            );

            const tooltip = await page.evaluate(
              (index) =>
                document.querySelectorAll(".CsEnBe")[index].dataset.tooltip,
              i
            );
            // Wait for 1 second before proceeding iteration
            await page.waitForTimeout(1000);
            if (tooltip == "Copy address") {
              info["Address"] = innerText;
            } else if (tooltip == "Open website") {
              info["Website"] = `https://www.${innerText}`;
            } else if (tooltip == "Copy phone number") {
              info["Phone Number"] = innerText;
            } else if (
              tooltip == "Open menu link" ||
              tooltip == "Place an order" ||
              tooltip == "Open reservation link" ||
              tooltip == undefined
            ) {
            } else {
              info[tooltip] = innerText;
            }
          }

          places.push(info);
        }
      } catch (error) {
        console.log(error);
      }

      await page.evaluate(() => {
        const scrollElement = document.querySelectorAll(".ecceSd")[1];
        scrollElement.scrollBy(0, 300);
      });
    } else {
      break;
    }
  }
  console.log(places);
  const wb = xlsx.utils.book_new();
  const ws = xlsx.utils.json_to_sheet(places);
  xlsx.utils.book_append_sheet(wb, ws, "Data");
  xlsx.writeFile(wb, "Places.xlsx");

  await browser.close();
})();
解决方案

Google Maps的营业时间元素不在你当前遍历的.CsEnBe集合中,该类仅包含地址、电话等带tooltip的交互元素,营业时间属于独立信息区块,需单独抓取。

修改步骤

  1. 在餐厅详情页加载完成后,添加针对营业时间元素的等待与抓取逻辑;
  2. 将抓取到的营业时间存入info对象。

完整修改后的代码

import puppeteer from "puppeteer";
import xlsx from "xlsx";

(async () => {
  const browser = await puppeteer.launch({ headless: "new" });
  const page = await browser.newPage();

  // Set the navigation timeout value
  page.setDefaultNavigationTimeout(60000);

  await page.goto("https://www.google.com/maps/@48.1403077,17.1036957,70m/data=!3m1!1e3?entry=ttu");

  // Set screen size
  await page.setViewport({ width: 1080, height: 1024 });

  // Type into search box
  await page.type("#searchboxinput", "restaurant+seattle");

  // Wait and click on first result
  const searchResultSelector = ".mL3xi";
  await page.waitForSelector(searchResultSelector);
  await page.click(searchResultSelector);

  await page.waitForSelector(".hfpxzc");
  const places = [];

  for (let i = 0; i < 1; i++) {
    console.log(i);

    const element = await page.evaluateHandle(
      (index) => document.querySelectorAll(".hfpxzc")[index],
      i
    );

    if (element) {
      try {
        await element.click();
        await page.waitForNavigation();
        await page.waitForSelector(".CsEnBe");
        await page.waitForTimeout(1500);

        const placeName = await page.evaluate(
          () => document.querySelectorAll(".DUwDvf")[0].innerText
        );

        const existingPlace = places.find((place) => place.name === placeName);

        if (!existingPlace) {
          const items = await page.evaluate(
            () => document.querySelectorAll(".CsEnBe").length
          );

          const info = {};

          info["Name"] = placeName;

          for (let i = 0; i < items; i++) {
            const innerText = await page.evaluate(
              (index) => document.querySelectorAll(".CsEnBe")[index].innerText,
              i
            );

            const tooltip = await page.evaluate(
              (index) =>
                document.querySelectorAll(".CsEnBe")[index].dataset.tooltip,
              i
            );
            // Wait for 1 second before proceeding iteration
            await page.waitForTimeout(1000);
            if (tooltip == "Copy address") {
              info["Address"] = innerText;
            } else if (tooltip == "Open website") {
              info["Website"] = `https://www.${innerText}`;
            } else if (tooltip == "Copy phone number") {
              info["Phone Number"] = innerText;
            } else if (
              tooltip == "Open menu link" ||
              tooltip == "Place an order" ||
              tooltip == "Open reservation link" ||
              tooltip == undefined
            ) {
            } else {
              info[tooltip] = innerText;
            }
          }

          // 新增:抓取营业时间
          await page.waitForSelector('.t39EBf .W4Efsd', { timeout: 5000 }).catch(() => {});
          const businessHours = await page.evaluate(() => {
            const hoursContainer = document.querySelector('.t39EBf .W4Efsd');
            if (!hoursContainer) return "无营业时间信息";
            // 将多行格式转为单行,方便Excel展示
            return hoursContainer.innerText.trim().replace(/\n/g, ' | ');
          });
          info["Business Hours"] = businessHours;

          places.push(info);
        }
      } catch (error) {
        console.log(error);
      }

      await page.evaluate(() => {
        const scrollElement = document.querySelectorAll(".ecceSd")[1];
        scrollElement.scrollBy(0, 300);
      });
    } else {
      break;
    }
  }
  console.log(places);
  const wb = xlsx.utils.book_new();
  const ws = xlsx.utils.json_to_sheet(places);
  xlsx.utils.book_append_sheet(wb, ws, "Data");
  xlsx.writeFile(wb, "Places.xlsx");

  await browser.close();
})();

说明

  • 选择器.t39EBf .W4Efsd是当前Google Maps详情页中包裹营业时间的稳定节点;
  • 添加超时容错处理,避免部分商家未填写营业时间导致代码中断;
  • 将多行营业时间转为单行格式,更适合Excel表格展示。

内容的提问来源于stack exchange,提问作者Ddd279

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 05:50:58