四、爬虫自动化------Selenium
1、Selenium基础知识与元素定位
1.1 Selenium介绍
在之前的文章中我们能够深刻学习到使用 request 库进行网页爬取。requests 只能获取服务器返回的 HTML 源码,但是现在网页越来越复杂,很多东西都是通过 JavaScript 进行动态加载。
- Ajax异步加载:微博、知乎的内容都是滚动自动加载更多
- SPA单页应用:整个应用在一个页面,切换页面不刷新浏览器
- 动态渲染:搜索结果、评论列表等需要JS执行后才能够显示
这时候,requests就无法获取到这些动态加载的内容了。因此需要一个能够执行JS的工具库------Selenium。
1.2 Selenium工作原理
Selenium整体架构图示:

工作流程分解:
- Python脚本通过WebDriver协议与浏览器驱动通信
- 浏览器驱动解析命令并且控制浏览器
- 浏览器执行实际操作
- 结果通过驱动返回给Python脚本
1.3 安装与配置Selenium
安装Python库:
💡pip install selenium
下载浏览器驱动:
Selenium版本使用的是W3C WebDriver协议,每个浏览器都需要对应的驱动:
|---------|-------------------------------------------------|
| 浏览器 | 驱动下载 |
| Chrome | https://chromedriver.chromium.org/downloads |
| Firefox | https://github.com/mozilla/geckodriver/releases |
| Edge | https://msedgedriver.azureedge.net/ |
放置驱动:
- 将驱动放到系统PATH环境变量所在目录
- 在代码中指定驱动路径
1.4 8中元素点位方式
python
from selenium import webdriver
from selenium.webdriver.common.by import By # 定位方式在这个类里
# 方式1:通过ID定位(最推荐,有唯一性)
driver.find_element(By.ID, 'kw') # 查找 id="kw" 的元素
# 方式2:通过NAME属性定位
driver.find_element(By.NAME, 'wd') # 查找 name="wd" 的元素
# 方式3:通过XPATH定位(最强大,可写任意条件)
driver.find_element(By.XPATH, '//input[@id="kw"]') # 相对路径
driver.find_element(By.XPATH, '//div[@class="wrapper"]//input') # 支持层级
# 方式4:通过CSS选择器定位(也很强大)
driver.find_element(By.CSS_SELECTOR, '#kw') # ID选择器
driver.find_element(By.CSS_SELECTOR, '.s_ipt') # 类选择器
driver.find_element(By.CSS_SELECTOR, 'input[name="wd"]') # 属性选择器
# 方式5:通过CLASS_NAME定位(注意:class有多个时只能选一个)
driver.find_element(By.CLASS_NAME, 's_ipt') # 查找 class="s_ipt" 的元素
# 方式6:通过标签名定位
driver.find_element(By.TAG_NAME, 'input') # 查找第一个 <input> 标签
# 方式7:通过完整链接文字定位
driver.find_element(By.LINK_TEXT, '新闻') # 查找链接文字完全匹配"新闻"的<a>
# 方式8:通过部分链接文字定位
driver.find_element(By.PARTIAL_LINK_TEXT, '新') # 查找链接文字包含"新"的<a>
定位方式选择建议:
- 有ID使用ID
- 复杂条件使用XPATH或CSS
- 链接文字使用LINK_TEXT或PARTIALL_LINK_TEXT
- 多个同类元素,使用find_element(加s)获取列表
1.5 实战示例
python
"""
Selenium基本使用流程:
1. 导入库
2. 配置选项(可选)
3. 创建driver对象
4. 打开网页
5. 定位元素并操作
6. 获取结果
7. 关闭浏览器
"""
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys # 键盘按键
from selenium.webdriver.chrome.options import Options # Chrome配置
from selenium.webdriver.chrome.service import Service
# ==================== 配置无头模式 ====================
# 无头模式:不显示浏览器窗口,后台运行
options = Options()
#options.add_argument('--headless') # 开启无头模式
options.add_argument('--disable-gpu') # 禁用GPU硬件加速
options.add_argument('--window-size=1920,1080') # 设置窗口大小
# ==================== 创建driver并操作 ====================
# 创建Chrome浏览器实例
# 指定 chromedriver 路径
service = Service('chromedriver.exe')
driver = webdriver.Chrome(service=service, options=options)
# 打开百度首页
driver.get('https://www.baidu.com')
# 定位搜索框(通过ID)
search_box = driver.find_element(By.ID, 'kw')
# 在搜索框输入文字
search_box.send_keys('Python')
# 按下回车键(Keys.RETURN = Enter键)
search_box.send_keys(Keys.RETURN)
# 等待页面加载后,获取标题
print(f'页面标题:{driver.title}')
# 截图保存
driver.save_screenshot('result.png')
# ==================== 关闭浏览器 ====================
driver.quit() # 正常关闭
# driver.close() # 只关闭当前窗口,不释放资源
💡使用异常处理机制:
- 1.NoSuchElementException:找不到元素,可能是还没加载完成,需要加等待
- 2.ElementNotInteractableException:元素不可交互,可能在iframe里或被遮挡
- 3.StaleElementReferenceException:元素过期,页面已刷新需要重新定位
- 4.WebDriverException:驱动问题,检查驱动版本是否匹配
2、Selenium等待与交互
2.1 等待用途
在使用Selenium时,常见的问题是"元素找不到",主要是因为:
- 网络延迟导致页面加载慢
- 网页使用Ajax异步加载,数据不是一次性返回
- JavaScript执行需要时间
例如当加载页面时:
- 收到HTML文档(requests获取)
- 解析HTML,发现需要加载JS/CSS/图片
- 执行JavaScript,需要请求更多的数据
- DOM更新,页面内容完成最终渲染
如果在第一步完成后就立刻查找元素,那些通过JS动态生成的内容肯定找不到,因此需要等待。
2.2 等待方式
|------|--------------------------|----------|-----------|----------|
| 等待方式 | 语法 | 优点 | 缺点 | 场景 |
| 强制等待 | time.sleep() | 简单 | 浪费时间 | 调试时临时调用 |
| 隐式等待 | driver.implicitly_wait() | 设置一次全局生效 | 只对于查找元素生效 | 配合显示等待使用 |
| 显示等待 | WebDriverWait() | 条件满足继续 | 代码复杂 | 推荐使用 |
python
import time
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
service = Service('chromedriver.exe')
driver = webdriver.Chrome(service=service)
# ==================== 方式1:强制等待 ====================
# 不管元素是否加载完成,都等够指定时间
time.sleep(3) # 必须等3秒,即使1秒就加载完了
# ==================== 方式2:隐式等待 ====================
# 设置全局等待时间,在查找元素时会轮询直到找到或超时
driver.implicitly_wait(10) # 10秒内一直尝试找元素
driver.get('https://example.com')
element = driver.find_element(By.ID, 'content') # 会等待最多10秒
# ==================== 方式3:显式等待(推荐) ====================
# 更智能,只等到条件满足为止
wait = WebDriverWait(driver, timeout=10) # 最多等10秒
# 等待元素出现(存在DOM中)
element = wait.until(
EC.presence_of_element_located((By.ID, 'content'))
)
# 等待元素可点击
button = wait.until(
EC.element_to_be_clickable((By.ID, 'submit'))
)
# 等待元素可见
visible_element = wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, 'result'))
)
2.3 常用等待条件
expected_conditions 模块提供了预支等待条件:
python
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
# 判断元素存在(DOM中存在即可,不一定可见)
EC.presence_of_element_located((By.ID, 'element_id'))
# 判断元素可见(肉眼可见)
EC.visibility_of_element_located((By.ID, 'element_id'))
# 判断元素可点击(可见且启用)
EC.element_to_be_clickable((By.ID, 'element_id'))
# 判断元素被选中(checkbox/radio)
EC.element_to_be_selected((By.ID, 'element_id'))
# 判断文本出现在元素中
EC.text_to_be_present_in_element((By.ID, 'element_id'), '期望的文本')
# 判断页面标题包含某文字
EC.title_contains('期望的标题')
# 判断元素可交互(可见且启用)
EC.element_to_be_clickable((By.XPATH, '//button[@type="submit"]'))
# 等待多个元素中的任意一个出现
EC.presence_of_all_elements_located((By.TAG_NAME, 'a'))
示例:
python
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
from selenium.webdriver.chrome.service import Service
service = Service('chromedriver.exe')
driver = webdriver.Chrome(service=service)
driver.get('https://www.baidu.com')
# 创建等待对象,设置超时10秒,轮询间隔0.5秒
wait = WebDriverWait(driver, 10, poll_frequency=0.5)
# 等待搜索框出现并可交互
search_box = wait.until(
EC.element_to_be_clickable((By.ID, 'kw'))
)
search_box.send_keys('Selenium')
# 等待搜索按钮出现并可点击
search_button = wait.until(
EC.element_to_be_clickable((By.ID, 'su'))
)
search_button.click()
# 等待搜索结果标题包含关键词
wait.until(EC.title_contains('Selenium'))
print(f'搜索完成,标题:{driver.title}')
2.4 执行JavaScript
python
# ==================== 滚动页面 ====================
# 滚动到页面底部
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
# 滚动到页面顶部
driver.execute_script('window.scrollTo(0, 0)')
# 滚动到指定元素位置
element = driver.find_element(By.ID, 'footer')
driver.execute_script('arguments[0].scrollIntoView();', element)
# ==================== 获取页面信息 ====================
# 获取页面标题
title = driver.execute_script('return document.title')
# 获取页面URL
url = driver.execute_script('return window.location.href')
# 获取页面高度
height = driver.execute_script('return document.body.scrollHeight')
# ==================== 操作隐藏元素 ====================
# 对于隐藏的input,可以直接用JS赋值
driver.execute_script("arguments[0].value='test';", hidden_input)
# ==================== 处理alert弹窗 ====================
# 获取alert文本
alert_text = driver.execute_script("return document.querySelector('.modal').textContent")
# 关闭alert
driver.execute_script("window.confirm = function(){return true;}")
2.5 交互操作
python
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
# ==================== 输入框操作 ====================
element.send_keys('文本') # 输入文本
element.send_keys(Keys.RETURN) # 按回车
element.send_keys(Keys.ENTER) # 按回车(与RETURN等价)
element.send_keys(Keys.CONTROL, 'a') # 全选
element.send_keys(Keys.BACKSPACE) # 删除
element.clear() # 清空输入框
# ==================== 鼠标操作 ====================
# 单击
element.click()
# 右键点击(上下文菜单)
ActionChains(driver).context_click(element).perform()
# 双击
ActionChains(driver).double_click(element).perform()
# 悬停(鼠标移动到元素上)
ActionChains(driver).move_to_element(element).perform()
# 拖拽
source = driver.find_element(By.ID, 'drag')
target = driver.find_element(By.ID, 'drop')
ActionChains(driver).drag_and_drop(source, target).perform()
# ==================== 键盘操作 ====================
# 组合键
ActionChains(driver).key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform()