Python爬虫 4.Selenium自动化与动态页面解析


一、为什么需要 Selenium

在前面的章节中,我们已经学习了如何使用 urllib / requests 获取网页内容,并通过 XPath、BeautifulSoup 进行解析。

对于普通的静态页面来说,这种方式已经足够使用。

但是在实际开发中,很多网站的数据并不是直接写在 HTML 中,而是通过 JavaScript 动态加载的。例如:

  • 评论区需要滚动后才加载
  • 商品列表需要点击或翻页才出现
  • 页面内容由前端框架(Vue / React)渲染

这就会导致一个问题:

我们通过 requests 获取到的 HTML,并不是最终展示给用户的内容。

此时,就需要一种能够“模拟真实浏览器行为”的工具。

Selenium 的作用就是:

通过代码控制浏览器,让页面完整加载后,再获取最终内容。

补充说明:为什么 requests 无法处理动态页面

# 使用 requests 获取页面
import requests

response = requests.get('https://quotes.toscrape.com/js/')

# 输出内容(注意这里没有动态数据)
print(response.text[:200])

说明:

  • requests 只能获取“原始 HTML”
  • JavaScript 执行后的内容不会包含在 response 中

而 Selenium 的优势在于:

  • 会真正打开浏览器
  • 执行页面中的 JS
  • 再获取最终渲染结果

补充:动态页面的三种常见来源

  • 接口加载(XHR / Fetch):数据通过接口返回 JSON,再由前端渲染
  • 懒加载:滚动或点击后才加载更多内容
  • 前端渲染(Vue / React):HTML 初始很少,内容由 JS 生成

判断方法:

  • 关闭 JS(浏览器插件)后页面是否无内容
  • 打开 Network 面板查看是否有接口请求(XHR)

二、什么是 Selenium

Selenium 是一个浏览器自动化工具,可以模拟用户操作,例如:

  • 打开网页
  • 输入内容
  • 点击按钮
  • 滚动页面

它的核心能力是:

获取“浏览器渲染后的页面内容”,而不是原始 HTML。这也是它能处理动态页面的根本原因。

Selenium 有两个很重要的特点:

  • 它通常需要配合不同浏览器的 driver 使用,例如 ChromeDriver、EdgeDriver、GeckoDriver
  • 它除了可以控制有界面的真实浏览器,也支持无界面(headless)模式运行

这也是 Selenium 在测试、爬虫、自动化脚本中都很常见的原因。

Selenium 工作流程(理解很重要)

Selenium 的基本执行流程可以理解为:

  1. 启动浏览器(Chrome / Edge)
  2. 通过 driver 控制浏览器
  3. 浏览器加载页面并执行 JS
  4. Selenium 获取最终 DOM
  5. 我们再进行解析或操作

也就是说:

Selenium = 浏览器 + 自动化控制

补充:Selenium 与 requests 的选择策略

  • 能通过接口获取数据(JSON) → 优先 requests
  • 页面必须渲染后才能获取 → 使用 Selenium
  • 需要模拟用户行为(点击/拖动/登录) → 使用 Selenium

三、环境准备(重要)

在使用 Selenium 之前,需要准备浏览器驱动。

1. 安装 Selenium

pip install selenium

2. 下载 ChromeDriver

需要下载与你本地 Chrome 浏览器版本对应的 ChromeDriver。

下载地址:
https://chromedriver.chromium.org/

3. 配置驱动

# 导入 webdriver
from selenium import webdriver

# 指定 driver 路径
driver = webdriver.Chrome('/path/to/chromedriver')

或者(推荐):

# 自动查找驱动(需放入环境变量)
driver = webdriver.Chrome()

常见问题

  • 版本不匹配:Chrome 与 ChromeDriver 版本需一致
  • Mac 权限问题:需要给驱动执行权限(chmod +x chromedriver)
  • 驱动路径错误:确认路径或加入环境变量

四、元素定位

在 Selenium 中,我们需要先找到页面元素,才能进行操作。

元素定位的核心概念

在 Selenium 中,所有操作都必须基于“元素”。

