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

