Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据

阿澈
阿澈
发布时间:2026-11-09| 10人浏览过
原创

详细讲解使用Python与Selenium获取JavaScript动态渲染页面数据的完整代码实现、显式等待机制、动态滚动加载处理与生产避坑指南。

在使用传统请求库(如 requests)抓取现代网页时,经常遇到返回的源码里只有基础骨架与脚本标签、而核心数据空空如也的现象。这是因为现代网页的大量内容是由客户端 JavaScript 异步拉取并动态渲染生成的。要稳定抓取此类数据,标准解决路径是借助 Python Selenium 驱动真实的无头浏览器引擎(Chrome Headless),在浏览器内部执行完整的脚本加载与数据挂载,并通过显式等待(WebDriverWait)机制精准截获渲染完毕的 DOM 节点。本文将提供清晰完整的代码实现、核心等待策略与真实交互处理方案。
Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据
图 0:Python Selenium 获取动态渲染页面的核心技术架构概览

一、为什么传统爬虫抓不到:静态请求与 JS 动态渲染的区别

在很多初学者的认知中,爬虫获取数据的逻辑等同于“向目标网址发送 GET 请求,并用正则或选择器提取 HTML”。对于早期的纯服务端渲染网页,这种方式确实行之有效。然而随着单页应用(SPA)与现代前端框架(Vue、React 等)的普及,网页的数据加载机制发生了根本改变。

传统的 requests 仅完成了一次单纯的网络 I/O 操作。服务器返回给 requests 的,只是未经浏览器引擎解析的初始 HTML 文本。在这个初始文本中,往往只存在诸如 <div id="app"></div> 的空容器和若干外部 JavaScript 链接。真正的业务数据,需要浏览器下载并运行这些 JS 脚本,由脚本向后端接口发起异步请求(AJAX/Fetch),再将拿到的 JSON 数据动态转换为 DOM 节点插入页面中。

Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据
图 1:普通 HTTP 请求与 Selenium 驱动真实浏览器渲染的底层对比

因为 requests 库不具备任何 JavaScript 执行能力,所以它永远无法触发后续的数据挂载阶段。而 Selenium 是一个浏览器自动化框架,它通过驱动真实的浏览器(如 Google Chrome),拥有完整的 V8 引擎、网络栈与 DOM 渲染树。浏览器能看到什么内容,Selenium 就能抓取到什么内容。

二、环境准备与免配置 WebDriver

在编写代码前,首先需要安装 Python 的 Selenium 库。从 Selenium 4.6.0 开始,官方内置了 Selenium Manager 工具。这意味着读者不再需要像旧版本那样手动查询 Chrome 浏览器版本,再去繁琐地下载对应版本的 chromedriver 并配置环境变量。只要机器上安装有官方 Chrome 浏览器,Selenium 会自动检测并匹配下载驱动。

# 安装 Selenium 核心库(建议 4.6.0 及以上版本)
pip install selenium

在正式生产运行中,我们通常无需弹出一个可见的浏览器窗口,而是开启无头模式(Headless Mode)。无头模式让浏览器在后台静默运行,不仅节省了操作系统的图形渲染资源,还能显著提升抓取效率与稳定性。

三、核心步骤与完整代码实现

利用 Selenium 获取动态渲染页面的标准操作流程包含五个关键步骤:配置浏览器参数、发起页面导航、设置显式等待、定位并提取 DOM 节点,以及最后安全关闭驱动。

Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据
图 2:Python Selenium 动态数据采集的标准执行五步流程