例如:

  • 在搜索框输入内容
  • 点击按钮
  • 获取文本

这些操作的前提都是:先找到对应的页面元素。

因此,元素定位是 Selenium 中最核心的能力之一。

常见定位方式包括:

  • ID:最推荐,通常唯一
  • class:可能不唯一
  • 标签名:通常不精准
  • XPath:最灵活
  • CSS 选择器:语法简洁

8. find_element 与 find_elements 的区别

这两个方法非常容易混淆。

find_element

# 查找单个元素
element = driver.find_element(By.ID, 'kw')

特点:

  • 返回第一个匹配到的元素
  • 如果找不到,会抛出异常

find_elements

# 查找多个元素
elements = driver.find_elements(By.TAG_NAME, 'input')
print(elements)

特点:

  • 返回一个列表
  • 如果找不到,返回空列表

使用建议:

  • 只需要一个元素 → 使用 find_element
  • 需要多个元素 → 使用 find_elements

9. 定位写法

# 通过 id 定位元素
button = driver.find_element_by_id('su')

# 通过 name 定位元素
name = driver.find_element_by_name('wd')

# 通过 xpath 定位元素
xpath_items = driver.find_elements_by_xpath('//input[@id="su"]')

# 通过标签名定位元素
names = driver.find_elements_by_tag_name('input')

# 通过 CSS 选择器定位元素
my_input = driver.find_elements_by_css_selector('#kw')[0]

# 通过链接文本定位元素
news_link = driver.find_element_by_link_text('新闻')

这些写法在旧版本 Selenium 中非常常见,阅读旧资料时经常会遇到。

不过在现在的写法中,更推荐统一使用 By.IDBy.XPATHBy.CSS_SELECTOR 这类方式,因为结构更统一,也更容易维护。

补充:推荐使用统一写法

from selenium.webdriver.common.by import By

# 推荐写法(更规范)
input_box = driver.find_element(By.ID, 'kw')

优点:

  • 结构统一
  • 可读性更好
  • 更符合新版 Selenium 规范

定位技巧(实战建议)

  • 优先使用 ID(稳定且唯一)
  • 其次使用 CSS(简洁)
  • XPath 用于复杂结构(如父子关系)
  • 避免使用绝对 XPath(易失效)

五、常见操作

1. 输入内容:send_keys()

input_box.send_keys('Python')

作用:模拟键盘输入。

常见场景:

  • 搜索框输入关键词
  • 登录输入账号密码

2. 点击元素:click()

button.click()

作用:模拟鼠标点击。

常见场景:

  • 点击搜索按钮
  • 点击登录按钮
  • 点击下一页

3. 清空输入:clear()

input_box.clear()

作用:清空输入框内容。


4. 获取元素文本

text = element.text
print(text)

用于获取页面上展示的文本内容。


5. 获取元素属性

value = element.get_attribute('href')
print(value)

用于获取标签属性,例如:

  • href
  • src
  • class

6. 获取元素信息

除了获取属性之外,还可以直接访问一些常见元素信息。

# 获取元素文本
print(element.text)

# 获取标签名称
print(element.tag_name)

# 获取元素的 class 属性
print(element.get_attribute('class'))

这些信息在调试页面结构、提取页面数据时都非常常用。


7. 页面前进与后退

Selenium 还可以模拟浏览器的前进和后退操作。

# 后退到上一页
driver.back()

# 前进到下一页
driver.forward()

适合场景:

  • 页面跳转后返回上一页继续抓取
  • 模拟用户浏览行为

8. 执行 JavaScript

在某些页面中,普通点击和查找还不够,这时可以直接执行 JavaScript 代码。

# 定义滚动页面的 JavaScript 代码
js = 'document.documentElement.scrollTop = 100000'

# 执行 JavaScript
driver.execute_script(js)

这类写法常用于:

  • 向下滚动页面
  • 触发懒加载内容
  • 执行页面中的某些脚本逻辑

9. 保存截图

在调试 Selenium 脚本时,截图功能非常有用。

# 保存当前页面截图
driver.save_screenshot('page.png')

