一、为什么需要 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 的基本执行流程可以理解为:
- 启动浏览器(Chrome / Edge)
- 通过 driver 控制浏览器
- 浏览器加载页面并执行 JS
- Selenium 获取最终 DOM
- 我们再进行解析或操作
也就是说:
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.ID、By.XPATH、By.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。
通常流程:
- 打开页面
- 等待加载完成
- 获取 page_source
- 使用 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()
这段代码的思路是:
- 先配置 Chrome 的启动参数
- 开启
--headless无界面模式 - 再创建浏览器对象
- 正常执行打开网页、截图、关闭等操作
虽然浏览器界面不会显示出来,但它仍然会完整执行页面加载流程。
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. 保存和加载 Cookie
有些网站登录流程比较复杂,直接用代码模拟登录并不稳定。更常见的做法是:
- 第一次手动登录。
- 使用 Selenium 保存 Cookie。
- 后续运行脚本时加载 Cookie。
- 再访问需要登录的页面。
保存 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() 更稳定。