使用Selenium爬取Kayak航班页面报错:element is not attached to the page document
解决Selenium中"stale element reference"报错问题
这个报错我太熟悉了,本质是你之前获取的元素引用已经失效——简单说就是这些元素已经从页面的DOM结构里被移除或者更新了,而你的代码还在尝试用旧的引用去操作它们。针对Kayak这个场景,大概率是页面在你循环处理航班列表时发生了动态更新(比如Kayak会自动刷新航班数据、加载更多内容,或者弹出Cookie提示改变了页面结构)。
报错核心原因
- 你提前获取了
list_flights这个元素列表,但在循环过程中,页面DOM发生了变化(比如刷新、动态加载),导致列表里的每个元素都和当前页面"脱节"了,此时再调用flight.find_elements_by_css_selector就会触发这个错误。 - 另一种可能是你没有等待页面完全加载完成就开始操作元素,元素还没稳定就被你获取了引用。
针对性解决办法
我给你调整了代码,结合Kayak的动态特性做了优化:
1. 先导入必要的等待模块
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.common.exceptions import StaleElementReferenceException
2. 修改核心逻辑,避免复用失效元素引用
wd = webdriver.Chrome('chromedriver', chrome_options=chrome_options) link = 'https://www.kayak.it/flights/MIL-BCN/2021-09-01/2021-09-02/?sort=bestflight_a' wd.get(link) import pprint detail_flights = [] # 初始化等待器,最长等待10秒 wait = WebDriverWait(wd, 10) # 先处理Kayak常见的Cookie弹窗(如果有的话),避免弹窗干扰DOM try: cookie_btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button[data-testid='accept']"))) cookie_btn.click() except: pass # 没有弹窗就跳过 # 等待航班列表完全加载(这里的.flight需要替换成你原来获取list_flights的实际选择器) wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".flight"))) # 改用索引循环,每次循环都重新获取最新的航班元素列表 flight_elements = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".flight"))) for i in range(len(flight_elements)): try: # 每次循环都重新获取当前索引的航班元素,确保引用是最新的 current_flight = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".flight")))[i] # 获取航班信息,处理元素不存在的情况 main_info = current_flight.find_element(By.CSS_SELECTOR, ".mainInfo").text if current_flight.find_elements(By.CSS_SELECTOR, ".mainInfo") else "" detail_flights.append({'flights': main_info}) except StaleElementReferenceException: # 遇到失效引用时,重新获取一次元素 current_flight = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".flight")))[i] main_info = current_flight.find_element(By.CSS_SELECTOR, ".mainInfo").text if current_flight.find_elements(By.CSS_SELECTOR, ".mainInfo") else "" detail_flights.append({'flights': main_info}) len(detail_flights) pprint.pprint(detail_flights[0:4])
关键优化点说明
- 避免提前存储元素列表:不要把
list_flights提前存成变量,而是每次循环都重新获取最新的元素集合,确保引用和当前DOM一致。 - 强制等待元素稳定:用
WebDriverWait替代直接操作元素,确保航班列表完全加载后再开始处理,同时处理可能的Cookie弹窗(这是Kayak常见的DOM变化触发点)。 - 捕获异常重试:针对
StaleElementReferenceException添加捕获逻辑,遇到失效引用时重新获取元素,提升代码稳定性。
另外补充一点:Kayak有反爬机制,如果你频繁请求可能会触发页面刷新或者验证,建议在循环中添加随机延迟(比如time.sleep(random.uniform(1,3))),降低被检测的概率。
内容的提问来源于stack exchange,提问作者Box
相关产品推荐
相关产品推荐