它可以帮助我们:

  • 确认页面是否真的打开成功
  • 检查元素是否正确显示
  • 观察无界面模式下页面的实际效果

10. 页面滚动加载(常见)

# 滚动到底部(触发懒加载)
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

适用于:

  • 无限滚动页面
  • 评论加载

11. 键盘操作(回车等)

from selenium.webdriver.common.keys import Keys

# 输入并回车
input_box.send_keys('Python')
input_box.send_keys(Keys.ENTER)

六、等待机制(重要)

由于页面是动态加载的,元素不一定会立刻出现。

如果在元素尚未加载完成时就执行操作,程序可能会报错。

因此需要“等待机制”。

1. 隐式等待

driver.implicitly_wait(10)

含义:在查找元素时,最多等待 10 秒。


2. 显式等待(推荐)

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, 'kw'))
)

作用:等待某个条件成立再继续执行。

隐式等待 vs 显式等待

  • 隐式等待:全局生效,但不够精确
  • 显式等待:针对具体元素,更推荐

常见等待条件

# 元素可点击
EC.element_to_be_clickable((By.ID, 'kw'))

# 元素可见
EC.visibility_of_element_located((By.ID, 'kw'))

建议:优先使用显式等待,并针对关键元素设置


七、获取动态页面数据

html = driver.page_source
print(html)

说明:

获取浏览器渲染后的 HTML。

通常流程:

  1. 打开页面
  2. 等待加载完成
  3. 获取 page_source
  4. 使用 XPath 或 BeautifulSoup 解析

推荐流程(实战)

# 打开页面
driver.get(url)

# 等待关键元素出现
WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.CLASS_NAME, 'quote'))
)

# 再获取页面源码
html = driver.page_source

提示:结合解析工具

获取到 html 后,可以继续:

from lxml import etree

# 构建解析对象
dom = etree.HTML(html)

# 使用 XPath 提取
titles = dom.xpath('//title/text()')
print(titles)

八、简单实战案例

from selenium import webdriver
from selenium.webdriver.common.by import By

# 创建浏览器对象
driver = webdriver.Chrome()

# 打开页面
driver.get('https://www.baidu.com')

# 找到输入框
input_box = driver.find_element(By.ID, 'kw')

# 输入内容
input_box.send_keys('Python')

# 找到按钮
button = driver.find_element(By.ID, 'su')

# 点击搜索
button.click()

# 获取页面源码
html = driver.page_source
print(html)

# 关闭浏览器
driver.quit()

优化版(推荐写法)

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

# 创建浏览器
driver = webdriver.Chrome()

# 打开页面
driver.get('https://www.baidu.com')

# 显式等待输入框出现
WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, 'kw'))
)

# 输入内容
driver.find_element(By.ID, 'kw').send_keys('Python')

# 点击按钮
driver.find_element(By.ID, 'su').click()

# 获取结果页面
print(driver.page_source[:200])

# 关闭浏览器
driver.quit()

扩展:抓取搜索结果标题

# 等待结果出现
WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'h3'))
)

# 获取所有标题
items = driver.find_elements(By.CSS_SELECTOR, 'h3')

for item in items:
    print(item.text)

九、适用场景总结

适合使用 Selenium 的场景:

  • JavaScript 渲染页面
  • 登录后页面
  • 需要交互的页面

不适合使用的场景:

  • 静态页面(建议使用 requests)

补充建议:

优先级选择:

  • requests 能解决 → 优先用 requests
  • 必须 JS 渲染 → 使用 Selenium

性能建议

  • Selenium 较慢(真实浏览器)
  • 大规模抓取建议:Selenium + 接口分析结合使用

十、补充:无界面模式与历史方案

所谓无界面运行,就是:

浏览器仍然在执行,但不会弹出可视化窗口。

这样做的好处是:

  • 节省界面渲染开销
  • 适合服务器环境
  • 适合批量自动化任务

1. Chrome Headless 模式

# 导入 webdriver
from selenium import webdriver
# 导入 Chrome 配置项
from selenium.webdriver.chrome.options import Options

