4、动态内容抓取与Selenium:JavaScript渲染页面原理、Selenium环境搭建、元素定位与交互、等待策略

好,咱们进入第四章。这一章聊的东西,说实话,是爬虫工程师从「入门」到「进阶」的一道分水岭。

你想想看,前面几章我们抓的页面,都是老老实实的 HTML。服务器把数据塞进标签里,我们直接拿 requests 去取就行。但现在的网站,尤其是社交媒体,谁还这么干?数据都是通过 JavaScript 动态加载的。你打开页面,看到的只是一个空壳子,真正的数据是 JS 跑起来之后,再通过 Ajax 请求拿到的。

这时候,requests 就彻底歇菜了。它只能拿到那个空壳子。那怎么办?我们需要一个真正的浏览器——Selenium。

JavaScript 渲染页面原理

先聊聊原理。为什么有些页面我们抓不到?

传统的网页,服务器返回完整的 HTML,浏览器直接渲染。这叫「服务端渲染」。但现在的单页应用(SPA),比如微博、Twitter、知乎的动态流,用的是「客户端渲染」。服务器只返回一个 index.html,里面就一个 <div id="app"></div>,然后一堆 JS 文件。浏览器下载完 JS,执行它,JS 再去调接口拿数据,最后把数据塞进那个 div 里。

所以,你用 requests 拿到的,就是那个空 div。数据在哪?在 JS 执行后的内存里。

核心结论: 要拿到动态内容,你必须让 JS 先跑起来。Selenium 就是干这个的——它模拟一个真实的浏览器环境,让 JS 完整执行,然后你再从渲染后的 DOM 里取数据。

我个人习惯把这种抓取方式叫做「浏览器自动化」。说白了,就是让程序像人一样操作浏览器。

Selenium 环境搭建

搭建环境其实不复杂,但有几个坑。我一步步说。

第一步:安装 Selenium 库

pip install selenium

嗯,就这一行。但别急,光装库没用,你还需要一个「浏览器驱动」。

第二步:下载 WebDriver

Selenium 需要通过 WebDriver 来操控浏览器。Chrome 用 ChromeDriver,Firefox 用 GeckoDriver。版本必须和你的浏览器版本匹配。

我记得有一次,项目上线前突然报错,查了半天,原来是 Chrome 自动更新了,但 ChromeDriver 没更新。从那以后,我养成了一个习惯:每次跑 Selenium 之前,先检查浏览器版本和驱动版本是否一致

浏览器 驱动名称 下载地址
Chrome ChromeDriver https://chromedriver.chromium.org/
Firefox GeckoDriver https://github.com/mozilla/geckodriver
Edge EdgeDriver https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/

第三步:启动浏览器

from selenium import webdriver

# 指定驱动路径(如果驱动在 PATH 环境变量中,可以省略)
driver = webdriver.Chrome(executable_path='/path/to/chromedriver')

# 打开一个页面
driver.get('https://example.com')

# 关闭浏览器
driver.quit()

小技巧: 如果你不想每次看到浏览器弹出来,可以开启「无头模式」。这样浏览器在后台运行,不显示界面,节省资源。

from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)

元素定位与交互

浏览器打开了,页面渲染了,接下来就是找到我们想要的数据。Selenium 提供了多种定位方式。

我个人最常用的是 find_element 系列方法。你可以根据 ID、Class Name、XPath、CSS Selector 等来定位。

定位方式 方法 示例
ID find_element(By.ID, 'id') driver.find_element(By.ID, 'username')
Class Name find_element(By.CLASS_NAME, 'class') driver.find_element(By.CLASS_NAME, 'post-content')
XPath find_element(By.XPATH, '//div[@class="content"]') driver.find_element(By.XPATH, '//span[contains(text(),"关注")]')
CSS Selector find_element(By.CSS_SELECTOR, 'div.content > p') driver.find_element(By.CSS_SELECTOR, '.avatar img')

定位到元素之后,你可以做各种操作:点击、输入文字、获取文本、获取属性等。

# 输入文字
search_box = driver.find_element(By.NAME, 'q')
search_box.send_keys('Selenium 教程')

# 点击按钮
submit_btn = driver.find_element(By.ID, 'search-btn')
submit_btn.click()

# 获取文本
title = driver.find_element(By.CSS_SELECTOR, 'h1').text
print(title)

# 获取属性
link = driver.find_element(By.TAG_NAME, 'a').get_attribute('href')

注意: 我曾经踩过一个坑——用 find_element 时,如果页面上有多个匹配的元素,它只返回第一个。如果你想获取所有匹配的元素,要用 find_elements(注意是复数)。

等待策略

这是 Selenium 抓取中最容易出问题的地方。为什么?因为页面是动态加载的。你定位一个元素时,它可能还没出现在 DOM 里。

你想想看,你打开一个社交媒体页面,数据是通过 Ajax 请求加载的。网络有延迟,JS 执行也需要时间。如果你在数据还没加载完就去定位,那肯定报错——NoSuchElementException

怎么解决?等。

Selenium 提供了两种等待方式:

1. 隐式等待

设置一个全局的超时时间。只要在这个时间内找到了元素,就继续执行。如果超时了还没找到,就报错。

driver.implicitly_wait(10)  # 最多等 10 秒

这个设置一次,对整个 driver 生命周期都有效。但有个问题:它只会在 find_element 时等待,不会等待其他条件(比如元素可点击)。

2. 显式等待

这个更灵活。你可以针对某个特定条件进行等待,比如元素可见、可点击、文本出现等。

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 等待直到某个元素可见
element = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, 'my-element'))
)

# 等待直到某个元素可点击
button = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.XPATH, '//button[text()="加载更多"]'))
)
button.click()

我的建议: 在实际项目中,我几乎只用显式等待。隐式等待虽然方便,但不够精确。比如你等一个弹窗出现,用隐式等待可能等不到,因为弹窗是异步加载的。显式等待可以精确控制「等到什么条件满足才继续」。

常用的预期条件(Expected Conditions)包括:

  • presence_of_element_located:元素出现在 DOM 中(不一定可见)
  • visibility_of_element_located:元素可见
  • element_to_be_clickable:元素可点击
  • text_to_be_present_in_element:元素中包含指定文本
  • alert_is_present:弹窗出现

我曾经在抓取微博评论时,遇到一个很头疼的问题:评论是分页加载的,每次点击「加载更多」后,新评论要等 1-3 秒才出现。如果用 time.sleep(3),太慢了;如果用隐式等待,又经常报错。最后我用显式等待,等新评论的容器出现后再继续,效率提升了一倍。

知识体系图

下面这张图,是我自己梳理的本章知识结构。你可以把它当作一个「思维导图」来看。

动态内容抓取与Selenium JS渲染原理 客户端渲染 vs 服务端渲染 Ajax异步数据加载 环境搭建 安装Selenium库 下载WebDriver 无头模式配置 元素定位与交互 ID / Class / XPath / CSS 点击 / 输入 / 获取文本 等待策略 隐式等待 (implicitly_wait) 显式等待 (WebDriverWait)

总结一下: 动态内容抓取的核心就三件事——让浏览器跑起来(Selenium)、找到你要的东西(元素定位)、等它出现(等待策略)。这三件事做好了,大部分动态页面你都能搞定。

嗯,这一章的内容就这些。下一章我们会聊更高级的话题——如何处理反爬虫机制,比如验证码、IP 封禁、请求头伪装等。到时候再细说。


公众号:蓝海资料掘金营,微信deep3321