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

使用BeautifulSoup爬取动态网站可预约日期,点击下月不生效如何解决?

问题背景

我需要爬取https://seeksophie.com/options/1-5hr-basic-candle-workshop站点的活动预约信息,核心目标是获取未来1年的所有可预约日期,站点的日期选择器用span元素渲染,可通过notAllowed、flatpickr-disabled两个类属性筛选不可预约日期,后续还需要获取指定日期的可预约时段,当前优先解决日期爬取问题。

我最初的实现思路是循环点击下个月按钮,逐页提取当前页的日期span元素,现有代码如下:

def find_all_span(self, soup):
    new_soup = soup.__copy__()
    all_spans = []
    for i in range(12):
        days_container = new_soup.find_all("div", {"class": "dayContainer"})
        spans = days_container[2].find_all("span")
        all_spans.extend(spans)
        next_month_clicker = self.page_loader.driver.find_element_by_id(
            "js-placeholder-booking-form-accommodation-date")
        self.page_loader.driver.execute_script("arguments[0].click();", next_month_clicker)
        next_month_clicker = self.page_loader.driver.find_elements_by_class_name("flatpickr-next-month")
        self.page_loader.driver.execute_script("arguments[0].click();", next_month_clicker[2])
        page_response = self.page_loader.driver.page_source
        new_soup = BeautifulSoup(page_response, 'html.parser')

        for span in spans:
            print(span["aria-label"])

    return list(set(all_spans))

注:传入的soup是使用HTML Parser解析页面响应得到的BeautifulSoup对象,当前代码仅能获取1个月内的span元素,点击下月按钮后页面源码没有更新,无法获取后续月份的日期。


问题原因
  • 点击下月按钮后未设置等待时间,flatpickr日期组件切换月份存在前端渲染延迟,立刻获取page_source拿到的还是切换前的旧页面内容
  • 循环中重复点击日期输入框唤起日历的操作冗余,可能导致组件状态重置
  • 直接对BeautifulSoup元素用set去重逻辑错误,会丢失元素属性,且无法正确去重

修复方案

1. 现有逻辑优化版

调整点击逻辑,增加显式等待,修改去重规则:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

def find_all_span(self, init_soup):
    all_dates = []
    # 首次唤起日历
    date_input = self.page_loader.driver.find_element_by_id("js-placeholder-booking-form-accommodation-date")
    self.page_loader.driver.execute_script("arguments[0].click();", date_input)
    # 等待日历加载完成
    WebDriverWait(self.page_loader.driver, 10).until(
        EC.presence_of_element_located((By.CLASS_NAME, "dayContainer"))
    )
    
    current_soup = init_soup
    seen_labels = set()
    
    for i in range(12):
        days_container = current_soup.find_all("div", {"class": "dayContainer"})
        spans = days_container[2].find_all("span")
        for span in spans:
            label = span.get("aria-label")
            if label and label not in seen_labels:
                seen_labels.add(label)
                # 保存需要的属性
                all_dates.append({
                    "date": label,
                    "is_disabled": "flatpickr-disabled" in span.get("class", []) or "notAllowed" in span.get("class", [])
                })
                print(label)
        # 点击下个月
        next_btn = self.page_loader.driver.find_elements_by_class_name("flatpickr-next-month")[2]
        self.page_loader.driver.execute_script("arguments[0].click();", next_btn)
        # 等待日历容器更新,判断标准:新的dayContainer加载完成
        WebDriverWait(self.page_loader.driver, 10).until(
            lambda d: len(d.find_elements(By.CLASS_NAME, "dayContainer")) >=3
        )
        # 等待100ms确保渲染完成
        self.page_loader.driver.implicitly_wait(0.1)
        current_soup = BeautifulSoup(self.page_loader.driver.page_source, 'html.parser')
    
    return all_dates

2. 更稳定的实现思路

直接调用前端组件API

该站点使用flatpickr日历组件,所有日期状态都存在组件实例中,无需逐页点击,直接执行JS就能拿到所有数据:

# 直接执行JS获取所有禁用日期和可用日期
disable_dates = self.page_loader.driver.execute_script("""
    const fp = document.querySelector('#js-placeholder-booking-form-accommodation-date')._flatpickr;
    return fp.config.disable;
""")
# 或者直接遍历拿未来1年所有日期的状态
all_date_status = self.page_loader.driver.execute_script("""
    const fp = document.querySelector('#js-placeholder-booking-form-accommodation-date')._flatpickr;
    const result = [];
    const now = new Date();
    const oneYearLater = new Date(now.getFullYear()+1, now.getMonth(), now.getDate());
    for(let d = new Date(now); d <= oneYearLater; d.setDate(d.getDate()+1)){
        const isDisabled = fp.config.disable.some(dis => {
            if(typeof dis === 'function') return dis(d);
            if(dis instanceof Date) return d.toDateString() === dis.toDateString();
            return false;
        }) || fp.isDisabled(d);
        result.push({date: d.toLocaleDateString('en-CA'), is_disabled: isDisabled});
    }
    return result;
""")

抓后端接口

打开浏览器开发者工具的网络面板,筛选XHR请求,选择日期时会有请求拉取对应月份的可预约状态,直接模拟请求接口即可拿到全量数据,比模拟前端操作稳定性更高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.13 14:42:02