下面是一份结构完整、带有健壮异常处理和精准显式等待的可运行 Python 示例代码。代码以常见的商品动态列表或新闻列表为场景,展示从页面加载到提取所有动态节点的完整过程:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def scrape_dynamic_page(url: str):
    # 1. 配置 Chrome 运行选项
    options = Options()
    # 启用现代无头模式(Chrome 109+ 推荐新语法)
    options.add_argument("--headless=new")
    # 禁用 GPU 硬件加速,提升容器及服务器环境运行兼容性
    options.add_argument("--disable-gpu")
    # 解决部分 Linux 环境下共享内存不足导致崩溃的问题
    options.add_argument("--no-sandbox")
    options.add_argument("--disable-dev-shm-usage")
    # 伪装常见的桌面浏览器 User-Agent,避免被直接拦截
    options.add_argument(
        "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    )

    # 2. 初始化 WebDriver 实例(Selenium 4 会自动托管驱动匹配)
    driver = webdriver.Chrome(options=options)
    
    try:
        print(f"正在导航至目标页面: {url}")
        driver.get(url)

        # 3. 显式等待:核心防翻车关键
        # 设置最大等待超时时间为 10 秒,每 0.5 秒轮询一次 DOM 树
        # 等待目标动态列表容器(此处以 class="product-item" 为例)渲染并挂载到 DOM 中
        wait = WebDriverWait(driver, timeout=10, poll_frequency=0.5)
        target_locator = (By.CSS_SELECTOR, ".product-item")
        
        # 确认至少有一个目标元素已经出现在页面 DOM 中
        wait.until(EC.presence_of_element_located(target_locator))
        print("JavaScript 渲染完成,目标 DOM 节点已成功挂载!")

        # 4. 获取所有动态渲染完毕的节点
        elements = driver.find_elements(*target_locator)
        results = []
        
        for index, el in enumerate(elements, start=1):
            # 在父节点内继续查询子节点信息
            try:
                title = el.find_element(By.CSS_SELECTOR, ".title").text.strip()
            except Exception:
                title = "暂无标题"

            try:
                price = el.find_element(By.CSS_SELECTOR, ".price").text.strip()
            except Exception:
                price = "暂无价格"

            results.append({"index": index, "title": title, "price": price})

        print(f"共成功抓取到 {len(results)} 条动态渲染数据。")
        return results

    except Exception as e:
        print(f"抓取过程中发生异常: {e}")
        return []

    finally:
        # 5. 安全销毁驱动实例,杜绝后台僵尸 Chrome 进程
        driver.quit()
        print("WebDriver 已安全退出。")

if __name__ == "__main__":
    # 执行测试示例
    target_url = "https://quotes.toscrape.com/js/"
    data = scrape_dynamic_page(target_url)
    for item in data[:3]:
        print(item)
注意:代码中的 finally: driver.quit() 是不可省略的关键防护。若直接使用 driver.close() 或在脚本崩溃时跳过了退出,操作系统后台会残留未关闭的 Chrome 进程与端口占用,长期累积会导致内存耗尽。

四、核心关键:为什么不能直接用 time.sleep?

在抓取动态页面时,最常见的误区是在 driver.get(url) 之后直接写一句 time.sleep(5)。这种“强制休眠”有两大致命缺陷:

  • 极其浪费时间:如果服务器和本地网络状况良好,JavaScript 渲染往往在 0.5 到 1.5 秒内即已完成,剩下几秒完全处于无效停滞状态;在批量抓取成千上万个页面时,累积耗时将十分惊人。
  • 极容易偶发报错:如果遇到网络瞬时波动或接口延迟,渲染花了 5.1 秒,强制休眠结束后代码仍会因为找不到元素直接抛出 NoSuchElementException 崩溃。
Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据
图 3:强制等待、隐式等待与显式等待(WebDriverWait)的时序行为与效率对比

Selenium 提供了两种内置等待策略:隐式等待(implicitly_wait)与显式等待(WebDriverWait):

  • 隐式等待:通过 driver.implicitly_wait(10) 设定全局超时。在查找任何元素时,只要该 HTML 标签出现在 DOM 树中就会立即放行。它的局限在于:现代前端框架很多时候会先生成一个空的占位标签(或骨架屏),数据内容尚未填充进去,隐式等待就会提前放行,导致取出的文本为空。
  • 显式等待:通过 WebDriverWait(driver, 10).until(EC.xxx) 针对具体的目标元素定义更严密的预期条件(Expected Conditions)。例如等待元素不仅存在,而且必须满足“可见”、“包含特定文本”或“可点击”。一旦条件满足便在毫秒级内放行,若超时则抛出清晰的 TimeoutException。
常用预期条件 (Expected Conditions) 判定标准 适用场景
presence_of_element_located 元素已存在于页面 DOM 树中(不要求可见) 仅提取隐藏字段、meta 属性或后台挂载节点
visibility_of_element_located 元素在 DOM 树中存在,且宽和高均大于 0(人眼可见) 提取正文文字、价格、商品列表等常规界面内容
element_to_be_clickable 元素可见且处于可交互、未被禁用的状态 需要点击“下一页”或“展开更多”按钮时
text_to_be_present_in_element 目标元素的 innerText 包含特定字符串 等待异步计算结果或动态加载百分比完成

五、复杂交互场景:滚动懒加载与分页点击

在很多实际网站中,数据并不会在页面初次载入时一次性全部渲染,而是采用了滚动懒加载(Infinite Scroll)机制:只有当用户将滚动条拖动到底部时,浏览器才会触发新的异步请求加载下一批数据卡片。

Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据
图 4:滚动懒加载与分页交互的动态检测与数据提取闭环

面对这种机制,可以通过调用 Selenium 的 driver.execute_script() 注入 JavaScript 代码执行窗口滚动,并监测页面总高度的变化来判定是否已经加载全部内容:

import time

def scroll_to_load_all(driver, max_scrolls=10, pause_time=1.5):
    """
    通过注入 JavaScript 滚动页面,动态触发懒加载数据
    """
    # 获取初始页面滚动总高度
    last_height = driver.execute_script("return document.body.scrollHeight")
    
    for scroll_idx in range(max_scrolls):
        # 执行滚屏至当前最底部
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        # 适当等待新数据包返回并完成 DOM 挂载
        time.sleep(pause_time)
        
        # 获取滚屏后的新总高度
        new_height = driver.execute_script("return document.body.scrollHeight")
        print(f"第 {scroll_idx + 1} 次滚动:旧高度={last_height}, 新高度={new_height}")
        
        # 如果高度没有发生变化,说明已经到达真正的数据底部
        if new_height == last_height:
            print("已触达页面最底部,懒加载全部结束。")
            break
            
        last_height = new_height

如果是通过点击“点击加载更多”按钮加载数据,则应定位该按钮,并在循环中结合 EC.element_to_be_clickable 进行点击,直至按钮消失或变为禁用状态。

六、生产环境避坑与架构选型建议

在实际工程中使用 Selenium 抓取动态数据时,以下几个细节决定了爬虫能否长期稳定运行:

Python Selenium爬虫代码:怎么获取JavaScript动态渲染页面数据
图 5:Selenium 爬虫生产环境工程化避坑与架构选型检查矩阵
  1. 防范 StaleElementReferenceException(陈旧元素引用异常): 在动态渲染页面中,如果某个 DOM 节点在 JavaScript 重新渲染时被替换,之前持有的 WebElement 对象就会失效。解决方法是:不要长久持有元素引用;每次循环提取数据时重新查找定位,或者在页面渲染完成后,直接调用 html = driver.page_source 获取包含完整渲染节点的 HTML 字符串,交由 BeautifulSoup 或 lxml 进行快速静态解析。
  2. 规避 navigator.webdriver 自动化特征检测: 许多现代反爬系统会检查前端对象的 navigator.webdriver 属性。在 ChromeOptions 中可加入参数去除提示:
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    # 初始化后通过 CDP 隐藏标志
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": """
            Object.defineProperty(navigator, 'webdriver', {
                get: () => undefined
            })
        """
    })
  3. 性能与架构选型边界: Selenium 的优势是功能完整、真实度高、能轻松应对复杂的客户端加密和状态计算;但由于调起了完整的浏览器内核,其内存消耗与单页耗时均高于轻量级爬虫。在面对百万级的大规模采集需求时,推荐的工程路径是:先打开浏览器的 F12 开发者工具,在 Network(网络)面板中过滤 Fetch/XHR,观察能否直接找到返回 JSON 的真实后端 API 接口。如果接口有难以逆向的复杂加密或动态滑块验证,再果断采用 Selenium 进行稳健的端到端数据采集。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。