Selenium/Python循环点击加载更多爬取香水数据超时问题
问题说明
爬取Fragrantica搜索页(https://www.fragrantica.com/search/)全量香水数据时,Selenium自动化脚本运行中断:站点共收录约73367款香水,搜索页默认仅展示30款,每次点击show more results按钮追加加载30款,累计需点击约2444次才能加载完全部内容。初始编写的Python脚本逻辑为循环判断加载更多按钮可点击状态,可点击则执行点击、同时统计已加载香水数量,但实际运行最多完成34次循环,就会在等待按钮变为可点击状态时抛出TimeoutException超时错误导致程序终止,需要调整代码实现持续加载直到无更多内容,最终获取全量香水数据。
故障根因
固定跑34次就触发超时是几个问题叠加导致的:
- 站点有隐性频率限制,连续无间隔快速点击30次左右后,后端接口响应速度会明显变慢,初始代码设置的固定短等待时长(通常默认10秒)不足以等待接口返回,直接触发超时
- 点击后没有校验新内容是否加载完成就进入下一轮循环,有时候按钮还处于上一次加载的禁用状态,直接判断可点击状态必然失败
- 加载上千条内容后页面DOM节点数暴涨(全量加载时节点数轻松突破几十万),Selenium定位元素的速度会持续下降,固定等待时长没有适配这种性能衰减
- 每次新内容加载完成后,“加载更多”按钮会被销毁重新渲染,如果提前缓存了按钮元素对象,会拿到失效的过期元素引用,触发元素不存在的异常
代码调整方案
核心优化逻辑:
- 每次点击前重新查找按钮元素,避免过期元素引用问题
- 点击后等待已加载香水条目数实际增长,确认本轮加载完成再进入下一轮
- 加入随机操作间隔模拟真人浏览行为,避开站点限流规则
- 动态调整等待时长,适配DOM节点变多后的定位延迟
- 加入异常重试机制,连续重试后条目数不再增长才判定为加载到末尾
可直接复用的调整后代码:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, ElementClickInterceptedException, StaleElementReferenceException import time import random # 初始化浏览器,提前匹配好本地浏览器对应版本的驱动 driver = webdriver.Chrome() driver.maximize_window() driver.get("https://www.fragrantica.com/search/") # 首次进入先手动处理cookie弹窗,避免遮挡按钮 try: accept_cookie_btn = WebDriverWait(driver, 15).until( EC.element_to_be_clickable((By.CSS_SELECTOR, "button[class*='accept-cookies']")) ) accept_cookie_btn.click() except: pass # 运行参数 BASE_WAIT = 10 MAX_RETRY = 3 current_count = 0 retry_count = 0 while True: try: # 统计当前已加载的香水数量 perfume_items = driver.find_elements(By.CSS_SELECTOR, ".card-section .fr-card") current_count = len(perfume_items) print(f"当前已加载香水数量:{current_count}") # 加载条目超过1000条后拉长等待时长,适配DOM性能下降 wait_dur = BASE_WAIT if current_count < 1000 else BASE_WAIT * 2 wait = WebDriverWait(driver, wait_dur) # 每次重新查找按钮,不缓存元素对象,避免stale元素问题 load_more_btn = wait.until( EC.element_to_be_clickable((By.CSS_SELECTOR, "button[class*='load-more']")) ) # 随机等待0.5-2秒再操作,模拟真人浏览间隔 time.sleep(random.uniform(0.5, 2)) # 滚动到按钮位置,避免元素不在视口导致点击失败 driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", load_more_btn) # 用JS触发点击,避免浮动元素遮挡导致点击无效 driver.execute_script("arguments[0].click();", load_more_btn) # 等待香水条目数实际增长,确认本轮加载完成 wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, ".card-section .fr-card")) > current_count) retry_count = 0 # 加载成功重置重试计数 except (TimeoutException, ElementClickInterceptedException, StaleElementReferenceException) as e: retry_count += 1 print(f"加载异常,第{retry_count}次重试,错误:{str(e)[:60]}") if retry_count >= MAX_RETRY: # 最终校验条目数是否变化,判断是否真的到了列表末尾 final_count = len(driver.find_elements(By.CSS_SELECTOR, ".card-section .fr-card")) if final_count == current_count: print(f"全量加载完成,总香水数量:{final_count}") break else: # 条目数仍在增长说明只是临时卡顿,重置重试计数 retry_count = 0 time.sleep(random.uniform(3, 5)) else: # 重试前随机等待2-4秒,给接口留足响应时间 time.sleep(random.uniform(2, 4)) # 后续在这里写页面数据提取逻辑 # ...... driver.quit()
运行注意事项
- 不要用无头模式跑,站点对无头浏览器检测严格,很容易直接拦截请求,正常开可视化窗口跑即可
- 全量加载时浏览器内存占用会达到几个G,跑的时候关掉其他占内存的程序,避免浏览器崩溃
- 如果连续加载上千条后出现长时间加载不出来的情况,暂停5-10分钟再跑,或者切换代理IP,不要硬刷避免IP被封
- 如果选择器定位不到按钮,可以在页面加载后手动检查下按钮的class属性,微调CSS选择器即可
内容的提问来源于stack exchange,提问作者Armonia
相关产品推荐
相关产品推荐

