如何在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的交互元素,营业时间属于独立信息区块,需单独抓取。
修改步骤
- 在餐厅详情页加载完成后,添加针对营业时间元素的等待与抓取逻辑;
- 将抓取到的营业时间存入
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
相关产品推荐
相关产品推荐

