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

Selenium脚本非无头模式正常,切换为无头模式失效求助

Booking.com无头模式Selenium脚本失效排查与修复

问题描述

我写了一个Selenium脚本,用来抓取Booking.com上拉斯维加斯华尔道夫酒店的首条价格。正常浏览器模式下完全能用,但切到无头(headless)模式后就跑不通了。核心问题是按下回车键提交搜索和日期后,输入的酒店名称和选择的日期会被清空,导致后续定位不到首个搜索结果元素。试过调整日期选择和搜索的顺序、增加等待时间,都没解决。

原始脚本

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import translateXMLfile
from flask import Flask, jsonify
from selenium.webdriver.chrome.options import Options

class BookingAutomation:
    WINDOW_SIZE = "1200,900"
    HEADLESS = True
    def __init__(self, driver):
        self.driver = driver

    def setup_driver(self):
        
        opts = Options()  # Use webdriver.ChromeOptions()

        if self.HEADLESS:
            opts.add_argument('--headless') #runs a headless search
            
        opts.add_argument("--window-size=%s" % self.WINDOW_SIZE)
        
        # self.driver =webdriver.Chrome()
        self.driver = webdriver.Chrome(opts)

        self.driver.maximize_window()
        self.driver.get('https://www.booking.com') #go to the booking.com url
        time.sleep(2)
        
        close_popup = self.driver.find_element(By.CSS_SELECTOR, "button[aria-label='Dismiss sign-in info.']") #close the popup that pops when you first enter the website
        close_popup.click()
        time.sleep(2)
        

    def select_dates(self):
        
        search = self.driver.find_element(By.ID,':re:') #find the search bar
        search.send_keys(translateXMLfile.name) # enter the waldorf astoria
        
        time.sleep(3)
        

        date = self.driver.find_element(By.CSS_SELECTOR, 'button[data-testid="date-display-field-end"]')
        date.click() #find the calendar and click it

        next_button = self.driver.find_element(By.CSS_SELECTOR,
                                               "button[class='a83ed08757 c21c56c305 f38b6daa18 d691166b09 f671049264 deab83296e f4552b6561 dc72a8413c f073249358']") #go into the next month
        next_button.click() #click the next button

        inday = translateXMLfile.iday #insert the before and after days
        oday = translateXMLfile.oday

        formatted_inday = f"{inday:02d}"
        formatted_oday = f"{oday:02d}"

        date_element_xpath = f"//span[contains(@aria-label, '{formatted_inday} October 2023')]"
        date_element_xpatho = f"//span[contains(@aria-label, '{formatted_oday} October 2023')]"

        checkin_date = self.driver.find_element(By.XPATH, date_element_xpath)
        checkout_date = self.driver.find_element(By.XPATH, date_element_xpatho)

        checkin_date.click()
        checkout_date.click()
        
        search.send_keys(Keys.ENTER) #click enter after entering the search and the date
        
    def search_hotels(self):
        self.driver.save_screenshot('2nd test.png') #this displays the image, somehow deleting what I wrote and the dates I've put in after I click enter

        first_result = self.driver.find_element(By.CSS_SELECTOR, "div[class='c82435a4b8 a178069f51 a6ae3c2b40 a18aeea94d d794b7a0f7 f53e278e95 da89aeb942 c12ee2f811'] div[class='d20f4628d0'] div[class='b978843432'] div[class='aca0ade214 aaf30230d9 cd2e7d62b0 f996d8c258'] div div[class='aca0ade214 c9835feea9 c2931f4182 d79e71457a d14b211b4f'] div[class='aaee4e7cd3 e7a57abb1e'] div[class='aca0ade214 a5f1aae5b2 cd2e7d62b0'] div div[class='f6431b446c a23c043802']")
        
        first_result.click()
        
        time.sleep(2)
        
    def check_prices(self):
        handles = []
        handles = self.driver.window_handles
        if len(handles)>1:
            self.driver.switch_to.window(handles[1])

        time.sleep(10)

        first_price = self.driver.find_element(By.XPATH, "//tr[contains(@class,'js-rt-block-row e2e-hprt-table-row hprt-table-cheapest-block hprt-table-cheapest-block-fix js-hprt-table-cheapest-block')]//div//div//span[@class='prco-valign-middle-helper'][normalize-space()='$2,177']")  # Replace with your XPath
        print(first_price.text)
        time.sleep(5)
        
    def close_driver(self):
        if self.driver:
            self.driver.quit()
if __name__ == "__main__":
    driver = webdriver.Chrome()
    booking_automation = BookingAutomation(driver)
    booking_automation.setup_driver()
    # booking_automation.check_height()
    booking_automation.select_dates()
    booking_automation.search_hotels()
    # booking_automation.check_prices()
    
    booking_automation.close_driver()

