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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 01:09:57