# 创建配置对象
chrome_options = Options()

# 开启无界面模式
chrome_options.add_argument('--headless')

# 禁用 GPU
chrome_options.add_argument('--disable-gpu')

# 创建浏览器对象
browser = webdriver.Chrome(options=chrome_options)

# 打开网页
browser.get('http://www.baidu.com/')

# 保存截图
browser.save_screenshot('headless_baidu.png')

# 关闭浏览器
browser.quit()

这段代码的思路是:

  1. 先配置 Chrome 的启动参数
  2. 开启 --headless 无界面模式
  3. 再创建浏览器对象
  4. 正常执行打开网页、截图、关闭等操作

虽然浏览器界面不会显示出来,但它仍然会完整执行页面加载流程。


2. PhantomJS(历史了解)

在更早期的一些 Selenium 教程中,还会看到 PhantomJS

它曾经是一种无界面浏览器,特点是:

  • 不显示真实浏览器窗口
  • 支持查找元素、执行 JavaScript
  • 运行速度相对较快

旧资料中的典型写法类似下面这样:

# 导入 webdriver
from selenium import webdriver

# 指定 PhantomJS 驱动路径(旧教程常见写法)
path = 'phantomjs.exe'

# 创建 PhantomJS 浏览器对象
browser = webdriver.PhantomJS(path)

# 打开网页
browser.get('http://www.baidu.com/')

# 保存截图
browser.save_screenshot('baidu.png')

# 关闭浏览器
browser.quit()

不过从当前实践角度看,PhantomJS 更多是历史知识,了解即可。实际开发中,更常见的方案还是 Chrome Headless 或其他现代浏览器的无界面模式。

Headless 常见参数

chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')

适用于:

  • Linux 服务器环境
  • Docker 环境

十一、实践补充:登录态、文件上传与元素稳定性

Selenium 在爬虫中不只用于“打开页面获取源码”,还经常用于处理登录态、表单填写、文件上传等浏览器交互场景。

有些网站登录流程比较复杂,直接用代码模拟登录并不稳定。更常见的做法是:

  1. 第一次手动登录。
  2. 使用 Selenium 保存 Cookie。
  3. 后续运行脚本时加载 Cookie。
  4. 再访问需要登录的页面。

保存 Cookie:

import json
import time
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://example.com')

# 预留时间手动登录
time.sleep(60)

with open('cookies.json', 'w', encoding='utf-8') as f:
    json.dump(driver.get_cookies(), f, ensure_ascii=False)

driver.quit()

加载 Cookie:

import json
from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://example.com')

driver.delete_all_cookies()

with open('cookies.json', 'r', encoding='utf-8') as f:
    cookies = json.load(f)

for cookie in cookies:
    driver.add_cookie(cookie)

driver.get('https://example.com/user')

注意:Cookie 文件属于敏感数据,不应该提交到代码仓库,也不应该直接写进博客。

2. 文件上传

如果页面中存在文件上传框,通常不需要模拟鼠标点击系统文件选择窗口,可以直接给 input[type=file] 赋值。

file_input = driver.find_element('xpath', '//input[@type="file"]')
file_input.send_keys('/tmp/test.mp4')

这种方式比模拟点击更稳定,因为系统文件选择窗口不属于网页 DOM,Selenium 不适合直接控制它。

3. 元素定位的稳定性

实际页面中,很多元素的 class 名会动态变化。定位时不要只依赖很长的 class 路径,可以优先考虑:

# 根据 placeholder 定位
driver.find_element('xpath', '//input[@placeholder="请输入标题"]')

# 根据文本定位
driver.find_element('xpath', '//*[text()="确定"]')

# 根据部分属性定位
driver.find_element('xpath', '//input[contains(@accept, "mp4")]')

如果页面加载较慢,建议配合显式等待:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
element = wait.until(
    EC.presence_of_element_located((By.XPATH, '//input[@type="file"]'))
)

这类写法比固定 time.sleep() 更稳定。


文章作者: hnbian
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hnbian !
评论
  目录