You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

优化存在页面加载时间不一致问题的Selenium网页爬虫

优化存在页面加载时间不一致问题的Selenium网页爬虫

我太懂你这种闹心的情况了——CloudFront防护+动态渲染的页面本来就是爬虫的硬骨头,尤其是Selenium这种全浏览器渲染工具,动不动就因为反爬校验、资源加载波动导致速度忽快忽慢。咱们一步步拆解问题,看看怎么把稳定性提上来。


一、先定位可能的性能瓶颈

结合你遇到的driver.get()卡顿、等待超时、总时长波动这几个问题,核心原因大概率是这几个:

  • CloudFront的动态校验:哪怕你改了UA、禁用了自动化标志,CloudFront有时候还是会触发隐形的JS挑战(比如指纹校验),这会让页面加载突然卡10-30秒。
  • 不必要的资源拖慢加载:Selenium默认会加载页面所有资源(图片、广告、第三方脚本),这些资源的加载速度完全不受控,直接导致driver.get()时长波动。
  • 等待逻辑的盲区:你用presence_of_element_located只判断了表格DOM存在,但有时候表格框架出来了,里面的内容还在异步加载,这时候后续解析也会出问题;或者CloudFront的校验JS还在跑,页面处于假加载状态,导致等待超时。
  • 固定范围随机延迟的双刃剑:1.5-4秒的延迟看似安全,但如果CloudFront已经盯上你,这种规律化的延迟反而可能被识别,而且会叠加页面本身的加载慢问题。

二、结构改进:减少阻塞和无效等待

针对你的脚本,我整理了几个立竿见影的优化点:

1. 给Chrome“瘦个身”:禁用不必要的资源加载

直接修改create_driver函数的Chrome选项,砍掉所有非必需的资源加载,能大幅降低driver.get()的波动:

def create_driver():
    options = Options()
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    
    # 新无头模式:更接近真实浏览器,比旧无头模式难被检测
    options.add_argument('--headless=new')
    # 禁用所有非必需资源
    options.add_argument('--disable-images')
    options.add_argument('--disable-css')
    options.add_argument('--disable-plugins')
    options.add_argument('--disable-extensions')
    options.add_argument('--disable-gpu')
    options.add_argument('--blink-settings=imagesEnabled=false')  # 双重禁用图片
    # 页面加载策略改为eager:DOM加载完成就返回,不用等所有资源加载完
    options.page_load_strategy = 'eager'

    # UA池:每次随机选一个真实UA,比固定UA更安全
    ua_list = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
    ]
    options.add_argument(f"user-agent={random.choice(ua_list)}")

    # 自动化特征隐藏
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option("useAutomationExtension", False)

    driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
    driver.set_page_load_timeout(20)  # 把超时时间从30秒缩到20,避免无意义等待
    driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
    return driver

2. 改进等待逻辑:从“等DOM存在”到“等内容加载完成”

原来的presence_of_element_located太宽松了,改成等表格有真实数据行,避免空表格导致的无效解析:

# 替换原来的WebDriverWait代码
try:
    # 等待表格至少有3行(表头+2行数据),确保内容加载完成
    WebDriverWait(driver, 15).until(
        lambda d: len(d.find_elements(By.CSS_SELECTOR, "#example tr")) > 2
    )
    # 加个0.5秒小延迟,确保异步内容完全渲染
    time.sleep(0.5)
except TimeoutException:
    print(f"❌ [{tc_code}] 表格内容加载超时")
    return []

3. 加重试机制:应对临时的CloudFront波动

偶尔的超时或403可能是CloudFront的临时校验,给crawl_vote_table加个重试逻辑,不用直接放弃:

def crawl_vote_table(driver, tc_code, url, max_retries=2):
    for retry in range(max_retries + 1):
        print(f" [{tc_code}] 检查中... (第{retry+1}次尝试)")
        # 随机延迟范围放大一点,避免规律化
        time.sleep(random.uniform(1.0, 5.0))

        try:
            driver.get(url)
            html = driver.page_source
            
            if is_cloudfront_403(html):
                print(f" [{tc_code}] CloudFront 403拦截 (第{retry+1}次尝试)")
                if retry == max_retries:
                    return None
                continue

            # 等待表格加载完成(用上面改进后的等待逻辑)
            WebDriverWait(driver, 15).until(
                lambda d: len(d.find_elements(By.CSS_SELECTOR, "#example tr")) > 2
            )
            time.sleep(0.5)

            soup = BeautifulSoup(driver.page_source, 'html.parser')
            table = soup.find('table', {'id': 'example'})
            if not table:
                return []

            # 后面的解析逻辑和你原来的一致...
            return results

        except TimeoutException:
            print(f"❌ [{tc_code}] 页面加载超时 (第{retry+1}次尝试)")
            if retry == max_retries:
                return []
        except WebDriverException as e:
            print(f"❌ [{tc_code}] 连接失败 (第{retry+1}次尝试): {str(e)}")
            if retry == max_retries:
                return None

三、要不要彻底换掉Selenium?

这得看你能不能挖到页面的隐藏API:

  • 优先找API:你的目标页面是Oracle APEX框架(URL里的f?p=103:26是典型特征),这类页面的表格数据通常是通过AJAX请求获取的。打开浏览器DevTools的Network标签,刷新页面,找带FSP_ORG_ID/FSP_LANG_ID参数的XHR/Fetch请求,大概率能直接拿到JSON格式的表格数据。如果能拿到API,用requests直接请求比Selenium快5-10倍,而且完全避开CloudFront的浏览器指纹校验。
  • 实在找不到API再用Selenium:如果API有复杂的签名或加密,那只能继续用Selenium,但一定要按上面的优化点把浏览器缩到最轻量,减少波动。

最后小提醒

建议你先花10分钟抓包看看有没有隐藏API,这是性价比最高的优化。如果必须用Selenium,按上面的调整后,应该能把总时长波动控制在1分钟以内,超时和卡顿的概率也会大幅降低。要是还有问题,咱们再进一步调试CloudFront的指纹特征~

火山引擎 最新活动