问题根源分析

  1. 无头模式被反爬识别:Booking.com会检测无头浏览器特征,触发页面重置或清空表单的逻辑,导致输入内容丢失。
  2. 硬等待不可靠:time.sleep()无法保证页面元素完全加载,无头模式下页面渲染速度与正常模式不同,易导致操作时机错误。
  3. 元素定位器脆弱:依赖长串固定class的CSS选择器易随网站样式更新失效;直接用固定价格文本的XPath不具备通用性。
  4. 回车提交逻辑问题:日期选择完成后搜索框可能失去焦点,回车操作无法正确触发表单提交,反而触发页面其他行为导致内容清空。

修复方案

1. 配置无头模式绕过反爬检测

给ChromeOptions添加参数模拟真实浏览器环境:

def setup_driver(self):
    opts = Options()
    if self.HEADLESS:
        opts.add_argument('--headless=new')  # 新版无头模式更接近真实浏览器
        opts.add_argument('--disable-blink-features=AutomationControlled')  # 移除自动化标识
        opts.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36')  # 设置真实UA
    opts.add_argument("--window-size=%s" % self.WINDOW_SIZE)
    opts.add_experimental_option("excludeSwitches", ["enable-automation"])
    opts.add_experimental_option('useAutomationExtension', False)
    
    self.driver = webdriver.Chrome(opts)
    # 执行JS移除webdriver标识
    self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
    
    self.driver.get('https://www.booking.com')
    # 显式等待关闭弹窗
    WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "button[aria-label='Dismiss sign-in info.']"))
    ).click()

2. 替换硬等待为显式等待

所有元素操作前用WebDriverWait确保元素可交互:

def select_dates(self):
    # 等待搜索框加载并输入酒店名称
    search = WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.ID, ':re:'))
    )
    search.send_keys(translateXMLfile.name)
    # 等待搜索建议出现,确保输入生效
    WebDriverWait(self.driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "div[data-testid='autocomplete-results']"))
    )
    
    # 点击日期选择框
    date_btn = WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[data-testid="date-display-field-end"]'))
    )
    date_btn.click()
    
    # 等待下月按钮可点击并切换月份
    next_button = WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-testid='datepicker-next']"))
    )
    next_button.click()
    
    inday = translateXMLfile.iday
    oday = translateXMLfile.oday
    formatted_inday = f"{inday:02d}"
    formatted_oday = f"{oday:02d}"
    
    # 选择入住日期
    checkin_date = WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.XPATH, f"//span[contains(@aria-label, '{formatted_inday} October 2023')]"))
    )
    checkin_date.click()
    
    # 选择退房日期
    checkout_date = WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.XPATH, f"//span[contains(@aria-label, '{formatted_oday} October 2023')]"))
    )
    checkout_date.click()
    
    # 等待搜索按钮出现并点击,替代回车提交
    search_btn = WebDriverWait(self.driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
    )
    search_btn.click()

3. 优化元素定位器

使用data-testid等稳定属性代替长串class:

def search_hotels(self):
    # 等待首个搜索结果加载完成
    first_result = WebDriverWait(self.driver, 15).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "div[data-testid='property-card'][data-testid='title-link']"))
    )
    first_result.click()
    
    # 等待新窗口打开并切换
    WebDriverWait(self.driver, 10).until(lambda d: len(d.window_handles) > 1)
    self.driver.switch_to.window(self.driver.window_handles[1])

4. 价格定位优化

移除固定价格文本,改用通用定位方式:

def check_prices(self):
    # 等待价格元素加载
    first_price = WebDriverWait(self.driver, 15).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "span.prco-valign-middle-helper"))
    )
    print(f"首个价格:{first_price.text}")

5. 修复初始化逻辑

原代码中__init__传入的driver会被setup_driver覆盖,修改为:

class BookingAutomation:
    WINDOW_SIZE = "1200,900"
    HEADLESS = True
    def __init__(self):
        self.driver = None

    def setup_driver(self):
        # 上述setup_driver代码...

主函数修改为:

if __name__ == "__main__":
    booking_automation = BookingAutomation()
    booking_automation.setup_driver()
    booking_automation.select_dates()
    booking_automation.search_hotels()
    booking_automation.check_prices()
    booking_automation.close_driver()

关键注意事项

  • 定期更新user-agent,避免被识别为爬虫
  • 不要过度频繁请求,添加合理的请求间隔
  • 避免使用固定日期文本,改用动态生成的日期逻辑(比如获取当前月份后推)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 15:49:50