Puppeteer爬取希尔顿酒店价格失败:日期选择函数失效求助
问题描述
后续问题
我的fill_stayDates函数无法选择日历日期,导致无法输出酒店价格。请问需要修改fill_stayDates辅助函数的哪些部分?该函数之前可用但现在失效了,另外我该如何整合START_YEAR和END_YEAR参数?
新代码
const puppeteer = require("puppeteer"); // ^21.4.1 const constants = {}; constants.START_MONTH = 'Jan' constants.START_DAY = '28' constants.START_YEAR = '2024' // 意识到需要把这个整合到代码里 constants.END_MONTH = 'Feb' constants.END_DAY = '3' constants.END_YEAR = '2024' // 意识到需要把这个整合到代码里 const url = "https://www.hilton.com/en/"; const ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"; let browser; (async () => { browser = await puppeteer.launch({ // * 测试时选`false`可以关闭无头模式 headless: false }); const [page] = await browser.pages(); await page.setUserAgent(ua); await page.setViewport({ width: 1065, height: 10700 }); await page.goto(url, {waitUntil: "domcontentloaded"}); // 填写目的地 await page.type('[class="form-input w-full"]', "Seattle, WA, USA"); // 点击日历UI选择日期 await page.click('[data-testid="search-dates-button"]'); await page.waitForSelector('[data-testid="calendar-container"]'); // ! 这里应该失效了 await fill_stayDates(page, constants); // 点击"查找酒店"按钮 await page.click('[data-testid="search-submit-button"]'); // 获取价格 const sel = 'li[class^="border-border"] p[data-testid="priceInfo"]'; await page.waitForSelector(sel); const prices = await page.$$eval( sel, els => els.map(el => el.textContent.trim()) ); console.log(prices); })() .catch(err => console.error(err)) .finally(() => browser?.close()); //* 辅助函数:点击切换到正确的起始月份 async function fill_stayDates(page, constants) { await page.waitForTimeout(3000); // 测试用延迟 const findMonth = async (targetMonth) => { for (let i = 0; i < 12; i++) { const month = await page.$eval('[id^="calendar-month-"]', (el) => el.textContent); if (month.includes(targetMonth)) break; const arrows = await page.$$( '[data-testid="calendar-container"] > [class="flex justify-between"] button:not([disabled])' ); await arrows[arrows.length - 1].click(); } }; const chooseDay = async (day) => { await page.waitForTimeout(3000); // 测试用延迟 const dayResult = await page.$$eval( 'button[id*=day-] span:not([class*=sr-only])', (elements) => elements.map((element) => element.textContent) ); const dayIndex = dayResult.indexOf(`${day}`); await page.$$('button[id*=day-] span:not([class*=sr-only])').then((el) => el[dayIndex].click()); }; await findMonth(constants.START_MONTH); await chooseDay(constants.START_DAY); await findMonth(constants.END_MONTH); await chooseDay(constants.END_DAY); // 关闭弹窗 await page.click('[class="stroke-text"]'); }
原始问题
我尝试爬取酒店价格,但无法定位价格元素。之前用以下代码能生效,但现在返回空数组[]:
page.$$eval('li[class^="border-border"] p[data-testid="priceInfo"]', elements => elements.map(el => el.textContent.trim()))
const [prices] = await Promise.all([ page.$$eval('li[class^="border-border"] p[data-testid="priceInfo"]', elements => elements.map(el => el.textContent.trim())); ]); console.log('prices:', prices);
解决方案
一、修复fill_stayDates函数的日期选择问题
1. 改进月份定位逻辑,加入年份校验
原函数只判断月份,忽略年份会导致跨年度日期选择出错。修改findMonth函数,同时校验月份缩写和年份:
const findMonth = async (targetMonth, targetYear) => { for (let i = 0; i < 24; i++) { // 扩大循环范围,覆盖跨年度情况 const headerText = await page.$eval('[id^="calendar-month-"]', el => el.textContent.trim()); const [currentMonth, currentYear] = headerText.split(' '); const monthAbbr = currentMonth.slice(0, 3); // 提取月份缩写(如January→Jan) if (monthAbbr === targetMonth && currentYear === targetYear) break; const nextArrow = await page.$('[data-testid="calendar-next-button"]'); if (!nextArrow) break; // 无下一页箭头则停止循环 await nextArrow.click(); await page.waitForTimeout(500); // 等待月份切换完成,替换长延迟 } };
2. 优化日期点击逻辑,避免元素引用失效
原chooseDay函数两次调用page.$$容易导致元素引用过期,改成直接通过文本匹配定位按钮:
const chooseDay = async (day) => { const dayButton = await page.$(`button[id*=day-]:has(span:not([class*=sr-only]):text("${day}"))`); if (dayButton) { await dayButton.click(); await page.waitForTimeout(500); } else { throw new Error(`找不到日期${day}`); } };
3. 整合年份参数调用
修改函数调用处,传入年份参数:
await findMonth(constants.START_MONTH, constants.START_YEAR); await chooseDay(constants.START_DAY); await findMonth(constants.END_MONTH, constants.END_YEAR); await chooseDay(constants.END_DAY);
4. 替换硬编码延迟为元素等待
去掉固定延迟,改用waitForSelector确保元素加载完成,提升稳定性:
async function fill_stayDates(page, constants) { await page.waitForSelector('[id^="calendar-month-"]'); // 等待日历标题加载 const findMonth = async (targetMonth, targetYear) => { // 上面的改进代码 }; const chooseDay = async (day) => { // 上面的改进代码 }; await findMonth(constants.START_MONTH, constants.START_YEAR); await chooseDay(constants.START_DAY); await findMonth(constants.END_MONTH, constants.END_YEAR); await chooseDay(constants.END_DAY); // 关闭弹窗前先确认元素存在 await page.waitForSelector('[class="stroke-text"]'); await page.click('[class="stroke-text"]'); }
二、修复价格元素定位问题
原选择器失效大概率是页面结构更新,改用更稳定的选择器,并等待页面完全加载:
// 替换原价格获取代码 await page.waitForNavigation({ waitUntil: 'networkidle2' }); // 等待搜索结果页面加载完成 const priceSel = '[data-testid="property-card"] [data-testid="priceInfo"]'; await page.waitForSelector(priceSel); const prices = await page.$$eval(priceSel, els => els.map(el => el.textContent.trim())); console.log(prices);
内容的提问来源于stack exchange,提问作者Nelson Mak
相关产品推荐
相关产品推荐

