Selenium 浏览器自动化
Selenium 是一个强大的浏览器自动化工具,最初用于 Web 应用测试,现在广泛用于爬虫开发。它可以模拟真实用户操作浏览器,能够处理 JavaScript 渲染的动态页面。
官方文档
本教程内容基于 Selenium 官方文档。
为什么需要 Selenium?
传统的 requests + BeautifulSoup 方案无法处理以下情况:
- JavaScript 动态渲染:页面内容通过 JS 加载
- 需要交互操作:点击按钮、滚动页面、填写表单
- 登录验证:需要模拟登录才能访问的内容
- 反爬检测:网站检测浏览器指纹
Selenium 通过控制真实浏览器来解决这些问题,但代价是速度较慢、资源消耗较大。
安装配置
安装 Selenium
pip install selenium
# 验证安装
python -c "import selenium; print(selenium.__version__)"
安装浏览器驱动
Selenium 需要浏览器驱动来控制浏览器。推荐使用 webdriver-manager 自动管理驱动:
pip install webdriver-manager
手动安装驱动(可选)
如果需要手动安装,请根据浏览器类型下载对应驱动:
| 浏览器 | 驱动名称 | 下载地址 |
|---|---|---|
| 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/ |
| Safari | SafariDriver | macOS 自带,无需下载 |
驱动版本必须与浏览器版本匹配。下载后将驱动放入系统 PATH 路径中。
快速开始
第一个示例
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
# 配置 Chrome 选项
options = Options()
options.add_argument('--headless') # 无头模式,不显示浏览器窗口
# 创建 WebDriver 实例
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=options
)
try:
# 访问网页
driver.get('https://www.example.com')
# 获取页面标题
print(f'页面标题: {driver.title}')
# 获取页面源码
print(driver.page_source[:500])
finally:
# 关闭浏览器
driver.quit()
使用 Firefox
from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
from webdriver_manager.firefox import GeckoDriverManager
options = Options()
options.add_argument('--headless')
driver = webdriver.Firefox(
service=Service(GeckoDriverManager().install()),
options=options
)
driver.get('https://www.example.com')
print(driver.title)
driver.quit()
浏览器配置
Chrome 常用配置
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 无头模式
options.add_argument('--headless')
# 禁用 GPU(无头模式下推荐)
options.add_argument('--disable-gpu')
# 禁用沙箱(Linux 下可能需要)
options.add_argument('--no-sandbox')
# 禁用共享内存(解决内存不足问题)
options.add_argument('--disable-dev-shm-usage')
# 设置窗口大小
options.add_argument('--window-size=1920,1080')
# 设置 User-Agent
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
# 禁用图片加载(加速爬取)
prefs = {'profile.managed_default_content_settings.images': 2}
options.add_experimental_option('prefs', prefs)
# 禁用自动化检测
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
# 忽略证书错误
options.add_argument('--ignore-certificate-errors')
driver = webdriver.Chrome(options=options)
启动速度优化
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 禁用不必要的功能
options.add_argument('--disable-extensions')
options.add_argument('--disable-infobars')
options.add_argument('--disable-notifications')
options.add_argument('--disable-popup-blocking')
# 禁用日志
options.add_experimental_option('excludeSwitches', ['enable-logging'])
driver = webdriver.Chrome(options=options)
元素定位
Selenium 提供了多种定位元素的方法,通过 By 类指定定位策略。
八种定位方式
from selenium import webdriver
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get('https://example.com')
# 1. 通过 ID 定位(最快最准确)
element = driver.find_element(By.ID, 'username')
# 2. 通过 name 属性定位
element = driver.find_element(By.NAME, 'password')
# 3. 通过 class 名定位
element = driver.find_element(By.CLASS_NAME, 'btn-primary')
# 4. 通过标签名定位
element = driver.find_element(By.TAG_NAME, 'input')
# 5. 通过链接文本定位(精确匹配)
element = driver.find_element(By.LINK_TEXT, '登录')
# 6. 通过部分链接文本定位(模糊匹配)
element = driver.find_element(By.PARTIAL_LINK_TEXT, '注册')
# 7. 通过 CSS 选择器定位(推荐)
element = driver.find_element(By.CSS_SELECTOR, 'div.content > a.link')
# 8. 通过 XPath 定位(最灵活)
element = driver.find_element(By.XPATH, '//div[@class="content"]//a[text()="登录"]')
driver.quit()
定位多个元素
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('https://example.com')
# find_elements 返回列表,找不到返回空列表
links = driver.find_elements(By.TAG_NAME, 'a')
for link in links:
print(link.text, link.get_attribute('href'))
driver.quit()
CSS 选择器详解
CSS 选择器语法简洁,性能好,是推荐的定位方式:
# 基本 CSS 选择器
driver.find_element(By.CSS_SELECTOR, '#username') # id 选择器
driver.find_element(By.CSS_SELECTOR, '.btn-primary') # class 选择器
driver.find_element(By.CSS_SELECTOR, 'input[name="user"]') # 属性选择器
# 层级关系
driver.find_element(By.CSS_SELECTOR, 'div p') # 后代选择器
driver.find_element(By.CSS_SELECTOR, 'div > p') # 子选择器
driver.find_element(By.CSS_SELECTOR, 'div + p') # 相邻兄弟
driver.find_element(By.CSS_SELECTOR, 'div ~ p') # 后续兄弟
# 属性匹配
driver.find_element(By.CSS_SELECTOR, '[type="text"]') # 精确匹配
driver.find_element(By.CSS_SELECTOR, '[class*="btn"]') # 包含
driver.find_element(By.CSS_SELECTOR, '[href^="http"]') # 开头匹配
driver.find_element(By.CSS_SELECTOR, '[href$=".pdf"]') # 结尾匹配
# 伪类选择器
driver.find_element(By.CSS_SELECTOR, 'li:first-child') # 第一个子元素
driver.find_element(By.CSS_SELECTOR, 'li:nth-child(2)') # 第二个子元素
driver.find_element(By.CSS_SELECTOR, 'li:last-child') # 最后一个子元素
XPath 详解
XPath 功能强大,适合复杂的定位场景:
# 基本 XPath
driver.find_element(By.XPATH, '//input') # 所有 input
driver.find_element(By.XPATH, '//div[@id="main"]') # 带属性的 div
driver.find_element(By.XPATH, '//input[@type="text"]') # 带属性的 input
# 文本定位
driver.find_element(By.XPATH, '//a[text()="登录"]') # 精确文本
driver.find_element(By.XPATH, '//a[contains(text(), "登")]') # 包含文本
# 属性包含
driver.find_element(By.XPATH, '//div[contains(@class, "content")]')
# 层级关系
driver.find_element(By.XPATH, '//div[@id="main"]/p') # 子元素
driver.find_element(By.XPATH, '//div[@id="main"]//p') # 后代元素
# 位置选择
driver.find_element(By.XPATH, '//li[1]') # 第一个 li
driver.find_element(By.XPATH, '//li[last()]') # 最后一个 li
driver.find_element(By.XPATH, '//li[position() < 3]') # 前两个 li
# 逻辑运算
driver.find_element(By.XPATH, '//input[@type="text" and @name="user"]')
driver.find_element(By.XPATH, '//input[@type="text" or @type="password"]')
# 轴定位
driver.find_element(By.XPATH, '//div[@id="main"]/parent::div') # 父元素
driver.find_element(By.XPATH, '//div[@id="main"]/following::p') # 之后的所有 p
driver.find_element(By.XPATH, '//div[@id="main"]/preceding::p') # 之前的所有 p
相对定位(Selenium 4 新增)
Selenium 4 引入了相对定位器,可以根据元素之间的空间关系定位:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with
driver = webdriver.Chrome()
driver.get('https://example.com')
# 找到某个元素
password = driver.find_element(By.ID, 'password')
# 在 password 上方的元素
username = driver.find_element(locate_with(By.TAG_NAME, 'input').above(password))
# 在 password 下方的元素
submit = driver.find_element(locate_with(By.TAG_NAME, 'button').below(password))
# 在某个元素左侧
label = driver.find_element(locate_with(By.TAG_NAME, 'label').to_left_of(password))
# 在某个元素右侧
icon = driver.find_element(locate_with(By.TAG_NAME, 'span').to_right_of(password))
# 在某个元素附近
nearby = driver.find_element(locate_with(By.TAG_NAME, 'div').near(password))
driver.quit()
等待机制
网页加载和 JavaScript 执行需要时间,必须使用等待机制确保元素可用。
隐式等待
隐式等待是全局设置,对所有元素定位生效:
from selenium import webdriver
driver = webdriver.Chrome()
# 设置隐式等待时间为 10 秒
driver.implicitly_wait(10)
# 如果元素未立即找到,会等待最多 10 秒
element = driver.find_element(By.ID, 'content')
driver.quit()
隐式等待的特点:
- 全局生效,只需设置一次
- 找到元素后立即返回,不会等待完整时间
- 找不到元素会抛出
NoSuchElementException
显式等待
显式等待针对特定条件,更加灵活和精确:
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://example.com')
# 创建 WebDriverWait 对象,最长等待 10 秒
wait = WebDriverWait(driver, 10)
# 等待元素出现
element = wait.until(EC.presence_of_element_located((By.ID, 'content')))
# 等待元素可见
element = wait.until(EC.visibility_of_element_located((By.ID, 'content')))
# 等待元素可点击
element = wait.until(EC.element_to_be_clickable((By.ID, 'submit')))
# 等待元素消失
wait.until(EC.invisibility_of_element_located((By.ID, 'loading')))
# 等待标题包含特定文本
wait.until(EC.title_contains('Example'))
# 等待 URL 包含特定内容
wait.until(EC.url_contains('dashboard'))
driver.quit()
常用等待条件
from selenium.webdriver.support import expected_conditions as EC
# 元素相关
EC.presence_of_element_located(locator) # 元素存在于 DOM
EC.visibility_of_element_located(locator) # 元素可见
EC.invisibility_of_element_located(locator) # 元素不可见
EC.element_to_be_clickable(locator) # 元素可点击
EC.element_to_be_selected(locator) # 元素被选中
EC.presence_of_all_elements_located(locator) # 所有匹配元素
# 文本相关
EC.text_to_be_present_in_element(locator, text) # 元素包含文本
EC.text_to_be_present_in_element_value(locator, text) # value 包含文本
# 页面相关
EC.title_is(title) # 标题等于
EC.title_contains(title) # 标题包含
EC.url_to_be(url) # URL 等于
EC.url_contains(url) # URL 包含
# 框架和窗口
EC.frame_to_be_available_and_switch_to_it(locator) # 框架可用
EC.new_window_is_opened(current_handles) # 新窗口打开
EC.number_of_windows_to_be(num) # 窗口数量
# Alert
EC.alert_is_present() # 弹窗出现
自定义等待条件
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
driver = webdriver.Chrome()
driver.get('https://example.com')
# 使用 lambda 表达式
wait = WebDriverWait(driver, 10)
element = wait.until(lambda d: d.find_element(By.ID, 'content'))
# 自定义条件函数
def element_has_text(driver, locator, text):
element = driver.find_element(*locator)
return text in element.text
# 使用自定义条件
wait.until(lambda d: element_has_text(d, (By.ID, 'content'), '完成'))
driver.quit()
Fluent Wait(高级等待)
Fluent Wait 提供更精细的控制,可以设置轮询间隔和忽略异常:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import NoSuchElementException, TimeoutException
driver = webdriver.Chrome()
# 创建 Fluent Wait
wait = WebDriverWait(
driver,
timeout=10, # 最长等待时间
poll_frequency=0.5, # 轮询间隔(默认 0.5 秒)
ignored_exceptions=[NoSuchElementException] # 忽略的异常
)
# 使用等待
element = wait.until(lambda d: d.find_element(By.ID, 'content'))
driver.quit()
等待机制选择建议
重要警告
不要混用隐式等待和显式等待,可能导致不可预测的等待时间。
| 场景 | 推荐方式 |
|---|---|
| 简单页面,元素加载快 | 隐式等待 |
| 动态加载,需要精确控制 | 显式等待 |
| 需要特定条件判断 | 显式等待 |
| 复杂场景,需要忽略异常 | Fluent Wait |
元素操作
基本操作
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('https://example.com/form')
# 点击元素
button = driver.find_element(By.ID, 'submit')
button.click()
# 输入文本
input_box = driver.find_element(By.ID, 'username')
input_box.clear() # 先清空
input_box.send_keys('admin') # 输入内容
# 获取元素文本
title = driver.find_element(By.TAG_NAME, 'h1')
print(title.text)
# 获取元素属性
link = driver.find_element(By.TAG_NAME, 'a')
print(link.get_attribute('href'))
print(link.get_attribute('target'))
# 获取元素的 HTML
div = driver.find_element(By.ID, 'content')
print(div.get_attribute('innerHTML'))
driver.quit()
表单操作
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
driver = webdriver.Chrome()
driver.get('https://example.com/form')
# 文本输入
username = driver.find_element(By.NAME, 'username')
username.send_keys('admin')
# 密码输入
password = driver.find_element(By.NAME, 'password')
password.send_keys('123456')
# 复选框
checkbox = driver.find_element(By.NAME, 'remember')
if not checkbox.is_selected():
checkbox.click()
# 单选框
radio = driver.find_element(By.NAME, 'gender')
radio.click()
# 下拉选择框
select_element = driver.find_element(By.NAME, 'country')
select = Select(select_element)
# 选择方式
select.select_by_visible_text('China') # 通过可见文本
select.select_by_value('cn') # 通过 value 属性
select.select_by_index(0) # 通过索引
# 获取选中项
selected_option = select.first_selected_option
print(selected_option.text)
# 取消选择
select.deselect_all()
# 文件上传
file_input = driver.find_element(By.NAME, 'file')
file_input.send_keys('C:/path/to/file.txt') # 发送文件路径
driver.quit()
键盘操作
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get('https://example.com')
input_box = driver.find_element(By.NAME, 'search')
input_box.send_keys('Python')
# 特殊按键
input_box.send_keys(Keys.ENTER) # 回车
input_box.send_keys(Keys.TAB) # Tab 键
input_box.send_keys(Keys.ESCAPE) # Esc 键
input_box.send_keys(Keys.BACK_SPACE) # 退格
input_box.send_keys(Keys.DELETE) # 删除
input_box.send_keys(Keys.CONTROL, 'a') # Ctrl+A 全选
input_box.send_keys(Keys.CONTROL, 'c') # Ctrl+C 复制
input_box.send_keys(Keys.CONTROL, 'v') # Ctrl+V 粘贴
# 组合键
from selenium.webdriver.common.action_chains import ActionChains
ActionChains(driver).key_down(Keys.CONTROL).send_keys('a').key_up(Keys.CONTROL).perform()
driver.quit()
鼠标操作
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome()
driver.get('https://example.com')
element = driver.find_element(By.ID, 'target')
actions = ActionChains(driver)
# 单击
actions.click(element).perform()
# 双击
actions.double_click(element).perform()
# 右键点击
actions.context_click(element).perform()
# 悬停(鼠标移到元素上)
actions.move_to_element(element).perform()
# 拖拽
source = driver.find_element(By.ID, 'source')
target = driver.find_element(By.ID, 'target')
actions.drag_and_drop(source, target).perform()
# 按偏移量拖拽
actions.click_and_hold(source).move_by_offset(100, 0).release().perform()
# 移动到元素偏移位置
actions.move_to_element_with_offset(element, 10, 10).perform()
driver.quit()
滚动操作
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://example.com')
# 滚动到页面底部
driver.execute_script('window.scrollTo(0, document.body.scrollHeight)')
# 滚动到页面顶部
driver.execute_script('window.scrollTo(0, 0)')
# 滚动到指定位置
driver.execute_script('window.scrollTo(0, 500)')
# 滚动到元素可见
element = driver.find_element(By.ID, 'target')
driver.execute_script('arguments[0].scrollIntoView()', element)
# 水平滚动
driver.execute_script('window.scrollTo(500, 0)')
driver.quit()
多窗口与框架
多窗口处理
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('https://example.com')
# 获取当前窗口句柄
main_window = driver.current_window_handle
print(f'主窗口: {main_window}')
# 点击链接打开新窗口
driver.find_element(By.LINK_TEXT, '打开新窗口').click()
# 获取所有窗口句柄
all_windows = driver.window_handles
print(f'所有窗口: {all_windows}')
# 切换到新窗口
for window in all_windows:
if window != main_window:
driver.switch_to.window(window)
break
# 在新窗口中操作
print(driver.title)
# 关闭当前窗口
driver.close()
# 切换回主窗口
driver.switch_to.window(main_window)
driver.quit()
iframe 处理
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('https://example.com')
# 通过索引切换(从 0 开始)
driver.switch_to.frame(0)
# 通过 name 或 id 切换
driver.switch_to.frame('frame_name')
driver.switch_to.frame('frame_id')
# 通过 WebElement 切换
frame_element = driver.find_element(By.TAG_NAME, 'iframe')
driver.switch_to.frame(frame_element)
# 在 frame 内操作
print(driver.find_element(By.ID, 'content').text)
# 切换回主文档
driver.switch_to.default_content()
# 切换到父 frame(嵌套 frame 时使用)
driver.switch_to.parent_frame()
driver.quit()
Alert 弹窗处理
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://example.com')
# 触发 alert
driver.find_element(By.ID, 'alert_button').click()
# 等待 alert 出现
wait = WebDriverWait(driver, 10)
alert = wait.until(EC.alert_is_present())
# 获取 alert 文本
print(alert.text)
# 接受 alert(点击确定)
alert.accept()
# 取消 alert(点击取消)
alert.dismiss()
# 在 prompt 中输入文本
alert.send_keys('输入内容')
alert.accept()
driver.quit()
执行 JavaScript
Selenium 可以直接执行 JavaScript 代码,实现更灵活的操作:
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://example.com')
# 执行简单脚本
driver.execute_script('alert("Hello")')
# 执行脚本并获取返回值
title = driver.execute_script('return document.title')
print(title)
# 传递参数
element = driver.find_element(By.ID, 'target')
driver.execute_script('arguments[0].style.border = "3px solid red"', element)
# 滚动到元素
driver.execute_script('arguments[0].scrollIntoView()', element)
# 点击被遮挡的元素
driver.execute_script('arguments[0].click()', element)
# 修改元素属性
driver.execute_script('arguments[0].setAttribute("value", "new value")', element)
# 获取元素的计算样式
style = driver.execute_script('return window.getComputedStyle(arguments[0])', element)
# 异步执行脚本
driver.execute_async_script('''
var callback = arguments[arguments.length - 1];
setTimeout(function() {
callback('done');
}, 1000);
''')
driver.quit()
Cookie 管理
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://example.com')
# 获取所有 Cookie
cookies = driver.get_cookies()
for cookie in cookies:
print(cookie)
# 获取单个 Cookie
cookie = driver.get_cookie('session_id')
print(cookie)
# 添加 Cookie
driver.add_cookie({
'name': 'user_token',
'value': 'abc123',
'domain': 'example.com',
'path': '/',
'secure': True
})
# 删除单个 Cookie
driver.delete_cookie('user_token')
# 删除所有 Cookie
driver.delete_all_cookies()
driver.quit()
截图与页面源码
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('https://example.com')
# 截取整个页面
driver.save_screenshot('screenshot.png')
# 截取元素
element = driver.find_element(By.ID, 'content')
element.screenshot('element.png')
# 获取截图为 base64
screenshot_base64 = driver.get_screenshot_as_base64()
# 获取截图为二进制数据
screenshot_png = driver.get_screenshot_as_png()
# 获取页面源码
html = driver.page_source
with open('page.html', 'w', encoding='utf-8') as f:
f.write(html)
driver.quit()
无头模式爬虫实战
下面是一个完整的无头模式爬虫示例:
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
from webdriver_manager.chrome import ChromeDriverManager
import time
import json
import random
class SeleniumSpider:
def __init__(self, headless=True):
self.options = Options()
if headless:
self.options.add_argument('--headless')
self.options.add_argument('--disable-gpu')
self.options.add_argument('--no-sandbox')
self.options.add_argument('--disable-dev-shm-usage')
self.options.add_argument('--window-size=1920,1080')
# 设置 User-Agent
user_agents = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
]
self.options.add_argument(f'user-agent={random.choice(user_agents)}')
# 禁用自动化检测
self.options.add_experimental_option('excludeSwitches', ['enable-automation'])
self.options.add_experimental_option('useAutomationExtension', False)
# 创建驱动
self.driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
options=self.options
)
# 设置隐式等待
self.driver.implicitly_wait(10)
# 显式等待
self.wait = WebDriverWait(self.driver, 15)
def get(self, url):
"""访问页面"""
self.driver.get(url)
# 随机延迟,模拟人类行为
time.sleep(random.uniform(0.5, 1.5))
def wait_for_element(self, locator, timeout=15):
"""等待元素出现"""
wait = WebDriverWait(self.driver, timeout)
return wait.until(EC.presence_of_element_located(locator))
def wait_for_clickable(self, locator, timeout=15):
"""等待元素可点击"""
wait = WebDriverWait(self.driver, timeout)
return wait.until(EC.element_to_be_clickable(locator))
def scroll_to_bottom(self):
"""滚动到页面底部"""
self.driver.execute_script(
'window.scrollTo(0, document.body.scrollHeight)'
)
time.sleep(1)
def scroll_page(self):
"""模拟滚动页面"""
total_height = self.driver.execute_script(
'return document.body.scrollHeight'
)
for i in range(0, total_height, random.randint(200, 400)):
self.driver.execute_script(f'window.scrollTo(0, {i})')
time.sleep(random.uniform(0.1, 0.3))
def extract_data(self, url):
"""提取数据示例"""
self.get(url)
# 等待内容加载
self.wait_for_element((By.CLASS_NAME, 'content'))
# 滚动页面加载更多内容
self.scroll_page()
# 提取数据
items = []
elements = self.driver.find_elements(By.CSS_SELECTOR, '.item')
for elem in elements:
try:
title = elem.find_element(By.CSS_SELECTOR, '.title').text
price = elem.find_element(By.CSS_SELECTOR, '.price').text
link = elem.find_element(By.TAG_NAME, 'a').get_attribute('href')
items.append({
'title': title,
'price': price,
'link': link
})
except Exception as e:
print(f'提取失败: {e}')
continue
return items
def save_to_json(self, data, filename):
"""保存数据到 JSON"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f'已保存 {len(data)} 条数据到 {filename}')
def close(self):
"""关闭浏览器"""
self.driver.quit()
# 使用示例
if __name__ == '__main__':
spider = SeleniumSpider(headless=True)
try:
# 访问页面
spider.get('https://example.com')
# 获取标题
print(f'页面标题: {spider.driver.title}')
# 提取数据
items = spider.extract_data('https://example.com/products')
# 保存数据
spider.save_to_json(items, 'products.json')
finally:
spider.close()
处理动态加载
很多网站使用无限滚动或点击加载更多,需要特殊处理:
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
import time
driver = webdriver.Chrome()
driver.get('https://example.com/infinite-scroll')
wait = WebDriverWait(driver, 10)
# 记录已加载的项目数
last_count = 0
max_scrolls = 10 # 限制滚动次数
for i in range(max_scrolls):
# 获取当前项目数
items = driver.find_elements(By.CSS_SELECTOR, '.item')
current_count = len(items)
print(f'滚动 {i+1}: 当前 {current_count} 个项目')
# 如果没有新项目加载,停止
if current_count == last_count:
print('没有更多内容')
break
last_count = current_count
# 滚动到底部
driver.execute_script(
'window.scrollTo(0, document.body.scrollHeight)'
)
# 等待新内容加载
time.sleep(2)
# 提取所有数据
items = driver.find_elements(By.CSS_SELECTOR, '.item')
print(f'总共加载 {len(items)} 个项目')
driver.quit()
绕过反爬检测
隐藏自动化特征
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 禁用自动化标志
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=options)
# 执行脚本隐藏 webdriver 属性
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
})
driver.get('https://example.com')
模拟人类行为
import random
import time
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome()
def human_like_input(element, text):
"""模拟人类输入"""
for char in text:
element.send_keys(char)
time.sleep(random.uniform(0.05, 0.15))
def human_like_click(element):
"""模拟人类点击"""
actions = ActionChains(driver)
# 移动到元素附近
actions.move_to_element(element)
time.sleep(random.uniform(0.1, 0.3))
# 点击
actions.click()
actions.perform()
# 随机延迟
time.sleep(random.uniform(0.5, 1.5))
# 使用
driver.get('https://example.com')
input_box = driver.find_element(By.NAME, 'search')
human_like_input(input_box, 'Python 爬虫')
button = driver.find_element(By.ID, 'submit')
human_like_click(button)
性能优化
禁用图片和 CSS
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 禁用图片
prefs = {
'profile.managed_default_content_settings.images': 2,
'profile.managed_default_content_settings.stylesheets': 2,
}
options.add_experimental_option('prefs', prefs)
driver = webdriver.Chrome(options=options)
复用浏览器会话
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
# 使用远程调试端口
options.add_argument('--remote-debugging-port=9222')
driver = webdriver.Chrome(options=options)
# 后续可以连接到同一个端口复用浏览器
# options.add_argument('--remote-debugging-port=9222')
# driver = webdriver.Chrome(options=options)
小结
本章我们学习了:
- 安装配置 - Selenium 和浏览器驱动的安装
- 浏览器配置 - 无头模式、窗口大小、User-Agent 等
- 元素定位 - 八种定位方式和相对定位
- 等待机制 - 隐式等待、显式等待、Fluent Wait
- 元素操作 - 点击、输入、表单、键盘鼠标
- 高级操作 - 多窗口、iframe、Alert、JavaScript 执行
- 实战技巧 - 无头模式爬虫、动态加载、反爬绕过
练习
- 使用 Selenium 登录一个网站并获取登录后的页面内容
- 实现一个处理无限滚动页面的爬虫
- 编写一个自动填写表单的脚本